Почему это не работает?
Я считаю, что стандартным поведением SQL Server является снятие общих блокировок, как только они больше не нужны.Ваш подзапрос приведет к кратковременной общей (S) блокировке таблицы, которая будет снята, как только подзапрос завершится.
На данный момент нет ничего, что могло бы предотвратить параллельную транзакциюот вставки той самой строки, которую вы только что подтвердили, отсутствующей.
Какую модификацию мне нужно сделать, чтобы исключить вероятность исключения из-за нарушения ограничения?
Добавление подсказки HOLDLOCK
к вашему подзапросу заставит SQL Server удерживать блокировку до завершения транзакции.(В вашем случае это неявная транзакция.) Подсказка HOLDLOCK
эквивалентна подсказке SERIALIZABLE
, которая сама по себе эквивалентна сериализуемому уровню изоляции транзакции, который вы упоминаете в своем списке «других подходов».
Одной подсказки HOLDLOCK
было бы достаточно для сохранения блокировки S и предотвращения одновременной транзакции вставки строки, от которой вы защищаете.Однако вы, скорее всего, обнаружите, что ошибка нарушения уникального ключа заменена взаимоблокировками, возникающими на той же частоте.
Если вы сохраняете только S-блокировку на столе, рассмотрите гонку между двумя одновременными попытками вставитьв той же строке, продолжая в режиме lockstep - оба преуспевают в получении блокировки S на таблице, но ни один не может преуспеть в получении блокировки Exclusive (X), необходимой для выполнения вставки.
К счастью, существует другой тип блокировки дляэтот точный сценарий называется блокировкой обновления (U).U-блокировка идентична S-блокировке со следующим отличием: хотя несколько S-блокировок могут удерживаться одновременно на одном и том же ресурсе, одновременно может удерживаться только одна U-блокировка.(Говорят иначе, в то время как S-блокировки совместимы друг с другом (то есть могут сосуществовать без конфликта), U-блокировки не совместимы друг с другом, но могут сосуществовать вместе с S-блокировками; и далее по всему спектру эксклюзивные (X) блокировки не являютсясовместим с блокировками S или U)
Вы можете обновить неявную блокировку S в подзапросе до блокировки U, используя подсказку UPDLOCK
.
Две одновременные попытки вставить одну и ту жестрока в таблице теперь будет сериализована в начальном операторе выбора, поскольку она получает (и удерживает) блокировку U, которая несовместима с другой блокировкой U из попытки одновременной вставки.
NULL значения
Отдельная проблема может возникнуть из-за того, что FieldC допускает значения NULL.
Если ANSI_NULLS
включено (по умолчанию), тогда проверка равенства FieldC=NULL
вернет false, дажев случае, когда FieldC имеет значение NULL (вы должны использовать оператор IS NULL
для проверки на нулевое значение, когда ANSI_NULLS
включен).Поскольку FieldC имеет значение NULL, ваша дублирующая проверка не будет работать при вставке значения NULL.
Чтобы правильно обрабатывать пустые значения, вам нужно будет изменить подзапрос EXISTS, чтобы использовать оператор IS NULL
вместо =
когда значение NULL вставляется.(Или вы можете изменить таблицу, чтобы запретить значения NULL во всех соответствующих столбцах.)
Электронная документация по SQL Server Books