В одной из наших баз данных у нас есть таблица, к которой интенсивно одновременно обращается несколько потоков. Потоки обновляют или вставляют строки через MERGE. Есть также потоки, которые время от времени удаляют строки, поэтому данные таблицы очень изменчивы. Потоки, выполняющие upserts, иногда страдают от взаимоблокировки. Проблема выглядит аналогично описанной в этом вопросе. Разница, однако, в том, что в нашем случае каждый поток обновляет или вставляет ровно одну строку .
Упрощенная настройка следующая. Таблица является кучей с двумя уникальными некластеризованными индексами
CREATE TABLE [Cache]
(
[UID] uniqueidentifier NOT NULL CONSTRAINT DF_Cache_UID DEFAULT (newid()),
[ItemKey] varchar(200) NOT NULL,
[FileName] nvarchar(255) NOT NULL,
[Expires] datetime2(2) NOT NULL,
CONSTRAINT [PK_Cache] PRIMARY KEY NONCLUSTERED ([UID])
)
GO
CREATE UNIQUE INDEX IX_Cache ON [Cache] ([ItemKey]);
GO
и типичный запрос
DECLARE
@itemKey varchar(200) = 'Item_0F3C43A6A6A14255B2EA977EA730EDF2',
@fileName nvarchar(255) = 'File_0F3C43A6A6A14255B2EA977EA730EDF2.dat';
MERGE INTO [Cache] WITH (HOLDLOCK) T
USING (
VALUES (@itemKey, @fileName, dateadd(minute, 10, sysdatetime()))
) S(ItemKey, FileName, Expires)
ON T.ItemKey = S.ItemKey
WHEN MATCHED THEN
UPDATE
SET
T.FileName = S.FileName,
T.Expires = S.Expires
WHEN NOT MATCHED THEN
INSERT (ItemKey, FileName, Expires)
VALUES (S.ItemKey, S.FileName, S.Expires)
OUTPUT deleted.FileName;
то есть сопоставление происходит по уникальному ключу индекса. Намек HOLDLOCKздесь, из-за параллелизма (как рекомендовано здесь ).
Я провел небольшое расследование, и вот что я нашел.
В большинстве случаев план выполнения запроса
со следующей схемой блокировки
т.е. IXзамок на объекте, сопровождаемый более детальными замками.
Иногда, однако, план выполнения запроса отличается
(эту форму плана можно форсировать, добавив INDEX(0)подсказку), и ее схема блокировки
Xблокировка уведомления на объекте IXуже установлена
Поскольку два IXсовместимы, а два Xнет, то при параллельности
тупик !
И тут возникает первая часть вопроса . Устанавливает ли Xзамок на объект после того, как IXимеет право? Разве это не ошибка?
Документация гласит:
Преднамеренные блокировки называются намеренными блокировками, потому что они получены перед блокировкой на более низком уровне, и, следовательно, сигнализируют о намерении установить блокировки на более низком уровне .
а также
IX означает намерение обновить только некоторые строки, а не все
Итак, установка Xблокировки на объекте после IXвыглядит ОЧЕНЬ подозрительно для меня.
Сначала я попытался предотвратить взаимоблокировку, пытаясь добавить подсказки блокировки таблицы
MERGE INTO [Cache] WITH (HOLDLOCK, TABLOCK) T
а также
MERGE INTO [Cache] WITH (HOLDLOCK, TABLOCKX) T
с TABLOCKузором блокировки на месте становится
и с TABLOCKXшаблоном блокировки
поскольку два SIX(а также два X) несовместимы, это эффективно предотвращает взаимоблокировку, но, к сожалению, также предотвращает параллелизм (что нежелательно).
Моими следующими попытками было добавить PAGLOCKи ROWLOCKсделать блокировки более детальными и уменьшить конфликт. И то, и другое не оказывает влияния ( Xна объекте все еще наблюдалось сразу после IX).
Моя последняя попытка была заставить "хорошую" форму плана выполнения с хорошей гранулярной блокировкой, добавив FORCESEEKподсказку
MERGE INTO [Cache] WITH (HOLDLOCK, FORCESEEK(IX_Cache(ItemKey))) T
и это сработало.
И тут возникает вторая часть вопроса . Может ли это случиться так, что FORCESEEKбудет проигнорировано и будет использоваться неправильная схема блокировки? (Как я уже упоминал, PAGLOCKи, ROWLOCKказалось бы , были проигнорированы).
Добавление не UPDLOCKимеет никакого эффекта ( Xна объекте, все еще наблюдаемом после IX).
Создание IX_Cacheкластерного индекса, как и ожидалось, сработало. Это привело к планированию с Clustered Index Seek и гранулярной блокировкой. Кроме того, я попытался заставить Clustered Index Scan, которая также показала гранулярную блокировку.
Однако. Дополнительное наблюдение. В исходной настройке даже при FORCESEEK(IX_Cache(ItemKey)))наличии одного изменения @itemKeyобъявления переменной с varchar (200) на nvarchar (200) план выполнения становится
Так как используется поиск, НО в этом случае снова показывает Xблокировку, установленную на объекте IX.
Таким образом, кажется, что принудительный поиск не обязательно гарантирует гранулярные блокировки (и отсутствие тупиков отсюда). Я не уверен, что кластерный индекс гарантирует гранулярную блокировку. Или это?
Мое понимание (поправьте меня, если я ошибаюсь) заключается в том, что блокировка в значительной степени ситуативна, и определенная форма плана выполнения не предполагает определенной схемы блокировки.
Вопрос о возможности установки Xблокировки на объект после того, как IXвсе еще открыт. И если это приемлемо, можно ли что-то сделать, чтобы предотвратить блокировку объекта?










