Вам не нужно IF
, чтобы проверить, существует ли уже запись. Предложение WHERE
в операторе UPDATE делает это. Все, что вам нужно, это убедиться, что запись не существует до вставки новой записи, например:
UPDATE MyTable
SET
Foo = @foo,
Bar = 1
WHERE BlahID = @id;
INSERT MyTable (Bar,Foo)
values (1,@foo)
where not exists (select BlahID
from MyTable
where BlahID=@id)
Используйте именованные параметры, если это возможно, поэтому вам нужно только передать 2 параметра вместо 4 и рискнуть перепутать порядок.
Вы можете заключить оба оператора в транзакцию, но Убедитесь, что BlahID проиндексирован . Это позволит серверу заблокировать только одну строку для обновления. Без индекса серверу пришлось бы сканировать и блокировать намного больше данных для обеспечения согласованности.
Это также позволяет избежать дублирования записей. Независимо от того, сколько блокировок вы берете, если вы используете предложение IF
, две одновременные попытки с одним и тем же несуществующим идентификатором приведут к двум вставкам, потому что оба запроса найдут пропущенную строку, оба попытаются безоговорочно вставить.
Другой вариант - использовать MERGE, хотя в этом случае он не будет работать хорошо. Из документации MERGE
При простом обновлении одной таблицы на основе строк другой таблицы можно повысить производительность и масштабируемость с помощью базовых операторов INSERT, UPDATE и DELETE. Например:
INSERT tbl_A (col, col2)
SELECT col, col2
FROM tbl_B
WHERE NOT EXISTS (SELECT col FROM tbl_A A2 WHERE A2.col = tbl_B.col);
Текущий случай еще проще, задействована только одна таблица:
INSERT MyTable (Bar,Foo)
VALUES (1,@foo)
WHERE NOT EXISTS (SELECT BlahID FROM MyTable WHERE BlahID=@id);
Почему тупик?
Сервер должен блокировать строки, чтобы обеспечить возможность повторения транзакции. При выборе сервер принимает блокировки SHARED (S) для извлеченных или отсканированных строк. Вот почему индекс приводит к меньшему количеству блокировок - сервер может немедленно найти нужную ему строку. Эти общие блокировки останутся на время транзакции. Если нет явной транзакции, в зависимости от режима изоляции общие блокировки могут быть сохранены на время соединения. Вот что происходит с REPEATABLE READ.
Когда вы пытаетесь обновить строку, сервер попытается выполнить блокировку UPDATE. Если строка имеет SHARED-блокировку сервера, операция обновления будет заблокирована. Если транзакция уже содержит блокировку SHARED в строке, она попытается обновить ее до блокировки UPGRADE. Если у кого-то еще есть S-блокировка в ряду, сделка будет заблокирована. Чтобы чтение было повторяемым , сервер должен заблокировать строки, к которым он прикоснулся.
Хуже, если сервер не может найти одну строку из-за отсутствия индексов.
NOLOCK не означает, что блокировки не принимаются, это означает, что блокировки других не соблюдаются. Операция все еще будет блокироваться, но приведет к грязным результатам, призракам или отсутствующим обновлениям.
Вот как в этом случае происходит разлочка:
- Два соединения выполняют
IF(SELECT)
и получают блокировки SHARED в ряду, S1 и S2.
- Соединение 1 пытается обновить блокировку до UPGRADE, но находит на ней блокировку S2 и блокирует ее ожидание.
- Соединение 2 пытается перейти на U, но находит S1 и блоки. Соединение не может быть установлено, что приводит к тупику.
Подробнее о блокировке, типах блокировок, совместимости и области действия можно узнать в разделе Блокировка в компоненте Database Engine в Руководстве по блокировке транзакций SQL Server и управлению версиями строк
Изоляция моментального снимка
Вы можете использовать уровень изоляции снимков , чтобы избежать блокирования друг друга читателями и писателями, подобно тому, как это делают Oracle и PostgreSQL. Это не поможет в этом случае, потому что один писатель блокирует другого.