2014-11-05 2 views
2

Я хотел бы создать веб-службу, которая позволяет клиенту извлекать все строки в таблице, а затем позволяет клиенту получать только новые или обновленные строки.Отправка только обновленных строк клиенту

Простейшая реализация, похоже, заключается в отправке текущей метки времени клиенту, а затем попросите клиента запросить строки, которые являются более новыми, чем метка времени в следующем запросе.

Похоже, что это можно сделать, сохранив столбец «updated_at» с отметкой времени, установленной в NOW(), в обновлении и вставке триггеров, а затем запросив более новые строки, а также передав значение NOW().

Проблема заключается в том, что если есть незавершенные транзакции, эти операции будут устанавливать updated_at ко времени старта сделки, не времени фиксации.

В результате эта простая реализация не работает, поскольку строки могут быть потеряны, поскольку они могут появляться с отметкой времени в прошлом.

Мне не удалось найти простого решения этой проблемы, несмотря на то, что это кажется очень распространенной потребностью: любые идеи?

Возможные решения:

  1. Keep монотонную временную метку в виде таблицы, обновлять его в начале каждой транзакции до MAX (теперь(), last_timestamp + 1) и использовать его в качестве строки метки времени. Проблема: это эффективно означает, что все транзакции записи полностью сериализованы и блокируют всю базу данных, так как они конфликтуют в таблице времени обновления.

  2. В конце транзакции добавьте сопоставление от NOW() к времени в таблице обновлений, как описано выше. Кажется, это требует явного блокирования и использования последовательности для создания невременных «временных меток», потому что просто использование UPDATE в одной строке приведет к откатам в режиме SERIALIZABLE.

  3. Как-то у PostgreSQL, время фиксации, перебрать все обновленные строки и установить updated_at к монотонному метки времени

  4. Как-то есть сам PostgreSQL поддерживать таблицу транзакции раз, что он, кажется, не делать на данный момент

Использование встроенного столбца xmin также представляется невозможным, поскольку VACUUM может удалить его.

Было бы неплохо иметь возможность сделать это в базе данных без изменений ко всем обновлениям приложения.

Как это обычно делается?

Проблема с наивными решениями

В случае это не очевидно, что это проблема с использованием NOW() или CLOCK_TIMESTAMP():

  1. Во время 1, мы проводим сейчас () или CLOCK_TIMESTAMP() в транзакции, которая дает 1, и мы обновляем время установки строки 1 как время обновления
  2. В момент 2 клиент извлекает все строки, и мы сообщаем ему, что мы дали все строки до момента времени 2
  3. В момент 3 транзакция фиксируется с «временем 1» в поле updated_at
  4. Клиент запрашивает обновленные строки со времени 2 (время, которое он получил от предыдущего запроса полной выборки), мы запрашиваем update_at> = 2, и мы не возвращать, вместо того, чтобы вернуться в строке, которая только что добавил
  5. Этой строка теряется и никогда не видела клиент
+0

Это еще не время фиксации, поэтому оно не будет работать. То есть транзакция может совершить секунду после вызова функции clock_timestamp(), и обновление теряется, если клиент обновляется тем временем. –

+0

Это не работает по той же причине (если вы не сделаете это под замком). –

+0

Запуск этой точной проблемы, а также поиск ответов. –

ответ

1

Вашего целое предложение идет вразрез с некоторыми из основополагающих основ кислотного-совместимых СУБД, как PostgreSQL , Время начала транзакции (например, current_timestamp()) и другие временные показатели не имеют смысла в качестве меры того, что получил конкретный клиент или нет. Отбросьте всю идею.

Предполагая, что ваши клиенты подключаются через постоянный сеанс в базу данных вы можете выполнить следующие действия:

  • Когда начинается сеанс, CREATE TEMP UNLOGGED TABLE для пользователя сессии. Эта таблица содержит только PK и последнее время обновления таблицы, из которой вы хотите получить данные.
  • Клиент проверяет новые данные и получает только те записи, у которых PK еще нет в таблице temp или в существующем PK, но более позднем последнем времени обновления. В настоящее время незафиксированные транзакции невидимы, но будут получены в следующем опросе для новых или обновленных записей. Время обновления требуется, потому что нет способа удалить записи из временных таблиц всех одновременных клиентов.
  • PK и последнее время восстановления записанной записи хранятся в таблице темпа.
  • Когда пользователь закрывает сеанс, временная таблица удаляется.

Если вы хотите сохранить извлеченные записи по нескольким сеансам для каждого клиента или клиент отключается после каждого запроса, тогда вам нужна обычная таблица, но затем я бы предложил также добавить oid пользователя, чтобы все пользователи могут использовать одну таблицу для отслеживания извлеченных записей. В этом последнем случае вы можете создать триггер в таблице с вашими данными, которые удаляют PK из таблицы с извлеченными записями для всех пользователей в одной развертке. В следующем опросе клиенты получат обновленную запись.

1

Добавьте столбец, который будет использоваться для отслеживания того, что запись была отправлена ​​клиенту:

alter table table_under_view 
    add column access_order int null; 

create sequence table_under_view_access_order_seq 
    owned by table_under_view.access_order; 

create function table_under_view_reset_access_order() 
    returns trigger 
    language plpgsql 
as $func$ 
    new.access_order := null; 
$func$; 

create trigger table_under_view_reset_access_order_before_update 
    before update on table_under_view 
    for each row execute procedure table_under_view_reset_access_order(); 

create index table_under_view_access_order_idx 
    on table_under_view (access_order); 

create index table_under_view_access_order_where_null_idx 
    on table_under_view (access_order) 
    where (access_order is null); 

(Вы можете использовать before insert on table_under_view триггер тоже, чтобы обеспечить только NULL значения вставляются в access_order).

Вам необходимо обновить этот столбец после транзакций с INSERT s & UPDATE s на этой таблице закончен, но перед тем, как любой клиент запросит ваши данные. Вы ничего не можете сделать только после транзакция завершена, поэтому давайте сделаем это до того, как произойдет запрос. Вы можете сделать это с помощью функции, f.ex:

create function table_under_access(from_access int) 
    returns setof table_under_view 
    language sql 
as $func$ 
    update table_under_view 
    set access_order = nextval('table_under_view_access_order_seq'::regclass) 
    where access_order is null; 

    select * 
    from table_under_view 
    where access_order > from_access; 
$func$; 

Теперь ваша первая «порция» данных (который будет получать все строки в таблице), выглядит следующим образом:

select * 
from table_under_access(0); 

ключевой элемент после этого заключается в том, что ваш клиент должен обрабатывать каждый «кусок» данных, чтобы определить, какой из них наибольший access_order, который он получил последним (если вы не включили его в свой результат с функциями окна f.ex., но если вы собираетесь обрабатывать результаты - что кажется весьма вероятным - вам это не нужно). Всегда используйте это для последующих вызовов.

Вы также можете добавить столбец updated_at, чтобы заказать свои результаты, если хотите.

Вы также можете использовать вид + rule (s) для последней части (вместо функции), чтобы сделать ее более прозрачной.

Смежные вопросы