lock_acquired 扩展事件中的锁升级和计数差异

Aru*_*ath 5 sql-server lock-escalation locking

我试图理解为什么在某些情况下锁定计数sys.dm_tran_lockssqlserver.lock_acquired扩展事件存在差异。这是我的重现脚本,我StackOverflow2013在 SQL Server 2019 RTM 上使用数据库,兼容性级别为 150。

/* Initial Setup */
IF OBJECT_ID('dbo.HighQuestionScores', 'U') IS NOT NULL 
DROP TABLE dbo.HighQuestionScores; 

CREATE TABLE dbo.HighQuestionScores 
(
    Id INT PRIMARY KEY CLUSTERED,
    DisplayName NVARCHAR(40) NOT NULL,
    Reputation BIGINT NOT NULL,
    Score BIGINT
)

INSERT dbo.HighQuestionScores  
        (Id, DisplayName, Reputation, Score)
SELECT u.Id, 
       u.DisplayName,
       u.Reputation, 
       NULL
FROM dbo.Users AS u;

CREATE INDEX ix_HighQuestionScores_Reputation ON dbo.HighQuestionScores  (Reputation);
Run Code Online (Sandbox Code Playgroud)

接下来我用一个大的假行数更新表统计信息

/* Chaotic Evil. */
UPDATE STATISTICS dbo.HighQuestionScores WITH ROWCOUNT = 99999999999999;
DBCC FREEPROCCACHE WITH NO_INFOMSGS;
Run Code Online (Sandbox Code Playgroud)

然后我打开一个交易并更新Score声誉,说56

BEGIN TRAN;
UPDATE dbo.HighQuestionScores  
SET Score = 1
WHERE Reputation = 56 /* 8066 records */
AND 1 = (SELECT 1);
/* Source: https://www.erikdarlingdata.com/sql-server/helpers-views-and-functions-i-use-in-presentations/ Thanks, Erik */
SELECT *
FROM dbo.WhatsUpLocks(@@SPID) AS wul 
WHERE wul.locked_object = N'HighQuestionScores'
ROLLBACK;
Run Code Online (Sandbox Code Playgroud)

我得到了一堆页面锁(尽管有声誉索引)。我猜测错误的估计确实对那里的优化器产生了影响。

页锁

我还仔细检查了使用sp_whoisactive,它也返回相同的信息。

<Object name="HighQuestionScores" schema_name="dbo">
  <Locks>
    <Lock resource_type="OBJECT" request_mode="IX" request_status="GRANT" request_count="1" />
    <Lock resource_type="PAGE" page_type="*" index_name="PK__HighQues__3214EC072EE1ADBA" request_mode="X" request_status="GRANT" request_count="6159" />
  </Locks>
</Object>
Run Code Online (Sandbox Code Playgroud)

同时,我还有一个sqlserver.lock_acquired单独运行的扩展事件。当我查看分组数据时,我看到8066页锁定而不是初始6159

extended_event_lock_count

我绝对没有看到锁升级(使用sqlserver.lock_escalation事件验证),所以我想我的问题是为什么扩展事件显示出与更多锁计数的差异?

Jos*_*ell 6

每次更新行时,XE 都会报告获取页面锁(受更新影响的 8066 行中的每一行都有一个事件)。但是,这些行仅存储在 6159 个唯一页面上,这就解释了这种差异。

我在这台机器上没有 StackOverflow2013,但是在 SO2010 上得到了类似的体验:

  • 更新了 1368 行(并且触发了许多 XEvent)
  • 958页锁

如果按resource_0以下方式排序,您可以在 XE 输出中看到相同的页面被重复锁定:

SSMS 中 XE 输出的屏幕截图

使用DBCC PAGE

DBCC TRACEON (3604); -- needed for the next one to work
GO

DBCC PAGE (StackOverflow2010, 1, 180020, 3);
GO
Run Code Online (Sandbox Code Playgroud)

我可以看到第 180020 页上有 163 条记录 ( m_slotCnt = 163):

DBCC TRACEON (3604); -- needed for the next one to work
GO

DBCC PAGE (StackOverflow2010, 1, 180020, 3);
GO
Run Code Online (Sandbox Code Playgroud)

其中 3 个符合更新条件(我将输出粘贴到 notepad++ 并搜索“Reputation = 56”):

显示 3 个数学的屏幕截图

以第一场比赛为例:

Page @0x000002C278C62000

m_pageId = (1:180020)               m_headerVersion = 1                 m_type = 1
m_typeFlagBits = 0x0                m_level = 0                         m_flagBits = 0x0
m_objId (AllocUnitId.idObj) = 174   m_indexId (AllocUnitId.idInd) = 256 
Metadata: AllocUnitId = 72057594049331200                                
Metadata: PartitionId = 72057594044350464                                Metadata: IndexId = 1
Metadata: ObjectId = 1525580473     m_prevPage = (1:180019)             m_nextPage = (1:180021)
pminlen = 24                        m_slotCnt = 163                     m_freeCnt = 31
m_freeData = 7835                   m_reservedCnt = 0                   m_lsn = (203:19986:617)
m_xactReserved = 0                  m_xdesId = (0:0)                    m_ghostRecCnt = 0
m_tornBits = 1894114769             DB Frag ID = 1                
Run Code Online (Sandbox Code Playgroud)

我相信这种行为是由于执行计划的流水线性质,以及这个特定 XE 的实现方式。