For*_*est 14 sql-server sql-server-2016
在我目前的环境中,我正在与 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())
Run Code Online (Sandbox Code Playgroud)
我尝试过的方法不起作用:
最有希望的探索来自删除垃圾变量并使用无结果查询。最初这显示 NOLOCK 稍微快一点,但是当我向老板展示演示时,NOLOCK 又变慢了。
使用变量赋值减慢扫描速度的 NOLOCK 是什么?
Jos*_*ell 14
注意:这可能不是您正在寻找的答案类型。但也许它会帮助其他潜在的回答者提供线索从哪里开始寻找
当我在 ETW 跟踪(使用 PerfView)下运行这些查询时,我得到以下结果:
Plain - 608 ms
NOLOCK - 659 ms
Run Code Online (Sandbox Code Playgroud)
所以差异是51ms。这与您的差异(约 50 毫秒)完全相同。由于分析器采样开销,我的数字总体上略高。
这是一个并排比较,显示 51ms 的差异在于 sqlmin.dll 中的FetchNextRow
方法:
普通选择位于左侧 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
其中的代码(不包括它称为“Exc”列的方法)运行了 63 毫秒 - 这是差异的大部分(63 - 25 = 38 毫秒的 CPU 时间净增加)。
那么其他 13 毫秒(总共 51 毫秒 - 到目前为止占 38 毫秒)的开销是FetchNextRow
多少?
我认为这是超过任何一个好奇心,但NOLOCK版本出现通过调用Windows API的方法招致一些接口调度开销kernel32!TlsGetValue
通过kernel32!TlsGetValueStub
-共17MS的。普通的 select 似乎没有通过接口,所以它永远不会命中存根,并且只花费 6 毫秒TlsGetValue
(相差 11毫秒)。您可以在上面的第一个屏幕截图中看到这一点。
我可能应该通过查询的更多迭代再次运行此跟踪,我认为有一些小事情,例如硬件中断,没有被 PerfView 的 1ms 采样率捕获
在该方法之外,我注意到另一个导致 nolock 版本运行速度较慢的小差异:
nolock 分支似乎更积极地运行该 sqlmin!RowsetNewSS::ReleaseRows
方法,您可以在此屏幕截图中看到:
普通选择在顶部,为 12 毫秒,而 nolock 版本在底部为 26毫秒(长14 毫秒)。您还可以在“何时”列中看到代码在示例期间执行的频率更高。这可能是 nolock 的一个实现细节,但是对于小样本来说,它似乎引入了相当多的开销。
还有很多其他小的差异,但这些都是大块。