我们有一台 SQL Server 2019 CU18,我们发现 querystore 存在一个奇怪的问题。通常,每小时日志备份的平均大小为 40MB,但一旦我们启用 querystore,日志备份的平均大小就会变成 2.5GB。
根据 querystore,每小时执行 140,000 个查询。这大约相当于每秒执行 40 次。
这是我们的查询存储的配置:
ALTER DATABASE [db_name]
SET QUERY_STORE = ON
(
OPERATION_MODE = READ_WRITE
,CLEANUP_POLICY = (STALE_QUERY_THRESHOLD_DAYS = 45)
,DATA_FLUSH_INTERVAL_SECONDS = 900
,MAX_STORAGE_SIZE_MB = 2048
,INTERVAL_LENGTH_MINUTES = 30
,SIZE_BASED_CLEANUP_MODE = AUTO
,QUERY_CAPTURE_MODE = AUTO
);
当我打开这么大的 logbackup 文件时,fn_dump_dblog
我发现同一秒内发生了多个事务。这些事务都名为“SwapPage”。
手术 | 语境 | 分配单元编号 | 页面 ID | 交易名称 |
---|---|---|---|---|
LOP_BEGIN_XACT | 空值 | 无效的 | 无效的 | 交换页面 |
LOP_INSYSXACT | LCX_INDEX_INTERIOR | 72057594047692800 | 0001:00056321 | 无效的 |
LOP_INSYSXACT | LCX_CLUSTERED | 72057594047692800 | 0001:000a871c | 无效的 |
LOP_INSYSXACT | LCX_CLUSTERED | 72057594047692800 | 0001:0000041b | 无效的 |
LOP_INSYSXACT | LCX_CLUSTERED | 72057594047692800 | 0001:0000041c | 无效的 |
页面格式化 | 链接重组页面 | 72057594047692800 | 0001:000a8715 | 无效的 |
LOP_修改头 | 链接重组页面 | 72057594047692800 | 0001:000a8715 | 无效的 |
LOP_INSYSXACT | LCX_CLUSTERED | 72057594047692800 | 0001:000a8715 | 无效的 |
LOP_修改头 | LCX_HEAP | 72057594047692800 | 0001:000a871c | 无效的 |
LOP_修改头 | LCX_HEAP | 72057594047692800 | 0001:0000041c | 无效的 |
插入行 | LCX_CLUSTERED | 72057594047692800 | 0001:000a8715 | 无效的 |
LOP_修改头 | LCX_HEAP | 72057594047692800 | 0001:000a8715 | 无效的 |
LOP_修改头 | LCX_HEAP | 72057594047692800 | 0001:000a8715 | 无效的 |
LOP_修改_行 | LCX_INDEX_INTERIOR | 72057594047692800 | 0001:00056321 | 无效的 |
LOP_修改头 | LCX_HEAP | 72057594047692800 | 0001:0000041b | 无效的 |
LOP_修改头 | LCX_HEAP | 72057594047692800 | 0001:0000041b | 无效的 |
锁定迁移 | 空值 | 无效的 | 0001:000a8715 | 无效的 |
LOP_INSYSXACT | LCX_CLUSTERED | 72057594047692800 | 0001:000a8715 | 无效的 |
LOP_INSYSXACT | LCX_CLUSTERED | 72057594047692800 | 0001:0000041c | 无效的 |
LOP_INSYSXACT | 链接重组页面 | 72057594047692800 | 0001:0000041b | 无效的 |
LOP_INSYSXACT | LCX_CLUSTERED | 72057594047692800 | 0001:000a871c | 无效的 |
LOP_INSYSXACT | LCX_INDEX_INTERIOR | 72057594047692800 | 0001:00056321 | 无效的 |
提交XACT | 空值 | 无效的 | 无效的 | 无效的 |
分配单元指向plan_persist_runtime_stats。
在Paul White的评论之后,我设置了一个扩展事件来捕获query_store_index_rebuild_started
和query_store_index_rebuild_finished
。令我惊讶的是,Querystore 正在进行索引重建。以下是此跟踪的结果:
事件 | 时间戳 | 当前大小 |
---|---|---|
query_store_index_rebuild_started | 2024-12-05 07:51:10.353 | 874208 |
查询存储索引重建已完成 | 2024-12-05 07:52:29.073 | 868832 |
query_store_index_rebuild_started | 2024-12-05 08:20:58.497 | 873504 |
查询存储索引重建已完成 | 2024-12-05 08:22:18.320 | 869152 |
query_store_index_rebuild_started | 2024-12-05 08:36:03.147 | 874528 |
查询存储索引重建已完成 | 2024-12-05 08:37:19.670 | 869664 |
query_store_index_rebuild_started | 2024-12-05 09:06:00.943 | 874336 |
查询存储索引重建已完成 | 2024-12-05 09:07:12.750 | 870304 |
看起来索引重建在 874MB 左右开始,Querystore 的最大大小设置为 2048。
我还将事件的堆栈跟踪包含query_store_index_rebuild_started
在扩展事件中。
sqllang!XeSqlPkg::CollectClientHostnameActionInvoke sqllang!XeSqlPkg::CollectDatabaseIdActionInvoke sqllang!XeSqlPkg::CollectDatabaseNameActionInvoke sqllang!XeSqlPkg
::CollectNtUsernameActionInvoke sqllang!XeSqlPkg::CollectSessionIdActionInvoke sqllang!XeSqlPkg::CollectTSqlStack<XE_ActionForwarder> sqllang!XeSqlPkg::CollectTSqlStackActionInvoke qds!XeQdsPkg::query_store_index_rebuild_started::Publish
qds!CDBQDS::ReclaimFreePages
qds!CDBQDS::DoSizeRetention
qds!CDBQDS::ProcessQdsBackgroundTask
qds!CQDSManager::AcquireGenericQdsDbAndProcess<<lambda_e51628d7833f66b5a045fa5bf2d27953>>
qds!CDBQDS::ProcessQdsBackgroundTask
sqldk!SOS_Task::Param::Execute
sqldk!SOS_Scheduler::RunTask
sqldk!SOS_Scheduler::ProcessTasks
sqldk!SchedulerManager::WorkerEntryPoint
sqldk!SystemThreadDispatcher::ProcessWorker
sqldk!SchedulerManager::ThreadEntryPoint
KERNEL32+0x17AC4
ntdll+0x5A8C1
我曾希望找到触发索引重建的原因,但没有那么幸运。
在Zikato的一些指导下,我在跟踪中添加了一些额外的查询存储相关事件。这表明,只有query_store_size_retention_cleanup_started
发生事件时才会触发索引重建。
重建:
每次运行清理时都会删除 0KB,但显然需要重建。让我感到困惑的是清理事件的出现。我以为只有当 querystore 达到最大存储大小的 90% 时才会触发此事件。
增加 querystore 的最大大小没有任何区别。
有人遇到过同样的问题吗?有人能解释一下发生了什么吗?实例上的其他数据库没有这个问题。
在调试器下进行检查可以发现,查询存储在任何清理之后都会在其内部表上运行索引重组,无论是由大小还是年龄触发。
这很有道理,因为清理的目的是为了释放空间。用户无法对内部 QDS 表进行维护。我们也无法禁用页面锁定,这是阻止重组运行的一种方法。
因此,在 2019 年,你无法禁用这种重组行为;这只是查询存储所做的。这解释了日志增长。
每次将查询存储数据保存到磁盘时,SQL Server 都会检查是否需要基于大小或基于时间的清理。基于大小的清理的 90% 阈值存储为变量值,而不是硬编码常量。这表明服务器可能会在某些条件下改变百分比。
可以使用未记录的全局跟踪标志 7748 来阻止触发基于时间的清理。对于基于大小的清理,似乎没有类似的功能。
无论触发条件如何,SQL Server 似乎总是首先尝试基于时间的清理(如果已启用,并且未设置跟踪标志)。这可能是因为基于时间的清理涉及的开销要少得多(无需根据针对内部表运行的相对昂贵的查询来决定要驱逐哪些查询)。
奇怪的是,SQL Server 每隔 运行一次基于时间的清理
INTERVAL_LENGTH_MINUTES
。即使它没有发现任何要做的事情,之后仍然会执行索引重组。有一个未记录的跟踪标志可以防止索引重组,但它仅存在于 SQL Server 2022 中,而不存在于 SQL Server 2019 中。即使您正在运行 SQL Server 2022,您也需要联系 Microsoft 支持以获得使用新标志的授权。
同时,您可以尝试使用未记录的跟踪标志 7748(或设置为零)禁用基于时间的清理
STALE_QUERY_THRESHOLD_DAYS
以进行正常操作,从而允许它在您可以容忍索引重组的期间运行。2GB 足以保存 45 天的查询存储数据吗?
理想情况下,你应该避免基于大小的清理,正如 Kendra Little 的博客文章所言:基于大小的清理会导致性能问题 - 如何避免
您可以尝试将最大存储大小加倍并查看重新索引频率是否下降吗?
您还可以使用 XE 进行监控
query_store_size_retention_cleanup_started
,并检查它是否与query_store_index_rebuild_started
经过反复试验,我找到了解决该问题的方法。
如果设置
Stale_Query_Threshold_Days
为 30,则重建过程每天仅触发几次。一旦我将其恢复为 45,它几乎每 20 分钟运行一次。我无法找到此问题的解释,但解决方法可以解决问题。