Обновления значений индекса MS SQL

Установка: SQL Server 2008 R2

Предыстория: у нас есть процедура, которая вызывается сразу в нескольких потоках. Эти многопоточные вызовы процедур иногда записывают перекрывающиеся (дублирующиеся) данные в таблицу, в которую они записываются. В операторе вставки у меня есть предложение «НЕ СУЩЕСТВУЕТ», чтобы убедиться, что дубликаты не вставлены, но мы все еще получаем дубликаты.

Вопрос. Предложение "НЕ СУЩЕСТВУЕТ" выполняет простой выбор, возможно ли, что, поскольку вставки для потоковых вызовов разделены всего миллисекундами, некоторые индексы (в частности, те, которые используются оператором "НЕ EXISTS" еще не обновлены? Поэтому он не видит существующую запись перед вставкой?

Мысли: Возможно, я не понимаю, как SQL делает свое дело. Если у меня есть вставка с «ГДЕ НЕ СУЩЕСТВУЕТ», проверяет ли она, чтобы убедиться, что ни одна из записей не существует, прежде чем выполнять вставку? Или он проверяет построчно, когда вставляет каждую строку? Если это первое (проверка всего перед выполнением какой-либо вставки), то я полагаю, что возможно, что один из других вызовов еще не завершил вставку.

Я в тупике.

Вот пример того, что я делаю:

INSERT INTO [SomeTable] (Col1,Col2)
SELECT
    ColumnA,
    ColumnB
FROM
    #TempTable
WHERE 
    NOT EXISTS (
        SELECT 1
        FROM [SomeTable]
        WHERE Col1 = #TempTable.ColumnA
              AND Col2 = #TempTable.ColumnB
    )

person Mark Wilkinson    schedule 06.06.2013    source источник
comment
Не могли бы вы показать нам код? Гадание занимает слишком много времени. См. Как составить идеальный вопрос.   -  person HABO    schedule 06.06.2013
comment
Используете ли вы транзакции в своем коде?   -  person George Mastros    schedule 06.06.2013
comment
Никаких транзакций, а процедура состоит из 10 000 строк. :) Однако это прямая вставка, она вставляет записи из временной таблицы в пользовательскую таблицу, где запись еще не существует. Если вы запустите процедуру для сотрудника, а затем снова запустите ее сразу после этого, она ничего не вставит, так как увидит, что записи уже есть.test   -  person Mark Wilkinson    schedule 06.06.2013
comment
Добавлен пример кода. Да, это так просто в производственном коде.   -  person Mark Wilkinson    schedule 06.06.2013


Ответы (2)


Вам необходимо заблокировать таблицу на время транзакции. Другие потоки будут ждать завершения транзакции (фиксация или откат).

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ

BEGIN TRANSACTION

--do stuff

COMMIT TRANSACTION

http://msdn.microsoft.com/en-us/library/ms173763.aspx

person JC Ford    schedule 06.06.2013
comment
Поскольку OP проверяет существование, ему также необходимо иметь возможность заблокировать несуществующую строку. REPEATABLE READ этого не предусматривает. - person Sebastian Meine; 06.06.2013

SQL Server по умолчанию использует уровень изоляции READ COMMITTED. Это означает, что во время выбора блокировка чтения для каждой строки удерживается только тогда, когда выбор активно обращается к этой строке.

Это означает, что после выполнения SELECT и до того, как произойдет INSERT, другой поток может вставить строку, даже если этот промежуток составляет всего несколько наносекунд.

Даже оператор MERGE не препятствует одновременным вставкам, поскольку между частью поиска и частью вставки есть некоторое время. (См. http://sqlity.net/en/1645/merge-wonders-insert-or-use/ для более подробной информации.)

Вам нужна блокировка для несуществующей строки для всей транзакции, которая включает проверку и следующую вставку. Единственный способ сделать это — использовать транзакцию-оболочку и установить уровень изоляции транзакции SERIALIZABLE.

Что-то типа:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRAN
  IF(NOT EXISTS(SELECT ...))
  BEGIN
   INSERT....
  END;
COMMIT;

ОБНОВИТЬ:

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

MERGE dbo.Product WITH(HOLDLOCK) AS p
USING (VALUES(@ProductName, @ProductNumber))n(Name,ProductNumber)
ON p.ProductNumber = n.ProductNumber
WHEN NOT MATCHED THEN
INSERT(Name, ProductNumber)
VALUES(n.Name, n.ProductNumber)

Подсказка HOLDLOCK имеет (локально) тот же эффект, что и установка уровня изоляции транзакции SERIALIZABLE. Поскольку MERGE является оператором изменения данных, он автоматически выполняется внутри транзакции.

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

person Sebastian Meine    schedule 06.06.2013