У меня есть таблица ImportSourceMetadata
, которую я использую для управления процессом импорта. Он содержит столбец PK SourceId
и столбец данных LastCheckpoint
. Процесс пакетного импорта читает LastCheckpoint
для данного SourceId
, выполняет некоторую логику (на других таблицах), затем обновляет LastCheckpoint
для этого SourceId
или вставляет его, если он еще не существует.Почему код SQLERT SQL Server иногда не блокируется?
Несколько экземпляров процесса запускаются в одно и то же время, обычно с отключением SourceIds
, и для этих случаев мне нужна высокая параллельность. Однако может случиться, что для одного и того же SourceId
запускаются два процесса; в этом случае мне нужны экземпляры, чтобы блокировать друг друга.
Поэтому мой код выглядит следующим образом:
BEGIN TRAN
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
SELECT LastCheckpoint FROM ImportSourceMetadata WITH (UPDLOCK) WHERE SourceId = 'Source'
-- Perform some processing
-- UPSERT: if the SELECT above yielded no value, then
INSERT INTO ImportSourceMetadata(SourceId, LastCheckpoint) VALUES ('Source', '2013-12-21')
-- otherwise, we'd do this: UPDATE ImportSourceMetadata SET LastCheckpoint = '2013-12-21' WHERE SourceId = 'Source'
COMMIT TRAN
Я использую транзакцию для достижения атомарности, но я могу использовать только READ COMMITTED уровня изоляции (из-за требования к параллельности в «Выполнить некоторую обработку «блок»). Поэтому (и чтобы избежать взаимоблокировок), я включаю подсказку UPDLOCK с инструкцией SELECT для достижения «критического раздела», параметризованного по значению SourceId
.
Теперь это работает довольно хорошо в большинстве случаев, но мне удалось вызвать ошибки нарушения первичного ключа с помощью инструкции INSERT при запуске множества параллельных процессов для того же SourceId
с пустой базой данных. Однако я не могу достоверно воспроизвести это, и я не понимаю , почему он не работает.
Я нашел намеки в Интернете (например, here и here, in a comment), что мне нужно указать WITH (UPDLOCK,HOLDLOCK)
(соотв. WITH (UPDLOCK,SERIALIZABLE)
), а не просто принимать UPDLOCK на SELECT, но я действительно не понимаю, почему это , MSDN документы say,
UPDLOCK
Указывает, что блокировки обновления должны быть приняты и проведены, пока транзакция не будет завершена.
замок обновление, которое принимается и удерживается до завершения транзакции должна быть достаточно, чтобы блокировать последующее INSERT, а на самом деле, когда я пытаюсь его в SQL Server Management Studio, она действительно блокирует мою вставку. Однако в некоторых редких случаях он, похоже, внезапно не работает.
Итак, почему именно UPDLOCK недостаточно, и почему этого достаточно в 99% моих тестовых прогонов (и при имитации его в SQL Server Management Studio)?
Update: Я теперь нашел, что я могу воспроизвести Неблокирующая поведение надежно, выполнив код, указанный выше в двух разных окнах среды SQL Server Management Studio, одновременно до как раз перед INSERT, но только первый после создания базы данных. После этого (хотя я удалил содержимое таблицы ImportSourceMetadata
), SELECT WITH (UPDLOCK)
действительно блокирует и код больше не будет работать. Действительно, в sys.dm_tran_locks
я вижу U-блокировку, даже если строка не существует при последующих тестовых прогонах, но не в первом запуске после создания таблицы.
Это полный пример, чтобы показать разницу в замках между «вновь созданной таблицы» и «старый стол»:
DROP TABLE ImportSourceMetadata
CREATE TABLE ImportSourceMetadata(SourceId nvarchar(50) PRIMARY KEY, LastCheckpoint datetime)
BEGIN TRAN
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
SELECT LastCheckpoint FROM ImportSourceMetadata WITH (UPDLOCK) WHERE SourceId='Source'
SELECT *
FROM sys.dm_tran_locks l
JOIN sys.partitions p
ON l.resource_associated_entity_id = p.hobt_id JOIN sys.objects o
ON p.object_id = o.object_id
INSERT INTO ImportSourceMetadata VALUES('Source', '2013-12-21')
ROLLBACK TRAN
BEGIN TRAN
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
SELECT LastCheckpoint FROM ImportSourceMetadata WITH (UPDLOCK) WHERE SourceId='Source'
SELECT *
FROM sys.dm_tran_locks l
JOIN sys.partitions p
ON l.resource_associated_entity_id = p.hobt_id JOIN sys.objects o
ON p.object_id = o.object_id
ROLLBACK TRAN
В моей системе (с SQL Server 2012), первый запрос показывает нет замков на ImportSourceMetadata
, но второй запрос показывает замок KEY
на ImportSourceMetadata
.
Другими словами, требуется HOLDLOCK
, но только если таблица была только что создана. Почему это?
Это код реального T-SQL, который выполняется. Я немного изменил его, чтобы вы могли выполнить его непосредственно в SQL Server Management Studio. –
И теперь я добавил полный образец. –