Блокировки SQL Server - избегайте вставки повторяющихся записей - PullRequest
2 голосов
/ 05 декабря 2009

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

Давайте предположим, что у нас есть таблица с именем t3:

create table t3 (a int , b int);
create index test on t3 (a);

и следующий запрос:

INSERT INTO T3
SELECT -86,-86
WHERE NOT EXISTS (SELECT 1 FROM t3 where t3.a=-86);

Запрос вставляет строку в таблицу t3 после проверки того, что строка еще не существует, на основе столбца "a".

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

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

  1. Сначала выполняется подзапрос.
  2. Механизм базы данных устанавливает общую (ые) блокировку для диапазона.
  3. Данные читаются.
  4. Общая блокировка снята. По данным MSDN общий доступ Блокировка снимается, как только данные был прочитан.
  5. Если строка не существует, она вставляет новую строку в таблицу.
  6. Новая линия заблокирована эксклюзивным замком (х)

Теперь рассмотрим следующий сценарий:

  1. Вышеприведенный запрос выполняется процессором A (SPID 1).
  2. Тот же запрос выполняется процессор B (SPID 2).
  3. [SPID 1] Механизм базы данных устанавливает общую (ые) блокировку (и)
  4. [SPID 1] Подзапрос читает данные. Теперь строки возвращаются.
  5. [SPID 1] Общая блокировка освобожден.
  6. [SPID 2] Механизм базы данных устанавливает общий (ые) замок
  7. [SPID 2] Подзапрос читает данные. Строки не возвращаются.
  8. [SPID 2] Общая блокировка освобожден.
  9. Оба процесса продолжают вставку строки (и мы получаем повторяющуюся запись).

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

Безопасный способ избежать дублирования записей - использовать приведенный ниже код, но мне просто интересно, правильный ли вышеуказанный метод.

begin tran
    if (SELECT 1 FROM t3 with (updlock) where t3.a=-86)
    begin
        INSERT INTO T3
        SELECT -86,-86
    end
commit

Ответы [ 2 ]

8 голосов
/ 06 декабря 2009

Если у вас есть уникальное ограничение для столбца, у вас никогда не будет дубликатов.

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

Я хотел бы добавить, что использование "внешнего" кода (даже T-SQL) для обеспечения согласованности вашей базы данных не является хорошей идеей. Во всех случаях использование декларативной ссылочной целостности на уровне таблицы важно для базы данных, чтобы обеспечить согласованность и соответствие ожиданиям независимо от того, написан ли код приложения хорошо или нет. Как и в области безопасности, вам необходимо использовать стратегию глубокой защиты - ограничения, уникальные индексы, триггеры, хранимые процедуры и представления могут помочь в создании многоуровневого подхода, гарантирующего, что база данных предоставляет согласованный и надежный интерфейс для приложения. или система.

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

Чтобы сохранить блокировки между несколькими операторами, они должны быть заключены в транзакцию. В вашем примере:

If (SELECT 1 FROM t3 with (updlock) where t3.a=-86)
    INSERT INTO T3 SELECT -86,-86

Блокировка обновления может быть снята до выполнения вставки. Это будет работать надежно:

begin transaction
If (SELECT 1 FROM t3 with (updlock) where t3.a=-86)
    INSERT INTO T3 SELECT -86,-86
commit transaction

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

 INSERT INTO T3 SELECT -86,-86
 WHERE NOT EXISTS (SELECT 1 FROM t3 with (updlock) where t3.a=-86)

(Предполагается, что у вас отключены «неявные транзакции», например, настройки SQL Server по умолчанию.)

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