1 sql-server parallelism lock-escalation
我使用定制的 Stack Overflow 数据库(180GB)并运行一个简单的更新查询:(Users 表上只有一个聚集索引)
Begin Tran
Update U set U.Reputation=100000
from StackOverflow.dbo.Users as U
where U.CreationDate = '2008-10-10 14:26:33.540'
Run Code Online (Sandbox Code Playgroud)
查询计划:
此查询会导致锁升级。我无法在另一个窗口中使用同一个表运行查询:
select * from StackOverflow.dbo.Users as U where U.id=11
Run Code Online (Sandbox Code Playgroud)
如果我option (maxdop 1)
在查询末尾添加以避免并行,则一切都很好(计划)。
在较小的 Stack Overflow DB (StackOverflow2013 - 52GB) 中不会发生锁升级(计划)。
如何确定导致升级的数据量?
我使用 SQL Server 2019。数据库兼容级别为 150。
表信息:
其中涉及到机会的因素。
\n我可以使用位于 的 Stack Overflow 2013 示例数据库可靠地重现您场景中的锁升级MAXDOP 8
。MAXDOP 12
在(我可以在此实例上使用的最多)或在 处没有锁升级MAXDOP 1
。
MAXDOP 1
在测试之前,我使用100重建了 Users 表FILLFACTOR
,以确保页面尽可能满:
ALTER TABLE dbo.Users \nREBUILD \nWITH \n(\n FILLFACTOR = 100, \n MAXDOP = 1, \n ALLOW_ROW_LOCKS = ON,\n ALLOW_PAGE_LOCKS = ON,\n DATA_COMPRESSION = NONE,\n ONLINE = OFF\n);\n
Run Code Online (Sandbox Code Playgroud)\n如果没有锁定粒度提示,SQL Server 存储引擎会选择使用页级锁定来处理聚集索引扫描。对于大表的扫描来说这是很正常的。获取和释放行锁会增加太多开销,并且表锁不利于并发性。
\n串行计划中不会发生锁升级,因为一旦针对谓词测试了该页上的所有行并发现不匹配,SQL Server 就可以释放该页上的锁。这是可能的,因为执行计划不包含任何阻塞运算符。
\n并行计划有所不同,因为Gather Streams运算符部分阻塞。在Update操作符处理来自该数据包的行之前,可以从扫描中生成多于一行并保存在交换缓冲区中。
\n为了保证并行计划的正确性,SQL Server 必须能够在必要时一次在多个页面上持有锁。这就需要使用锁类,如果需要的话,它允许在语句结束时保持多个锁。
\n该引擎确实包含在安全时尽早释放锁类中持有的锁的优化,但细节很复杂并且没有记录。在并行执行计划中,锁保留到语句末尾。
\n更准确地说,页面级更新锁是在扫描操作员处获取的。这些分布在并行线程中,每个线程根据当前持有的锁数量独立尝试升级。
\n在我的测试中MAXDOP 8
,一两个线程最终将其页级更新锁升级为独占表锁(表级更新锁不存在)。在 时MAXDOP 12
,工作通常分布得足够均匀,以至于没有线程获取足够的锁来尝试锁升级。所有四个线程都MAXDOP 4
升级了它们的锁。
注意:一个或多个线程可能会升级到表锁,而同一计划中的其他线程会继续以不同的粒度持有同一对象上的锁,因为这些线程不会触发升级。
\n我没有 180GB Stack Overflow 数据库,也不愿意下载它只是为了测试,但随着页面数量的增加,线程获取足够页面锁以尝试升级的机会明显增加。在 DOP 足够高且分布均匀的情况下,仍然可以避免升级。这就是我之前提到的机会因素。
\n我用来监视锁升级“尝试”和成功的脚本如下所示:
\nSELECT \n IOS.row_lock_count, \n IOS.page_lock_count, \n IOS.index_lock_promotion_attempt_count, \n IOS.index_lock_promotion_count\nFROM sys.dm_db_index_operational_stats\n(\n DB_ID(), OBJECT_ID(N\'dbo.Users\', \'U\'), NULL, NULL\n) AS IOS;\n\nBEGIN TRANSACTION;\n\n UPDATE U \n SET U.Reputation = 100000 \n FROM dbo.Users AS U\n WHERE \n U.CreationDate = \'2008-10-10 14:26:33.540\'\n OPTION (MAXDOP 8, RECOMPILE);\n\n SELECT \n IOS.row_lock_count, \n IOS.page_lock_count, \n IOS.index_lock_promotion_attempt_count, \n IOS.index_lock_promotion_count\n FROM sys.dm_db_index_operational_stats\n (\n DB_ID(), OBJECT_ID(N\'dbo.Users\', \'U\'), NULL, NULL\n ) AS IOS;\n\nROLLBACK TRANSACTION; \n
Run Code Online (Sandbox Code Playgroud)\n并行工作线程之一升级的运行的示例输出:
\n\n我还使用lock_escalation
扩展事件来确认升级:
有关锁升级内部原理的更多详细信息,请参阅我的系列文章:
\nMicrosoft还提出了解决 SQL Server 中锁升级引起的阻塞问题,但在许多方面并不精确,而且总体上也不完整。
\n 归档时间: |
|
查看次数: |
421 次 |
最近记录: |