Я борюсь против NOLOCK в моей нынешней обстановке. Один аргумент, который я услышал, заключается в том, что накладные расходы на блокировку замедляют запрос. Итак, я разработал тест, чтобы увидеть, насколько эти затраты могут быть.
Я обнаружил, что NOLOCK на самом деле замедляет сканирование.
Сначала я был в восторге, но теперь я просто растерялся. Мой тест недействителен как-то? Разве NOLOCK не должен позволять немного более быстрое сканирование? Что тут происходит?
Вот мой сценарий:
USE TestDB
GO
--Create a five-million row table
DROP TABLE IF EXISTS dbo.JustAnotherTable
GO
CREATE TABLE dbo.JustAnotherTable (
ID INT IDENTITY PRIMARY KEY,
notID CHAR(5) NOT NULL )
INSERT dbo.JustAnotherTable
SELECT TOP 5000000 'datas'
FROM sys.all_objects a1
CROSS JOIN sys.all_objects a2
CROSS JOIN sys.all_objects a3
/********************************************/
-----Testing. Run each multiple times--------
/********************************************/
--How fast is a plain select? (I get about 587ms)
DECLARE @trash CHAR(5), @dt DATETIME = SYSDATETIME()
SELECT @trash = notID --trash variable prevents any slowdown from returning data to SSMS
FROM dbo.JustAnotherTable
ORDER BY ID
OPTION (MAXDOP 1)
SELECT DATEDIFF(MILLISECOND,@dt,SYSDATETIME())
----------------------------------------------
--Now how fast is it with NOLOCK? About 640ms for me
DECLARE @trash CHAR(5), @dt DATETIME = SYSDATETIME()
SELECT @trash = notID
FROM dbo.JustAnotherTable (NOLOCK)
ORDER BY ID --would be an allocation order scan without this, breaking the comparison
OPTION (MAXDOP 1)
SELECT DATEDIFF(MILLISECOND,@dt,SYSDATETIME())
То, что я пробовал, не сработало:
- Работа на разных серверах (одинаковые результаты, серверы были 2016-SP1 и 2016-SP2, оба тихие)
- Запуск на dbfiddle.uk на разных версиях (шумный, но, вероятно, тот же результат)
- УСТАНОВИТЬ УРОВЕНЬ ИЗОЛЯЦИИ вместо подсказок (те же результаты)
- Отключение повышения блокировки на столе (те же результаты)
- Изучение фактического времени выполнения сканирования в фактическом плане запроса (те же результаты)
- Перекомпилировать подсказку (те же результаты)
- Файловая группа только для чтения (те же результаты)
Наиболее многообещающее исследование происходит от удаления переменной корзины и использования запроса без результатов. Первоначально это показывало NOLOCK немного быстрее, но когда я показал демонстрацию своему боссу, NOLOCK снова стал медленнее.
Что такого в NOLOCK, который замедляет сканирование с присвоением переменных?
источник
Ответы:
ПРИМЕЧАНИЕ: это может быть не тот тип ответа, который вы ищете. Но, возможно, это будет полезно для других потенциальных ответчиков, поскольку они дают подсказки о том, где начать искать
Когда я выполняю эти запросы в режиме трассировки ETW (используя PerfView), я получаю следующие результаты:
Таким образом, разница составляет 51 мс . Это довольно мертвое с вашей разницей (~ 50 мс). Мои цифры немного выше в целом из-за накладных расходов выборки профилировщика.
Нахождение разницы
Вот параллельное сравнение, показывающее, что разница в 51 мс заключается в
FetchNextRow
методе в sqlmin.dll:Обычный выбор находится слева на 332 мс, а версия nolock справа на 383 (на 51 мс больше). Вы также можете видеть, что эти два пути кода различаются следующим образом:
гладкий
SELECT
sqlmin!RowsetNewSS::FetchNextRow
звонкиsqlmin!IndexDataSetSession::GetNextRowValuesInternal
С помощью
NOLOCK
sqlmin!RowsetNewSS::FetchNextRow
звонкиsqlmin!DatasetSession::GetNextRowValuesNoLock
который вызывает либоsqlmin!IndexDataSetSession::GetNextRowValuesInternal
или жеkernel32!TlsGetValue
Это показывает, что в
FetchNextRow
методе есть некоторые ветвления, основанные на подсказке уровня изоляции / nolock.Почему
NOLOCK
ветка занимает больше времени?Ветвь nolock фактически тратит меньше времени на звонки
GetNextRowValuesInternal
(на 25 мс меньше). Но непосредственно кодGetNextRowValuesNoLock
(не включая методы, которые он называет AKA столбцом «Exc») работает в течение 63 мс, что составляет большую часть разницы (63 - 25 = 38 мс чистого увеличения времени ЦП).Так в чем же другие 13 мс (всего 51 мс - 38 мс приходилось до сих пор) в накладных расходах
FetchNextRow
?Диспетчеризация интерфейса
Я думал, что это было больше любопытно, чем что-либо еще, но версия nolock, по-видимому, влечет за собой некоторые накладные расходы на диспетчеризацию интерфейса, вызывая метод Windows API
kernel32!TlsGetValue
черезkernel32!TlsGetValueStub
- всего 17 мс. Простой выбор, кажется, не проходит через интерфейс, поэтому он никогда не попадает в заглушку и тратит только 6 мсTlsGetValue
(разница в 11 мс ). Вы можете увидеть это выше на первом скриншоте.Я, вероятно, должен выполнить эту трассировку снова с большим количеством итераций запроса, я думаю, что есть некоторые мелочи, такие как аппаратные прерывания, которые не были обнаружены частотой дискретизации 1 мс PerfView
Помимо этого метода, я заметил еще одно небольшое отличие, которое заставляет версию nolock работать медленнее:
Выпуск замков
Ветвь nolock, кажется, более агрессивно запускает
sqlmin!RowsetNewSS::ReleaseRows
метод, который вы можете увидеть на этом скриншоте:Простой выбор находится на вершине, на 12ms, в то время как версия NOLOCK находится на дне в 26ms ( 14ms дольше). В столбце «Когда» также видно, что код выполнялся чаще во время выборки. Это может быть деталью реализации nolock, но, похоже, она вносит немало накладных расходов для небольших выборок.
Есть много других небольших различий, но это большие куски.
источник