Эффективное обнаружение одновременных вставок с использованием стандартного SQL - PullRequest
1 голос
/ 15 августа 2011

Требования

У меня есть следующая таблица (псевдо DDL):

CREATE TABLE MESSAGE (
    MESSAGE_GUID GUID PRIMARY KEY,
    INSERT_TIME DATETIME
)

CREATE INDEX MESSAGE_IE1 ON MESSAGE (INSERT_TIME);

Несколько клиентов одновременно вставляют строки в эту таблицу, возможно, много раз в секунду.Мне нужно спроектировать приложение «Монитор» , которое будет:

  1. Изначально извлекать все строки, находящиеся в настоящий момент в таблице.
  2. После этого периодически проверять,вставляются все новые строки, а затем извлекаются только эти строки * .

Возможно одновременное выполнение нескольких мониторов .Все Мониторы должны видеть все строки (то есть, когда строка вставлена, она должна быть "обнаружена" всеми текущими Мониторами ).

ЭтоПервоначально приложение будет разрабатываться для Oracle, но мы должны поддерживать его переносимость на все основные СУБД и избегать как можно большего числа специфических для базы данных данных.

Проблема

Наивное решениебыло бы просто найти максимальный INSERT_TIME в строках, выбранных на шаге 1, а затем ...

SELECT * FROM MESSAGE WHERE INSERT_TIME >= :max_insert_time_from_previous_select

... на шаге 2.

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

  1. Транзакция A вставляет новую строку, но не все же фиксирует.
  2. Транзакция B вставляет новую строку и фиксирует.
  3. Монитор выбирает строки и видит, что максимальный INSERT_TIME - это тот, который вставлен B.
  4. Транзакция A фиксирует транзакцию.На данный момент INSERT_TIME А на самом деле на раньше , чем B (ВСТАВКА А фактически была выполнена до В, прежде чем мы даже знали, кто собирается совершить в первую очередь).
  5. Монитор выбирает строки, более новые, чем INSERT_TIME B (как следствие шага 3).Поскольку INSERT_TIME у A предшествует времени вставки B, строка A пропускается.

Итак, строка, вставленная транзакцией A, никогда не выбирается.

Любые идеи о том, как спроектировать клиентский SQLили даже изменить схему базы данных (если она слегка переносима), чтобы избежать подобных проблем параллелизма, сохраняя при этом приличную производительность?

Спасибо.

Ответы [ 4 ]

2 голосов
/ 15 августа 2011

Без использования какой-либо из платформенно-зависимых технологий сбора данных об изменениях (CDC) существует пара подходов.

Опция 1

Каждая Монитор регистрирует своего рода подписку на таблицу MESSAGE.Код, который пишет сообщения, затем записывает каждый MESSAGE один раз за Монитор , т.е.

CREATE TABLE message_subscription (
  message_subscription_id NUMBER PRIMARY KEY,
  message_id RAW(32) NOT NULLL,
  monitor_id NUMBER NOT NULL,
  CONSTRAINT uk_message_sub UNIQUE (message_id, monitor_id)
);

INSERT INTO message_subscription
  SELECT message_subscription_seq.nextval,
         sys_guid,
         monitor_id
    FROM monitor_subscribers;

Каждый Монитор затем удаляет сообщение из своей подписки после обработки.

Опция 2

Каждый Монитор поддерживает кэш последних обработанных им сообщений, который по крайней мере длится дольше всехтранзакция может быть.Например, если Monitor поддерживает кэш сообщений, обработанных им в течение последних 5 минут, он запросит в вашей таблице MESSAGE все сообщения позже, чем его LAST_MONITOR_TIME.Затем Monitor будет отвечать за то, что некоторые из выбранных строк уже обработаны. Монитор будет обрабатывать только значения MESSAGE_ID, которых нет в его кеше.

Опция 3

Как и в варианте 1, вы настраиваетеподписки для каждого монитора , но вы используете некоторую технологию очередей для доставки сообщений на монитор .Это менее переносимо, чем два других варианта, но большинство баз данных могут доставлять сообщения в приложения через какие-либо очереди (например, очереди JMS, если ваш Monitor является приложением Java).Это избавляет вас от необходимости заново изобретать колесо, создавая собственную таблицу очередей, и предоставляет стандартный интерфейс на уровне приложений для кодирования.

0 голосов
/ 15 августа 2011

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

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

0 голосов
/ 15 августа 2011

Вам следует использовать Oracle AQ с многопользовательской очередью.

Это специфично для Oracle, но вы можете создать уровень абстракции хранимых процедур (или абстрагирования в Java, если хотите), чтобы у вас былобщий API для постановки в очередь новых сообщений и для каждого подписчика (монитора) исключать из очереди все ожидающие сообщения.За этим API для Oracle вы используете AQ.

Я не уверен, есть ли решение для очередей для других баз данных.

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

0 голосов
/ 15 августа 2011

Вы должны иметь возможность идентифицировать все строки, добавленные с момента последней проверки (т. Е. Монитор проверяет).У вас есть столбец "время вставки".Однако, как вы понимаете, этот столбец времени вставки нельзя использовать с логикой «больше, чем [последняя проверка]», чтобы надежно идентифицировать вставленные впоследствии новые элементы.Коммиты не встречаются в том же порядке, что и (начальные) вставки.Я не знаю ничего, что работает на всех основных РСУБД, которые бы четко и безопасно применяли такой тег «как» в момент фактического принятия.[ Это не , чтобы сказать, что я знал бы это, если бы такая вещь существовала, но мне кажется, это довольно безопасное предположение. ] Таким образом, вам придется использовать что-то другое, чемалгоритм «больше, чем последняя проверка».

Это приводит к фильтрации.При вставке элемент (строка) помечается как «еще не отмечен»;когда в систему входит montior, он считывает все еще не проверенные элементы, возвращает этот набор и переключает флаг на «флажок» (и, если имеется несколько мониторов, все это должно быть сделано в пределах его собственной транзакции).Запросы мониторов должны будут прочитать все данные и выбрать те, которые еще не были проверены.Однако подразумевается, что это будет довольно небольшой набор данных, по крайней мере, относительно всего набора данных.Отсюда я вижу два вероятных варианта:

  • Добавить столбец, возможно, «Проверено».Сохраните двоичное значение 1/0 для проверки / не проверки.Мощность этого значения будет предельной - 99,9 с проверено, 00,0 с не проверено, поэтому оно должно быть достаточно эффективным.(Некоторые РСУБД предоставляют отфильтрованные запросы, так что строки Checked даже не будут присутствовать в индексе; после перехода к флажку строка, по-видимому, никогда не будет перевернута обратно, поэтому накладные расходы на поддержку этого не должны быть слишком большими.)
  • Добавьте отдельную таблицу и идентифицируйте те строки в «первичной» таблице, которые еще не были проверены.Когда montior входит в систему, он читает и удаляет элементы из этой таблицы.Это не кажется эффективным ... но опять же, если набор данных небольшой, общая проблема с производительностью может быть приемлемой.
...