我在 Azure SQL 数据库中有一个聚集索引表。检查 dm_db_index_operational_stats 后,page_lock_wait_count列大于 0,表示之前发生过一些页面锁定,但没有记录row_lock_wait_count,如下图所示。
我尝试在 SQL Server 2019 中模拟这种情况。在我的测试数据库环境中将事务级别设置为“读取已提交快照”,与 Azure SQL 相同。但我只能遇到以下锁定情况。
- 开始事务 A,更新表 T 中的几行。开始事务 B,更新表中的类似行。事务 B 的等待锁资源类型是dm_tran_locks 中的KEY 。
- 开始事务 A,更新表 T 中的大量行。开始事务 B,也更新表的大量行。事务 B 的等待资源类型是 OBJECT,因为事务 A 持有 OBJECT 的 X 模式,我猜这是因为锁已经升级了。我在我的测试环境中多次尝试了上述场景。由于事务 A 会导致 Page IX 锁,因此 row_lock_count /row_lock_wait_count/page_lock_count会增加,但没有发生 page_lock_wait_count。
问题:
- 为什么当row_lock_wait_count=0时page_lock_wait_count可以大于0?
- 什么情况或者场景会引起页面锁等待?
他们测量的是两个不同且通常独立的事物。
如果进程请求行锁并发现必须等待,则会增加
row_lock_wait_count
。如果进程请求页锁并发现必须等待,则会增加
page_lock_wait_count
。每次数据访问都从存储引擎决定使用何种锁定粒度开始。它可能决定从行、页甚至对象级锁定开始。
您可以使用 、 和 等提示来影响此决定
ROWLOCK
。PAGLOCK
否则TABLOCK
,引擎会根据执行时的各种元数据自行做出决定。引擎无需重新编译计划即可在同一计划的执行之间改变锁定粒度。在页面级锁定下访问数据的进程可能会增加,
page_lock_wait_count
但它永远不会增加,row_lock_wait_count
因为它从未尝试获取该粒度的锁。除了用户活动之外,还有一些系统进程仅在页面级锁定粒度上运行,例如索引重组。
任何时候,一个进程请求的页面锁与另一个进程已经持有的页面锁不兼容。一定有一百万种可能性。
一种常见情况(在 RCSI 下)是两个并发进程以页面粒度
U
对同一张表进行更新()锁定。两个进程都无法成功完成锁定升级,因为另一个进程已持有该表上的不兼容锁定。请注意,锁升级只会升级到分区或对象级别。例如,锁永远不会从行粒度升级到页粒度。