SQL - одновременная вставка ключей в таблицу - PullRequest
3 голосов
/ 17 февраля 2011

Возможно, это тривиальный вопрос, но я хочу найти наилучшее из возможных решений.

Проблема:

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

  1. Рабочий A читает таблицу, если ключ существует (SELECT). Там нет ключа.
  2. Рабочий B читает таблицу, если ключ существует (SELECT). Там нет ключа.
  3. Рабочий А вставляет ключ.
  4. Рабочий B вставляет ключ.
  5. Работник А совершает.
  6. Работник Б фиксирует. Исключение составляют броски, так как уникальное ограничение нарушено

Таблицы ключей - это простые пары. Первый столбец представляет собой целое число автоинкремента, а второй - ключ varchar.

Как лучше всего решить проблему параллелизма? Я считаю, что это общая проблема. Одним из способов наверняка является обработка исключений, но почему-то я не верю, что это лучший способ решить эту проблему.

База данных, которую я использую, если Firebird 2.5

EDIT

Некоторая дополнительная информация, чтобы прояснить ситуацию.

  1. Синхронизация на стороне клиента не является хорошим подходом, потому что вставки происходят из разных процессов (рабочих). И когда-нибудь у меня будут рабочие на разных машинах, так что даже мьютексы не нужны.
  2. Первичным ключом и первыми столбцами такой таблицы является поле автоинкремента. Нет проблем там. Проблема заключается в поле varchar, так как оно вставляется клиентом.

Типичной такой таблицей является таблица пользователей. Например:

1  2056
2  1044
3  1896
4  5966
...

Каждый работник проверяет, существует ли пользователь "xxxx" и, если нет, вставляет его.

РЕДАКТИРОВАТЬ 2 :

Просто для справки, если кто-то пойдет по тому же маршруту. IB / FB возвращает пару кодов ошибок (я использую компоненты InterBase Express). Проверка на наличие дублирующихся значений выглядит следующим образом:

except
  on E: EIBInterBaseError do
  begin
    if (E.SQLCode = -803) and (E.IBErrorCode = 335544349) then
    begin
      FKeysConnection.IBT.Rollback;
      EnteredKeys := False;
    end;
  end;
end;

Ответы [ 6 ]

6 голосов
/ 17 февраля 2011

В Firebird вы можете использовать следующую инструкцию:

UPDATE OR INSERT INTO MY_TABLE (MY_KEY) VALUES (:MY_KEY) MATCHING (MY_KEY) RETURNING MY_ID
  • при условии, что есть триггер BEFORE INSERT, который сгенерирует MY_ID, если будет вставлено значение NULL.

Вот документация .

Обновление : вышеприведенный оператор будет избегать исключений и приведет к успешному завершению каждого оператора.Тем не менее, в случае большого количества повторяющихся значений ключей это также вызовет много ненужных обновлений.Этого можно избежать другим подходом: просто обработайте уникальное исключение ограничения на клиенте и проигнорируйте его.Детали зависят от того, какую библиотеку Delphi вы используете для работы с Firebird, но должна быть возможность изучить SQLCode, возвращаемый сервером, и игнорировать только конкретный случай нарушения уникального ограничения.

2 голосов
/ 17 февраля 2011

Первый вариант - не делай этого.

Не делай этого; Если РАБОТНИКИ не выполняют необычайно много работы (мы говорим о компьютерах, поэтому требуется 1 секунда на запись, которая квалифицируется как «необычайно много работы»), просто используйте один поток; Более того, выполняя всю работу в хранимой процедуре, вы будете удивлены ускорением, достигнутым за счет того, что данные не передаются по протоколу в ваше приложение.

Второй вариант - использовать очередь

Убедитесь, что все ваши рабочие потоки не работают с одним и тем же идентификатором. Настройте очередь, поместите все идентификаторы, которые необходимо обработать, в эту очередь, чтобы каждый рабочий поток вытеснил идентификатор из этой очереди. Таким образом, вы гарантированно не работаете над двумя записями одновременно. Это может быть трудно реализовать, если ваши работники не являются частью одного и того же процесса.

Последнее средство

Настройте основанную на БД систему «Резервирование», чтобы рабочий поток мог пометить ключ как «незавершенный», чтобы не было двух рабочих, работающих с одним и тем же ключом. Я бы накрыл стол вот так:

CREATE TABLE KEY_RESERVATIONS (
  KEY INTEGER NOT NULL, /* This is the KEY you'd be reserving */
  RESERVED_UNTIL TIMESTAMP NOT NULL /* We don't want to keep reservations for ever in case of failure */
);

Каждый из ваших работников будет использовать короткие транзакции для работы с этой таблицей: выберите ключ-кандидат, которого нет в таблице KEY_RESERVATIONS. Попробуй ВСТАВИТЬ. Не удалось? Попробуйте другой ключ. Периодически удаляйте все зарезервированные ключи со старыми временными метками RESERVED_UNTIL. Убедитесь, что транзакции для работы с KEY_RESERVATIONS настолько коротки, насколько это возможно, чтобы два потока, одновременно пытающихся зарезервировать один и тот же ключ, потерпели бы неудачу.

2 голосов
/ 17 февраля 2011

Я не знаю, доступно ли что-то подобное в Firebird, но в SQL Server вы можете проверить это при вставке ключа.

insert into Table1 (KeyValue) 
select 'NewKey'
where not exists (select *
                  from Table1
                  where KeyValue = 'NewKey')
1 голос
/ 17 февраля 2011

Это то, с чем вам приходится иметь дело в оптимистической (или без-) схеме блокировки.

Один из способов избежать этого - это поставить пессимистическую блокировку на стол вокруг всего select, insert, commitsequence.

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

Если под рабочими вы подразумеваете потоки в одном экземпляре приложениявместо разных пользователей (экземпляров приложений) вам потребуется синхронизация потоков, как говорит kubal5003 в последовательности select-insert-commit.

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

0 голосов
/ 17 февраля 2011

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

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

Подробнее о правильной реализации autoinc в Firebird здесь: http://www.firebirdfaq.org/faq29/

0 голосов
/ 17 февраля 2011

Синхронизируйте ваши потоки, чтобы было невозможно вставить одно и то же значение или использовать метод генерации ключа на стороне БД (я не знаю Firebird, поэтому даже не знаю, есть ли он, например, в MsSQL Server есть столбец идентификацииили GUID также решают проблему, потому что вряд ли сгенерирует два одинаковых)

...