为什么在对表变量和临时表进行更新时,latch等待有很大的不同?

时间:2018-02-16 13:52:24

标签: sql-server

帮助我理解表变量和临时表之间的锁存差异。 例如:

SET NOCOUNT ON

DECLARE @t TABLE (id int NOT NULL IDENTITY(1,1) PRIMARY KEY, ss varchar(50))
INSERT INTO @t (ss)
    VALUES ('TestTest'), ('TestTest')

declare @n int = 0

WHILE  @n < 100000
BEGIN
    SET @n += 1

    UPDATE @t
    SET ss = REVERSE(ss)
END

运行它! 并检查等待统计:

SELECT * FROM sys.dm_exec_session_wait_stats WHERE session_id = <spid>

结果:

session_id wait_type                                                    waiting_tasks_count  wait_time_ms         max_wait_time_ms     signal_wait_time_ms

951        PAGELATCH_SH                                                 1                    0                    0                    0
951        PAGELATCH_EX                                                 200002               12655                379                  12414
951        SOS_SCHEDULER_YIELD                                          22                   636                  205                  636
951        MEMORY_ALLOCATION_EXT                                        45                   0                    0                    0

似乎锁定器插入2次插入,200000次更新。 但是当我们将表变量(@t)切换到临时表(#t)时,我们根本没有锁存器:

session_id wait_type                                                    waiting_tasks_count  wait_time_ms         max_wait_time_ms     signal_wait_time_ms

1108       SOS_SCHEDULER_YIELD                                          910                  83                   4                    82
1108       MEMORY_ALLOCATION_EXT                                        110                  0                    0                    0

我有两个问题:

  1. 为什么我们在表变量中有锁存器而没有临时表?
  2. 为什么要锁定每一行?为什么不每次更新一次(交易)?

1 个答案:

答案 0 :(得分:3)

  

似乎闩锁被采取了...... 200000次   更新

您的查询显示了锁存等待的数量 - 而不是锁存器的数量,但这仍然是一个有趣的差异。以下是部分答案。它解释了发生了什么,但不解释为什么存在差异。

它基于Paul White的博文Optimizing Update Queries

中提供的信息

对于具有表变量的更新,在这种情况下,计划在RHS上进行聚簇索引扫描

enter image description here

聚簇索引扫描在页面上采用共享锁存器,在输出行值时不会释放它。聚簇索引扫描的输出列为idss - 字符串相反,然后聚簇索引更新运算符查找传入的行idsqlmin.dll!CValFetchByKeyForUpdate::ManipData下面)并更新行。它需要一个独有的锁存器来更新页面,但被扫描保持的共享锁存器阻止。所以它需要进入锁存器的等待状态,然后释放共享锁存器并解锁自身,如链接博客文章的Lazy Latches部分所述。

表格变量热门路径

enter image description here

使用临时表时,可以使用行集共享。这也在链接的帖子中描述

  

...特殊优化,意味着聚集索引扫描和   聚集索引更新共享相同的行集...更新不再需要找到要更新的行 - 读取已正确定位。

该代码路径与表变量(如here所示)非常不同。

这似乎避免了上述延迟锁存的问题。它仍然在开始时使用SH锁存器,但在尝试获取EX锁存器之前释放它,并在更新的其余部分保持EX锁存器。所以在任何时候都不得不等待

锁定页面以进行#temp表更新(两行)

enter image description here

在页面上锁定@table变量更新(两行)

enter image description here

跟踪标志8666添加到执行计划的附加信息表明这两个计划在更新运算符中具有m_isolationLevelHintm_lockModeHint的不同值。

也许这些必须是某个值才能获得行集共享优化(?)

enter image description here