Чтение только новых строк из логоподобной таблицы в базе данных - PullRequest
8 голосов
/ 16 декабря 2009

У нас есть предположение, что несколько серверов вставляют порции строк в таблицу в реляционной базе данных, и один сервер время от времени читает новые данные из таблицы. (Таблица концептуально является своего рода лог-файлом - данные только вставляются, но никогда не изменяются, и сервер чтения показывает хвост журнала.) Есть ли способ, чтобы сервер чтения только считывал новые данные? Мы можем структурировать таблицу (таблицы) так, как хотим.

Некоторые идеи, которые приходили мне в голову, но не работали:

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

  • Мы можем вставить метку времени в каждой строке, которая заполнена системным временем базы данных. Проблема в том, что это не временная метка времени фиксации, а время вставки. Если вы попросите базу данных «дать мне все значения в промежутке от 5 минут до настоящего момента», вы не сможете рассчитывать на то, что все значения присутствуют, так как могут происходить транзакции. Позже вам нужно будет снова запросить значения в этом интервале, чего я и хотел избежать.

  • Мы могли бы вставить счетчик бегущих строк, заполненный из последовательности. С выполнением транзакций возникает та же проблема, что и при использовании меток времени.

Есть ли какое-либо решение этой проблемы, или мне нужно применить некоторые эвристики, например, предполагая максимальное время транзакции и всегда запрашивая значения, записанные после «сейчас - максимальное время транзакции» и считывая некоторые данные дважды?

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

Ответы [ 6 ]

4 голосов
/ 17 декабря 2009

MS SQL имеет свое конкретное решение:

Вы можете добавить столбец типа rowversion в таблицу. Этот столбец будет автоматически обновляться в связанных строках ядром при любом операторе update / insert.

Если писатель использует ReadCommitted уровень изоляции, тогда читатель может использовать ReadUncommitted уровень изоляции (поэтому ему не нужно ждать завершения всех транзакций, прежде чем возвращать какие-либо результаты), но с запросом, подобным этому:

SELECT * FROM [Log]
WHERE Version > @LastKnownVersion
    AND Version < MIN_ACTIVE_ROWVERSION()

Где @LastKnownVersion - максимальная версия строки, обрабатываемая считывателем, а MIN_ACTIVE_ROWVERSION() - встроенная функция MS SQL, которая возвращает минимальный номер версии строки, которая все еще находится в транзакции.

Так что с этим решением, даже если у вас есть зафиксированный ID = 4, но еще нет ID = 3, он вернет только те строки, которые были изменены до ID = 3, потому что его версия будет точно MIN_ACTIVE_ROWVERSION().

Преимущество этого метода заключается в том, что читателю не нужно ждать, пока транзакция будет зафиксирована, прежде чем получить какие-либо результаты, что может иметь решающее значение при большом количестве авторов. (Читатель может быть заблокирован навсегда.)

3 голосов
/ 17 декабря 2009

Используемая база данных не была указана, поэтому неясно, нужно ли решение вбивать в существующее развертывание или нет. Есть некоторые механизмы очередей, которые могут быть подключены к MySQL, которые потенциально могут работать. Одним из них является Q4M . Некоторые коммерческие базы данных, такие как Oracle, имеют функциональность временной базы данных, которая позволяет определять время транзакции по сравнению с действительным временем по сравнению с реальным временем.

При использовании Oracle псевдостолбец ora_rowscn или полезная комбинация scn_to_timestamp(ora_rowscn) могут эффективно обеспечить отметку времени, когда строка была зафиксирована (SCN, в которой она имела место). В качестве альтернативы Oracle Workspace Manager предоставляет таблицы с поддержкой версий, в основном это выглядит так: вы включаете управление версиями в таблице с DBMS_WM.EnableVersioning(...), строки вставляются с дополнительным полем WMSYS.WM_PERIOD(...), указывающим допустимый диапазон времени, устанавливаете допустимый диапазон для рабочая область настроена на считыватель DBMS_WM.SetValidTime(...).

Вы также можете в определенной степени подделать эту функциональность, объединив свою идею метки времени с эвристикой времени фиксации. Идея состоит в том, чтобы просто сохранить «действительное время» в виде столбца вместе с данными вместо использования произвольной дельты from now (). Другими словами, столбец вторичной метки времени, который будет указывать некоторую будущую дату («действительное время») на основе эвристики времени фиксации + некоторое приемлемое окно задержки (возможно, среднее время фиксации + двойное стандартное отклонение). В качестве альтернативы можно использовать некоторое значение ceil () среднего времени фиксации («хотя бы время фиксации, но округляя до, скажем, 30-секундных интервалов»). Последний будет эффективно квантовать (объединять?) Записи журнала времени. Кажется, это не слишком отличается, но этот способ избавит вас от чтения лишних строк. Это также решает проблему, заключающуюся в том, что приложение для чтения не может точно знать время фиксации приложения для записи, не написав намного больше кода.

0 голосов
/ 16 декабря 2009

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

так что если у вас есть:

id | log
1  | blah
2  | blah again
3  | more blah
* transaction to insert row '4' in progress

тогда вы получите все эти журналы и записали 3 как последний найденный идентификатор. И при следующем запуске:

выберите идентификатор, войдите из журналов, где идентификатор> last_recorded идентификатор порядка по идентификатору #id будет 3

4  | yet again some blah
5  | does this blah never end
6  | omg blah

и запишите 6 как ваш новый последний записанный идентификатор. Я все еще думаю, что это хорошо, чтобы сохранить даты, когда журнал был также сделан.

РЕДАКТИРОВАТЬ, чтобы поймать все, что вам нужно, вам нужно будет сохранить набор всех записей, прочитанных в альтернативном месте, а затем получить разницу между набором чтения и активной таблицей журнала. Если вы не можете дотронуться до таблицы журналов, тогда вы просто имеете дело с наборами и находите то, чего нет в одном из наборов.

0 голосов
/ 16 декабря 2009

Это возможное решение, в зависимости от вашей ситуации и т. Д.

Иметь столбец с именем "read_timestamp", который является нулевым, после прочтения строки процесс чтения обновит его до ненулевой отметки времени.

Читатель запрашивает в этой таблице выражение «где read_timestamp равно нулю».

Более простым решением было бы пойти с оценкой (то есть эта строка , возможно, уже рассматривалась своего рода предостережение). Следовательно, вы могли бы в любое время отобразить «последние 50 строк» ​​или «строки, поступившие за последние 10 минут» (с неточностью, которую другой просмотрщик журнала, возможно, уже вытянул).

Третье решение состоит в том, чтобы передать эти строки, используя внутренний процесс, в очередь: при каждом чтении строки строка исчезает из очереди (потому что это операция «pop»). Следовательно, строку можно просмотреть только один раз (первым пришел-первым обслужен).

0 голосов
/ 16 декабря 2009

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

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

0 голосов
/ 16 декабря 2009

Создать другую таблицу LOG_REVISION. Содержит одну строку (INTEGER).

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

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

[РЕДАКТИРОВАТЬ] Есть еще два способа обойти это:

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