Время ожидания блокировки SQL Server превысило время удаления записей в цикле
я тестирую процесс, который удаляет много-много записей. Не может!--3-->, потому что там есть записи, которые должны остаться.
из-за громкости я разбил delete на цикл, подобный этому:
-- Do not block if records are locked.
SET LOCK_TIMEOUT 0
-- This process should be chosen as a deadlock victim in the case of a deadlock.
SET DEADLOCK_PRIORITY LOW
SET NOCOUNT ON
DECLARE @Count
SET @Count = 1
WHILE @Count > 0
BEGIN TRY
BEGIN TRANSACTION -- added per comment below
DELETE TOP (1000) FROM MyTable WITH (ROWLOCK, READPAST) WHERE MyField = SomeValue
SET @Count == @@ROWCOUNT
COMMIT
END TRY
BEGIN CATCH
exec sp_lock -- added to display the open locks after the timeout
exec sp_who2 -- shows the active processes
IF @@TRANCOUNT > 0
ROLLBACK
RETURN -- ignoring this error for brevity
END CATCH
MyTable-это кластеризованная таблица. MyField находится в первом столбце кластеризованного индекса. Это указывает на логическую группировку записей, поэтому MyField = SomeValue
часто выбирает много записей. Мне все равно, в каком порядке они удаляются, поэтому пока одна группа обрабатывается одновременно. Других индексов в этой таблице нет.
добавил ROWLOCK
намек, чтобы попытаться избежать блокировки эскалации мы видели в производстве. Я добавил READPAST
подсказка, чтобы избежать удаления записей, заблокированных другими процессами. Этого никогда не должно случиться, но я стараюсь быть в безопасности.
проблема: иногда этот цикл попадает в тайм-аут блокировки 1222 "время ожидания запроса блокировки превышено", когда это единственное бегущий.
я уверен, что нет никакой другой активности в этой системе, пока я тестирую этот процесс, потому что это мой собственный ящик разработчика, никто больше не подключен, на нем нет других процессов, и профилировщик не показывает никакой активности.
я могу повторно запустить тот же сценарий секундой позже, и он поднимает, где он остановился, счастливо удаляя записи-до следующего таймаута блокировки.
я пробовал BEGIN TRY
/ BEGIN CATCH
игнорировать 1222 ошибка и повторите попытку удаления, но она немедленно завершается с той же ошибкой тайм-аута блокировки. Он также терпит неудачу, если я добавляю небольшую задержку перед повторной попыткой.
я предполагаю, что тайм-ауты блокировки из-за чего-то вроде разделения страницы, но я не уверен, почему это будет противоречить текущей итерации цикла. Предыдущая инструкция delete должна была быть уже завершена, и я подумал, что это означает, что любые разбиения страниц также завершены.
почему цикл удаления поражает таймаут блокировки против себя?
есть ли способ, процесс может избежать этого тайм-аута блокировки или обнаружить, что это безопасно возобновить?
это на SQL Server 2005.
-- EDIT --
я добавил событие Lock:Timeout в профилировщик. Это тайм-аут на PAGELOCK во время удаления:
Event Class: Lock:Timeout
TextData: 1:15634 (one example of several)
Mode: 7 - IU
Type: 6 - PAGE
DBCC PAGE reports эти страницы находятся вне диапазона главной базы данных (ID 1).
-- EDIT 2 --
добавил BEGIN TRY
/ BEGIN CATCH
и exec sp_lock
в блоке catch. Вот что я увидел:--13-->
spid dbid ObjId IndId Type Resource Mode Status
19 2 1401108082 1 PAG 1:52841 X GRANT (tempdb.dbo.MyTable)
19 2 1401108082 0 TAB IX GRANT (tempdb.dbo.MyTable)
Me 2 1401108082 0 TAB IX GRANT (tempdb.dbo.MyTable)
Me 1 1115151018 0 TAB IS GRANT (master..spt_values) (?)
SPID 19-это диспетчер задач SQL Server. Почему один из этих менеджеров задач приобретает замки на MyTable?
2 ответов
я нашел ответ: мое циклическое удаление противоречит proc очистки призрака.
используя предложение Николаса, я добавил BEGIN TRANSACTION
и COMMIT
. Я завернул цикл удаления в BEGIN TRY
/ BEGIN CATCH
. В BEGIN CATCH
, перед ROLLBACK
, Я побежала sp_lock
и sp_who2
. (Я добавил изменения кода в вопрос выше.)
когда мой процесс заблокирован, я увидел следующий результат:
spid dbid ObjId IndId Type Resource Mode Status
------ ------ ----------- ------ ---- -------------------------------- -------- ------
20 2 1401108082 0 TAB IX GRANT
20 2 1401108082 1 PAG 1:102368 X GRANT
SPID Status Login HostName BlkBy DBName Command CPUTime DiskIO
---- ---------- ----- -------- ----- ------ ------------- ------- ------
20 BACKGROUND sa . . tempdb GHOST CLEANUP 31 0
для дальнейшего использования, когда SQL Server удаляет записи, он устанавливает немного на них, чтобы просто пометить их как "записи-призраки". Каждые несколько минут выполняется внутренний процесс под названием ghost cleanup для восстановления страниц записей, которые были полностью удалены (т. е. все записи являются записями-призраками).
процесс очистки призрака обсуждался на ServerFault в этом вопросе.
вот объяснение пола С. Рэндала процесса очистки призраков.
можно отключите процесс очистки призрака с помощью флага трассировки. но в этом случае мне не нужно было этого делать.
я закончил тем, что добавил тайм-аут ожидания блокировки 100 мс. Это вызывает случайные таймауты ожидания блокировки в процессе очистки записи-призрака, но это приемлемо. Я также добавил цикл our, который пытается заблокировать тайм-ауты до 5 раз. С этими двумя изменениями мой процесс обычно завершается. Теперь он получает только тайм-аут, если есть очень длинный процесс, толкающий много данных вокруг этого получает блокировки таблицы или страницы данных, которые мой процесс должен очистить.
редактировать 2016-07-20
окончательный код выглядит так:
-- Do not block long if records are locked.
SET LOCK_TIMEOUT 100
-- This process volunteers to be a deadlock victim in the case of a deadlock.
SET DEADLOCK_PRIORITY LOW
DECLARE @Error BIT
SET @Error = 0
DECLARE @ErrMsg VARCHAR(1000)
DECLARE @DeletedCount INT
SELECT @DeletedCount = 0
DECLARE @LockTimeoutCount INT
SET @LockTimeoutCount = 0
DECLARE @ContinueDeleting BIT,
@LastDeleteSuccessful BIT
SET @ContinueDeleting = 1
SET @LastDeleteSuccessful = 1
WHILE @ContinueDeleting = 1
BEGIN
DECLARE @RowCount INT
SET @RowCount = 0
BEGIN TRY
BEGIN TRANSACTION
-- The READPAST below attempts to skip over locked records.
-- However, it might still cause a lock wait error (1222) if a page or index is locked, because the delete has to modify indexes.
-- The threshold for row lock escalation to table locks is around 5,000 records,
-- so keep the deleted number smaller than this limit in case we are deleting a large chunk of data.
-- Table name, field, and value are all set dynamically in the actual script.
SET @SQL = N'DELETE TOP (1000) MyTable WITH(ROWLOCK, READPAST) WHERE MyField = SomeValue'
EXEC sp_executesql @SQL, N'@ProcGuid uniqueidentifier', @ProcGUID
SET @RowCount = @@ROWCOUNT
COMMIT
SET @LastDeleteSuccessful = 1
SET @DeletedCount = @DeletedCount + @RowCount
IF @RowCount = 0
BEGIN
SET @ContinueDeleting = 0
END
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0
ROLLBACK
IF Error_Number() = 1222 -- Lock timeout
BEGIN
IF @LastDeleteSuccessful = 1
BEGIN
-- If we hit a lock timeout, and we had already deleted something successfully, try again.
SET @LastDeleteSuccessful = 0
END
ELSE
BEGIN
-- The last delete failed, too. Give up for now. The job will run again shortly.
SET @ContinueDeleting = 0
END
END
ELSE -- On anything other than a lock timeout, report an error.
BEGIN
SET @ErrMsg = 'An error occurred cleaning up data. Table: MyTable Column: MyColumn Value: SomeValue. Message: ' + ERROR_MESSAGE() + ' Error Number: ' + CONVERT(VARCHAR(20), ERROR_NUMBER()) + ' Line: ' + CONVERT(VARCHAR(20), ERROR_LINE())
PRINT @ErrMsg -- this error message will be included in the SQL Server job history
SET @Error = 1
SET @ContinueDeleting = 0
END
END CATCH
END
IF @Error <> 0
RAISERROR('Not all data could be cleaned up. See previous messages.', 16, 1)
Вы или кто-то другой, используя соединение настройки таймаута блокировки на что-то другое, чем по умолчанию. См.http://msdn.microsoft.com/en-US/library/ms189470 (v=SQL.90).aspx для деталей.
время блокировки по умолчанию -1 миллисекунда, что означает "ждать вечно".
подсказки строк хороши, но они являются запахом кода и их следует избегать. Пусть SQL Server выполняет свою работу. У него больше информации о системе, чем у вас. целый.
для начала вы не можете контролировать размер блокировки: эскалация блокировки происходит автоматически, в зависимости от количества незаполненных блокировок. Он начинается с блокировки строк. Если вы накапливаете слишком много блокировок строк, SQL Server переходит к блокировке страницы. Приобретите слишком много блокировок страниц, и он перерастет в блокировки таблиц. Вижу http://msdn.microsoft.com/en-us/library/ms184286(в=в SQL.90).аспн на укрупнение детали. Однако есть несколько флагов трассировки, которые вы можете установить, что предотвратит эскалация блокировки: однако это приведет к снижению производительности SQL Server.
другое дело: вы должны обернуть DELETE
оператор в транзакции, особенно в хранимой процедуре.
DECLARE @Count INT
SET @Count = 1
WHILE @Count > 0
BEGIN
BEGIN TRANSACTION
DELETE TOP (1000) FROM MyTable WITH (ROWLOCK, READPAST) WHERE MyField = SomeValue
SET @Count = @@ROWCOUNT
COMMIT TRANSACTION
END
это делает ясным ваше намерение и гарантирует, что замки освобождаются, когда они должны быть.