更改跟踪导致闩锁争用

Dan*_*vey 7 change-tracking sql-server-2016

我在 Windows Server 2012 R2 Standard 6.3 上使用 Microsoft SQL Server 2016 (SP2-GDR) (KB4505220) - 13.0.5101.9 (X64) Jun 15 2019 23:15:58 版权所有 (c) Microsoft Corporation 标准版(64 位) (内部版本 9600:)

数据库大小约为 870 GB。这是 SQL 标准,我在服务器上有 128 GB 的 RAM。数据库位于 SSD 驱动器上。数据文件与日志文件位于不同的驱动器上,Tempdb 也有自己的 SSD 驱动器。服务器平均获得大约 1200 个查询/秒,它可以高达 2000 个查询/秒。重新编译保持较低,每秒只有 1 到 8 次。页面预期寿命不错,平均 61 分钟。

服务器有 6 个物理核心 + 超线程。

我们在数千个设备连接并尝试将更改与跟踪键同步的系统上大量使用 SQL Server 的更改跟踪。

它通常运行良好,但有时,有一天或另一天,服务器的闩锁会猛增,从 0 毫秒到平均 60677 毫秒。

SQL 锁存器

当我检查正在运行哪些查询时,我只能看到同步查询,全部被阻止,带有“PAGELATCH_UP”,全部试图访问更改跟踪表,被阻止的 300 多个查询。

我有几个问题:

  • SQL Server 是否在查找更改跟踪更改时锁定整个表?
  • 使用 SQL Entreprise 会得到更好的结果还是不会改变任何东西?
  • 知道为什么更改跟踪在大多数情况下运行良好,但每周都没有明显原因崩溃吗?

这些是我的更改跟踪表大小。我的查询阻塞的表是前三个表,只有几 mb 的数据。

在此处输入图片说明

他们都在等待同一个waitresource。

服务员资源

Waitresource 2:4:88968 在 tempdb 中。但是 tempdb 只负责服务器大约 9% 的写入和 6% 的读取。

io报告

但是我的查询不使用 tempdb,所以我猜是因为更改跟踪的内部方式是这样的吗?这是我的查询

DECLARE @Id INT; SET @Id = (SELECT Id FROM Users WHERE No=@No); 

SELECT DISTINCT lh.Key1 
FROM ( 
    SELECT Key1 FROM CHANGETABLE(CHANGES dbo.Table1, @TrackingKey) AS CT 
    UNION ALL 
    SELECT Key1 
    FROM dbo.Table2 lhd 
    INNER JOIN (SELECT Key2 FROM CHANGETABLE(CHANGES dbo.Table2, @TrackingKey) AS CT) AS CTLHD ON(CTLHD.Key2=lhd.Key2) 
    UNION ALL 
    SELECT Key1 
    FROM CHANGETABLE(CHANGES dbo.Table3, @TrackingKey) AS CT 
) AS L 
JOIN dbo.Table1 lh ON lh.Key1 = L.Key1 
WHERE lh.Id = @Id AND lh.Date BETWEEN @StartUtc AND @EndUtc 
Run Code Online (Sandbox Code Playgroud)

我的 tempdb 有 10 个文件,它们的大小相同。

临时数据库

为了让客户端恢复正常,我通常最终会做的是将其置于停机时间,然后逐渐将其恢复,以便所有移动设备逐渐同步。但我们的系统是关键任务,这不是一个长期的解决方案。

我一直在考虑的另一个解决方案是改变系统处理变更跟踪查询的方式。让移动设备与“自制”表同步,并使用来自更改跟踪的单个服务读取更改填充此表。这样我会将并发查询限制为更改跟踪表,但恐怕我只会将问题移到自制表中。

对此有何想法?任何帮助将不胜感激。


编辑:头部阻滞剂

我试图确定谁是头部拦截器以及它在等待什么,但这是一项艰巨的任务。看来我有很多“头部阻滞剂”。

所有查询都运行相同的 SELECT,几乎都分成 4 个线程,对于某些查询,它们根本没有被阻塞,而是在等待“杂项”,但对于某些查询,至少部分线程被其他查询阻塞。

例如,现在显示 294 个线程。

查询 202 被分成 4 个线程,其中一个线程被 123 阻塞,但其他线程没有被阻塞。三个线程正在等待“MISCELLANEOUS”,被阻塞的线程正在等待“PAGELATCH_UP”

至于查询 123,它没有被阻塞,它的 4 个线程正在等待“杂项”

或者例如,查询 219 在一个线程上被查询 140 阻塞,而在其他三个线程上被查询 69 阻塞。

69被193阻塞了,193正在运行,再次等待“MISCELLANEOUS”。140 不再在列表中,因此它已超时或已完成。

我的并行成本阈值是 70。
锁 0
最大并行度 3
查询等待 -1

未在数据库上启用快照隔离级别。查询未使用快照隔离级别。

数据库选项

我还检查了表的统计信息,甚至是 sys.change_tracking 表。对于查询的表,表上的索引没有碎片化(小于 10%)。

我运行了一两个查询,通常查询的结果是 4 行,由于 DISTINCT 子句,它变成了只有一行。所以它不像是返回数千行。

当我在 SSMS 中运行查询时,它很快并且不会阻塞,即使我目前在同一查询的服务器上看到数百个被阻塞的查询。所以我想这可能与参数嗅探有关??

这是我在 SSMS 中执行时的 I/O 统计信息。

SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 0 ms.
SQL Server parse and compile time: 
   CPU time = 92 ms, elapsed time = 92 ms.
Table 'Users'. Scan count 0, logical reads 2, physical reads 0, read-ahead  reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row affected)

 SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 0 ms.

(1 row affected)
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Workfile'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'syscommittab'. Scan count 3, logical reads 555, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'change_tracking_62623266'. Scan count 1, logical reads 3364, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Table1'. Scan count 3, logical reads 9, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Table2'. Scan count 0, logical reads 34281, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'change_tracking_46623209'. Scan count 1, logical reads 1152, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'change_tracking_78623323'. Scan count 1, logical reads 1077, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row affected)

 SQL Server Execution Times:
   CPU time = 296 ms,  elapsed time = 435 ms.
SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 0 ms.

 SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 0 ms.

Completion time: 2020-01-22T14:18:25.9480651-05:00
Run Code Online (Sandbox Code Playgroud)

这是我的查询计划 https://www.brentozar.com/pastetheplan/?id=By7k-7UWI

但同样,那是不会阻止的版本。我启用了查询存储,所以我可以尝试获取阻止的版本,我只是不是 100% 怎​​么做。


编辑:查询商店信息

我的查询是查询存储的“回归查询”中的第一个。

查询店铺信息

计划

根据查询商店,这里是“坏计划” https://www.brentozar.com/pastetheplan/?id=ryqKGQUbL

这是“好计划” https://www.brentozar.com/pastetheplan/?id=rknnGQ8WL

我应该“强制”这个好计划吗?


编辑我的解决方案

好的,所以我使用了 Brent Ozar 的 sp_blitzcache ( https://www.brentozar.com/blitz/ ),“expert_mode”为 1,以便能够检索“坏计划”的句柄并能够从缓存(不清除任何其他内容)。

DBCC FREEPROCCACHE (0x06000800155A5106F08F632F1C00000001000000000000000000000000000000000000000000000000000000);
Run Code Online (Sandbox Code Playgroud)

我的服务器再次恢复正常状态,所有数百个被阻止的查询都消失了。我猜这是参数嗅探呢?希望不要再发生了。想找个办法让它不再发生。

Jos*_*ell 5

I'm not sure how to deal with the bad plan issue, maybe someone will come along with better query tuning skills to help out with that.

However, to speak to the tempdb contention issue, the page that all these sessions are fighting over is a PFS page. These are defined in the documentation as:

Page Free Space (PFS) pages record the allocation status of each page, whether an individual page has been allocated, and the amount of free space on each page.

请注意,它们仅跟踪某些类型页面的可用空间:

页面中的可用空间量仅针对堆和文本/图像页面进行维护。

那里的“文本/图像”注释包括更现代的 LOB 数据类型(nvarchar(max)等等)。

顺便说一句,你可以看出它是一个 PFS 页,因为它可以被 8088 整除:

因此,在第一个 PFS 页之后有一个新的 PFS 页 8,088 页,并且在后续的 8,088 页间隔中有附加的 PFS 页。


所有这些都表明,“糟糕的计划”可能会溢出到 tempdb(有两种排序和一种散列连接),这会导致对该特定 PFS 页面的争用。还有一些急切的索引假脱机正在写入 tempdb。

您可以通过安装SP2 CU1来缓解此问题,其中包括此修复

除了跨所有 tempdb 数据文件的循环分配之外,此修复还通过在同一数据文件中的多个 PFS 页面之间执行循环分配来改进 PFS 页面分配。因此,数据文件中包含的 PFS 数据页越多以及数据文件越多,分配分布就越好。

因此,即使有“糟糕的计划”,您也希望减少锁存争用,从而减慢速度。