Безопасный способ потока, чтобы проверить, существует ли строка перед вставкой. Правильно ли мой код?

У меня есть таблица "INSERTIF", которая выглядит так:

id  value
S1  s1rocks
S2  s2rocks
S3  s3rocks

Прежде чем вставлять строку в эту таблицу, мне нужно проверить, существует ли данный идентификатор. Если он не существует, тогда вставьте. Else, просто обновите значение. Я хочу сделать это безопасным потоком. Можете ли вы сказать, правильно ли мой код или нет? Я попробовал, и это сработало. Но я хочу быть уверенным, что у меня нет недостатка в производительности.

EDIT 1 - Я хочу использовать этот код для вставки миллионов строк по одному. Каждый оператор insert обернут вокруг кода, который я показал.

EDIT 2 - Я не хочу использовать часть UPDATE для моего кода, достаточно только вставки.

Я НЕ хочу использовать MERGE, потому что он работает только с SQL Server 2008 и выше

Спасибо.

Код -

-- no check insert 
INSERT INTO INSERTIF(ID,VALUE)
VALUES('S1', 's1doesNOTrock')

--insert with checking 

begin tran /* default read committed isolation level is fine */
if not exists 
(select * from INSERTIF with (updlock, rowlock, holdlock) 
where ID = 'S1')
BEGIN
INSERT INTO INSERTIF(ID,VALUE)
VALUES('S1', 's1doesNOTrock')
END
else
/* update */
UPDATE INSERTIF
SET VALUE = 's1doesNOTrock'
WHERE ID = 'S1'
commit /* locks are released here */

Код для создания таблицы -

CREATE TABLE [dbo].[INSERTIF](
    [id] [varchar](50) NULL,
    [value] [varchar](50) NULL
)
INSERT [dbo].[INSERTIF] ([id], [value]) VALUES (N'S1', N's1rocks')
INSERT [dbo].[INSERTIF] ([id], [value]) VALUES (N'S2', N's2rocks')
INSERT [dbo].[INSERTIF] ([id], [value]) VALUES (N'S3', N's3rocks')

Ответ 1

Ваш вопрос касается безопасности потока вашего кода. В сжатом виде, нет - это не поточно-безопасный. (Но см. Ниже, где обсуждается изоляция.)

У вас есть (небольшое) окно уязвимости из-за проблемы TOCTOU (время проверки, времени использования) между вашим "не существует" SELECT и соответствующим действием. Предполагая, что у вас есть уникальное (основное) ограничение ключа в столбце id, вы должны использовать парадигму "Легче задавать прощение, чем разрешение", а не парадигму "Взгляд перед прыжком" (см. EAFP vs LBYL).

Это означает, что вы должны определить, какую из двух последовательностей действий вы собираетесь использовать:

  • INSERT, но UPDATE, если он не работает.
  • UPDATE, но INSERT, если строки не обновлены.

Либо работает. Если работа будет в основном вставляться и время от времени обновляться, то 1 лучше, чем 2; если работа будет в основном обновляться с помощью случайной вставки, то 2 лучше, чем 1. Вы можете даже работать адаптивно; следите за тем, что произошло в последних N строках (где N может быть всего 5 или целых 500), и используйте эвристику, чтобы решить, что попробовать в новой строке. Там может быть проблема, если INSERT терпит неудачу (поскольку существовала строка), но UPDATE ничего не обновляет (поскольку кто-то удалил строку после неудачной вставки). Точно так же может быть проблема с UPDATE и INSERT (строка не существует, но одна была вставлена).

Обратите внимание, что опция INSERT полностью зависит от уникального ограничения, чтобы не вставлять повторяющиеся строки; опция UPDATE более надежна.

Вам также необходимо рассмотреть свой уровень изоляции, который может изменить исходный ответ. Если ваша изоляция достаточно высока, чтобы гарантировать, что после выполнения "не существует" SELECT никто другой не сможет вставить строку, которую вы определили, не существует, тогда вы можете быть в порядке. Это вникает в какое-то серьезное понимание вашей СУБД (и я не эксперт SQL Server).

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

Ответ 2

Этот метод обычно называется UPSERT. Это можно сделать в SQL Server с помощью MERGE. Он работает следующим образом:

MERGE INTO A_Table
USING 
    (SELECT 'data_searched' AS Search_Col) AS SRC
    -- Search predicates
    --
    ON A_Table.Data = SRC.Search_Col
WHEN MATCHED THEN
    -- Update part of the 'UPSERT'
    --
    UPDATE SET
        Data = 'data_searched_updated'
WHEN NOT MATCHED THEN
    -- INSERT part of the 'UPSERT'
    --
    INSERT (Data)
    VALUES (SRC.Search_Col);    

Также см. http://www.sergeyv.com/blog/archive/2010/09/10/sql-server-upsert-equivalent.aspx

EDIT: Я вижу, что вы используете старый SQL Server. В этом случае вы должны использовать несколько операторов.