☰
SQL Server Query Store实战:性能监控、计划强制与优化闭环
2026/10/14 16:16:10 网站建设 项目流程

1. 被大多数人忽略的性能透视镜:Query Store到底解决了什么痛点

如果你在数据库运维这条路上摸爬滚打过几年,一定经历过这样的场景:某个查询昨天还好好的,今天突然变得奇慢无比,CPU直接飙到90%以上。你手忙脚乱地去查执行计划,发现它跟以前不一样了,但问题是——你根本不知道它是什么时候变的,也不知道变成了什么样,更不知道原来那个"好"的计划长什么样子。这种"案发现场已经被破坏"的无力感,是每一个DBA都深恶痛绝的。

在Query Store出现之前,我们排查这类问题的核心手段无非就是查DMV(动态管理视图),比如sys.dm_exec_query_stats、sys.dm_exec_sql_text、sys.dm_exec_query_plan这几个老搭档。但这套方案有个致命缺陷:所有数据都存在内存里,一旦发生计划缓存淘汰、服务重启、故障转移,这些信息就全部归零。你只能看到"当前"的状态,看不到"历史"的轨迹。这就好比医生只能看到病人现在的体温,却拿不到过去一周的体温曲线图,诊断起来全靠猜。

Query Store的出现彻底改变了这个局面。它本质上是一个内置在数据库引擎里的"飞行记录仪",持续不断地把查询的执行计划、运行时统计信息、等待类别等关键数据持久化到用户数据库内部的系统表中。注意这里的关键词——持久化。这意味着即使数据库重启、计划缓存被清空、发生主备切换,历史数据依然完好无损地躺在那里,随时等你来查。

我第一次在生产环境启用Query Store是在一个高并发的OLTP系统上。当时有个存储过程每隔几天就会突然变慢,重启一下服务就好了,但过几天又复发。用传统DMV根本抓不到现场,因为等你接到告警登录上去的时候,坏计划可能已经被挤出了缓存。启用Query Store之后,我直接查了sys.query_store_plan和sys.query_store_runtime_stats,不到十分钟就定位到了问题:某个参数化查询在不同参数值下触发了不同的执行计划,其中一个计划做了全表扫描。有了这个证据,后续的修复就有的放矢了。

这篇文章适合哪些人看?如果你是数据库开发工程师,Query Store能帮你找到那些"写的时候没觉得有问题、跑起来才发现要命"的查询;如果你是DBA,它是你排查性能回归、做容量规划、验证索引效果的得力助手;如果你是架构师,理解Query Store的能力边界能帮你在设计阶段就做好可观测性规划。不管你是刚接触Azure SQL Database还是已经用了一段时间,这篇内容都会帮你重新建立对Query Store的系统认知。

2. Query Store的数据采集机制:它到底在后台记了什么账

2.1 两个核心存储区域:计划与运行时统计

要理解Query Store怎么工作,得先搞清楚它内部的数据组织方式。Query Store在用户数据库中创建了一系列系统目录视图,核心的几张表各司其职:

  • sys.query_store_query:记录每个被跟踪的查询文本和查询指纹(query hash),相当于给每条不同的查询发了一张身份证。
  • sys.query_store_plan:存储每个查询对应的执行计划,包括计划本身的XML、编译时间、强制计划标记等。
  • sys.query_store_runtime_stats:按时间区间聚合的运行时统计,包括执行次数、持续时间、CPU时间、逻辑读写、物理读写等。
  • sys.query_store_runtime_stats_interval:定义统计信息的时间分桶边界,默认每60分钟一个区间,可配置。
  • sys.query_store_wait_stats:记录查询在执行过程中的等待类别分布,这是排查阻塞和资源争用的利器。

这几张表之间的关系可以这样理解:一个查询(query)可能有多个执行计划(plan),每个计划在不同的时间段(interval)内产生不同的运行时统计(runtime_stats)。当你发现某个查询变慢了,实际上是在说"这个查询的某个计划在某个时间段内的平均持续时间突然升高了"。

2.2 采集频率与数据聚合逻辑

Query Store并不是每执行一次查询就写一条记录,那样开销太大了。它的工作方式是先在内存中聚合,再定期刷写到磁盘。默认情况下,运行时统计数据的刷写间隔是15分钟(由INTERVAL_LENGTH_MINUTES控制,最小1分钟,最大1440分钟)。也就是说,在这15分钟内,同一条查询、同一个计划的多次执行会被合并成一条聚合记录,记录总执行次数、总持续时间、最大/最小/平均持续时间等。

这里有个容易踩的坑:很多人以为Query Store记录的是每一次执行的精确数据,其实不是。它是聚合后的统计值。如果你需要精确到每一次执行的粒度,Query Store满足不了你,得靠Extended Events或者SQL Trace。但对于绝大多数性能排查场景,聚合数据已经足够用了——你关心的是"趋势"和"异常",而不是"某一次"。

注意:INTERVAL_LENGTH_MINUTES设置得太小会导致数据量急剧膨胀,设置得太大又会让统计粒度太粗。对于大多数生产系统,15分钟是个比较平衡的选择。如果系统QPS很高且你对时间精度要求高,可以调到5分钟,但要同步关注空间使用情况。

2.3 数据保留与空间管理

Query Store的数据是存在用户数据库的文件组里的,所以它会占用你的数据库空间。默认情况下,MAX_STORAGE_SIZE_MB是100MB,STALE_QUERY_THRESHOLD_DAYS是30天。当空间使用接近上限时,Query Store会自动清理最旧的数据。这个自动清理机制有个特点:它优先删除旧数据,但如果旧数据删完了空间还不够,它会进入"只读模式",停止采集新数据。

我见过好几次生产事故是因为Query Store空间满了进入只读模式,结果新的性能问题没有被记录下来,排查时发现"最近几小时没有数据"。所以我的建议是:把MAX_STORAGE_SIZE_MB设置得足够大,至少500MB起步,对于繁忙的系统建议2GB以上。同时定期查一下sys.database_query_store_options里的current_storage_size_mb和max_storage_size_mb,做到心里有数。

-- 查看Query Store当前配置和空间使用情况 SELECT desired_state_desc, actual_state_desc, readonly_reason, current_storage_size_mb, max_storage_size_mb, stale_query_threshold_days, interval_length_minutes, flush_interval_seconds FROM sys.database_query_store_options;

这段查询是我每次接手一个新数据库时必跑的,几秒钟就能对Query Store的健康状况有个全面了解。如果actual_state_desc显示READ_ONLY,那就得赶紧处理了。

3. 从零到一:在不同环境中启用Query Store的正确姿势

3.1 Azure SQL Database上的默认行为

如果你用的是Azure SQL Database,有个好消息:Query Store默认就是开启的,而且默认配置针对云环境做了优化。默认的MAX_STORAGE_SIZE_MB通常是100MB,INTERVAL_LENGTH_MINUTES是60分钟,STALE_QUERY_THRESHOLD_DAYS是30天。对于轻量级应用,这套默认值基本够用;但对于中大型系统,我强烈建议你主动调整。

在Azure SQL Database上调整Query Store配置的语法和本地SQL Server基本一致:

-- 调整Query Store配置(Azure SQL Database) ALTER DATABASE [YourDatabaseName] SET QUERY_STORE = ON ( OPERATION_MODE = READ_WRITE, CLEANUP_POLICY = (STALE_QUERY_THRESHOLD_DAYS = 30), DATA_FLUSH_INTERVAL_SECONDS = 900, INTERVAL_LENGTH_MINUTES = 15, MAX_STORAGE_SIZE_MB = 2048, QUERY_CAPTURE_MODE = AUTO, SIZE_BASED_CLEANUP_MODE = AUTO, MAX_PLANS_PER_QUERY = 200, WAIT_STATS_CAPTURE_MODE = ON );

这里有几个参数值得展开说:

  • QUERY_CAPTURE_MODE:控制哪些查询被捕获。ALL表示捕获所有查询,AUTO表示忽略那些执行频率极低、编译开销极小的查询,NONE表示停止捕获新查询。生产环境建议用AUTO,既能抓住重点又不会让数据量爆炸。
  • MAX_PLANS_PER_QUERY:每个查询最多保留多少个计划。默认200,对于大多数场景够用。但如果你的系统有大量参数化查询且参数分布极不均匀,可能需要调大。
  • WAIT_STATS_CAPTURE_MODE:是否捕获等待统计。这个功能在排查阻塞和资源争用时非常有用,建议开启。

3.2 本地SQL Server的启用差异

本地SQL Server从2016版本开始支持Query Store,但默认是关闭的。启用语法和Azure基本一致,但有几个细节需要注意:

第一,本地SQL Server的Query Store数据存在主文件组里,如果你的数据库主文件组空间紧张,需要提前规划。第二,本地SQL Server的OPERATION_MODE可以设置为READ_WRITE、READ_ONLY或READ_WRITE,但在故障转移场景下(比如Always On可用性组),Query Store的行为会有所不同——主副本上的数据会异步同步到辅助副本,但辅助副本上的Query Store是只读的。

-- 本地SQL Server启用Query Store ALTER DATABASE [YourDatabaseName] SET QUERY_STORE = ON ( OPERATION_MODE = READ_WRITE, CLEANUP_POLICY = (STALE_QUERY_THRESHOLD_DAYS = 30), DATA_FLUSH_INTERVAL_SECONDS = 900, INTERVAL_LENGTH_MINUTES = 15, MAX_STORAGE_SIZE_MB = 1024, QUERY_CAPTURE_MODE = AUTO );

3.3 启用前必须确认的三件事

在正式启用Query Store之前,我建议你先确认以下三件事,避免启用后才发现问题:

第一,数据库兼容级别。Query Store需要数据库兼容级别至少为130(对应SQL Server 2016)。如果兼容级别低于130,启用会直接报错。用SELECT compatibility_level FROM sys.databases WHERE name = 'YourDB'查一下。

第二,空间余量。虽然Query Store有自动清理机制,但如果你把MAX_STORAGE_SIZE_MB设得很大而磁盘空间本身就不足,那就是给自己挖坑。建议预留至少2倍于MAX_STORAGE_SIZE_MB的磁盘空间。

第三,是否有大量即席查询。如果你的系统有大量未参数化的即席查询(ad-hoc query),每条查询文本都不同,Query Store会为每条都生成一条记录,数据量会迅速膨胀。这种情况下建议用QUERY_CAPTURE_MODE = AUTO,或者考虑在应用层做参数化改造。

提示:启用Query Store本身对性能的影响通常在1%-3%之间,对于绝大多数系统是可以接受的。但如果你的系统已经处于CPU瓶颈边缘,建议在低峰期启用,并密切观察启用后的性能变化。

4. 读懂Query Store的报表:从内置界面到自定义查询

4.1 内置报表的四个核心视图

在SQL Server Management Studio(SSMS)和Azure Data Studio里,Query Store提供了一套内置报表,右键数据库 -> Query Store即可看到。这套报表包含四个核心视图:

  • Regressed Queries(回归查询):展示那些执行计划发生变化且性能下降的查询。这是排查性能回归的第一站。
  • Overall Resource Consumption(整体资源消耗):按时间维度展示CPU、持续时间、执行次数等指标的趋势图,帮你快速定位异常时间段。
  • Top Resource Consuming Queries(资源消耗Top查询):按CPU、持续时间、逻辑读写等维度排序,找出最耗资源的查询。
  • Queries With Forced Plans(强制计划的查询):展示所有被强制了执行计划的查询,方便你管理计划强制策略。

这四个视图我几乎每天都会用到。特别是"Regressed Queries",它直接告诉你"哪些查询变差了",省去了大量手动对比的时间。

4.2 用T-SQL直接查底层视图

内置报表虽然方便,但灵活性有限。真正做深度分析时,还是得直接写T-SQL查底层视图。下面这个查询是我常用的"查找最近24小时内性能下降最严重的查询":

-- 查找最近24小时内平均持续时间增长最快的查询 WITH RecentStats AS ( SELECT rs.plan_id, rs.runtime_stats_interval_id, rs.avg_duration, rs.count_executions, rsi.start_time, rsi.end_time, ROW_NUMBER() OVER (PARTITION BY rs.plan_id ORDER BY rsi.start_time DESC) AS rn FROM sys.query_store_runtime_stats rs JOIN sys.query_store_runtime_stats_interval rsi ON rs.runtime_stats_interval_id = rsi.runtime_stats_interval_id WHERE rsi.start_time >= DATEADD(HOUR, -24, GETUTCDATE()) ), PlanComparison AS ( SELECT plan_id, MAX(CASE WHEN rn = 1 THEN avg_duration END) AS latest_avg_duration, MAX(CASE WHEN rn = 2 THEN avg_duration END) AS previous_avg_duration FROM RecentStats GROUP BY plan_id ) SELECT q.query_id, qt.query_sql_text, p.plan_id, pc.latest_avg_duration, pc.previous_avg_duration, CASE WHEN pc.previous_avg_duration > 0 THEN (pc.latest_avg_duration - pc.previous_avg_duration) * 100.0 / pc.previous_avg_duration ELSE NULL END AS duration_change_pct FROM PlanComparison pc JOIN sys.query_store_plan p ON pc.plan_id = p.plan_id JOIN sys.query_store_query q ON p.query_id = q.query_id JOIN sys.query_store_query_text qt ON q.query_text_id = qt.query_text_id WHERE pc.previous_avg_duration > 0 AND pc.latest_avg_duration > pc.previous_avg_duration * 2 ORDER BY duration_change_pct DESC;

这个查询的逻辑是:取最近两个统计区间(通常是最近两小时),对比同一个计划的平均持续时间,找出那些"最新区间比前一个区间慢了一倍以上"的查询。这种查询能帮你快速锁定"刚刚变慢"的查询,而不是那些一直都很慢的。

4.3 等待统计:被低估的排查利器

很多人在用Query Store时只关注持续时间、CPU这些指标,忽略了sys.query_store_wait_stats。但等待统计往往是定位性能问题根因的关键。比如,一个查询的持续时间突然变长,但CPU时间没变,那大概率是在等锁或者等IO。这时候查一下等待统计,就能直接看到是LOCK_WAIT还是PAGEIOLATCH_SH。

-- 查看指定查询的等待类别分布 SELECT ws.wait_category_desc, SUM(ws.total_query_wait_time_ms) AS total_wait_ms, AVG(ws.avg_query_wait_time_ms) AS avg_wait_ms, SUM(ws.total_query_wait_time_ms) * 100.0 / SUM(SUM(ws.total_query_wait_time_ms)) OVER () AS wait_pct FROM sys.query_store_wait_stats ws JOIN sys.query_store_plan p ON ws.plan_id = p.plan_id WHERE p.query_id = @query_id GROUP BY ws.wait_category_desc ORDER BY total_wait_ms DESC;

这个查询会告诉你,某个查询的等待时间主要花在了哪里。如果wait_pct最高的是CPU,说明是计算密集型;如果是LOCK,说明有阻塞;如果是BUFFER_IO,说明在等数据页从磁盘读入内存。不同类别对应不同的优化方向,这个信息比单纯的"持续时间变长了"有价值得多。

5. 计划强制:一把需要谨慎使用的双刃剑

5.1 计划强制的适用场景

Query Store最强大的功能之一就是计划强制(Plan Forcing)。当你发现某个查询因为执行计划变更导致性能下降时,可以把它之前的"好计划"强制回去,让优化器不再重新编译选择新计划。这个功能在紧急故障处理时简直是救命稻草——你不需要改代码、不需要加Hint、不需要重启服务,一条命令就能让查询恢复性能。

但计划强制不是万能药。它本质上是一种"冻结"操作,把某个特定计划的执行方式固定下来。如果数据分布发生了根本性变化(比如表从1万行涨到了1亿行),那个被强制的"好计划"可能也不再是最优的了。所以我的原则是:计划强制用于应急和过渡,不作为长期方案。用了强制计划之后,一定要持续观察,找到根本原因后该改索引改索引,该改查询改查询。

-- 强制使用指定计划 EXEC sys.sp_query_store_force_plan @query_id = 123, @plan_id = 456; -- 取消计划强制 EXEC sys.sp_query_store_unforce_plan @query_id = 123, @plan_id = 456; -- 查看所有被强制的计划及其执行情况 SELECT q.query_id, qt.query_sql_text, p.plan_id, p.is_forced_plan, p.force_failure_count, p.last_force_failure_reason_desc FROM sys.query_store_plan p JOIN sys.query_store_query q ON p.query_id = q.query_id JOIN sys.query_store_query_text qt ON q.query_text_id = qt.query_text_id WHERE p.is_forced_plan = 1;

5.2 强制失败的常见原因

计划强制并不是100%成功的。有时候你强制了一个计划,但查询执行时并没有使用它,force_failure_count会增加。常见的失败原因包括:

  • 计划对应的对象被修改了:比如索引被删除或重建,导致原计划无法执行。
  • 统计信息发生了重大变化:优化器认为强制计划不再适用。
  • 计划包含临时对象:比如临时表,这些对象在下次执行时可能不存在。
  • 参数嗅探问题:强制计划是针对特定参数编译的,换了参数可能不适用。

当你发现force_failure_count大于0时,一定要查last_force_failure_reason_desc,它会告诉你具体原因。我遇到过好几次是因为索引重建导致强制计划失效,这种情况下重新强制一次通常就好了,但更好的做法是搞清楚为什么优化器不愿意用那个计划。

5.3 自动计划纠正:让Query Store自己干活

从SQL Server 2017开始,Query Store支持自动计划纠正(Automatic Plan Correction)。开启后,如果Query Store检测到某个查询的性能下降,并且找到了一个更好的历史计划,它会自动帮你强制那个好计划。这个功能对于无人值守的系统特别有用,相当于给数据库配了一个24小时值班的DBA。

-- 开启自动计划纠正 ALTER DATABASE [YourDatabaseName] SET AUTOMATIC_TUNING (FORCE_LAST_GOOD_PLAN = ON);

但自动纠正也有它的局限性。它只能在你已经捕获了"好计划"的前提下工作,如果好计划从来没被Query Store记录过,它就无能为力。另外,自动纠正的决策是基于历史统计的,如果业务模式发生了根本变化,它可能会做出错误的判断。所以我的建议是:开启自动纠正,但定期审查它的决策,看看它强制了哪些计划、效果如何。

6. 那些只有踩过坑才知道的Query Store运维经验

6.1 空间爆满导致只读模式的应急处理

前面提到过Query Store空间满了会进入只读模式,但真遇到的时候,很多人第一反应是"把MAX_STORAGE_SIZE_MB调大"。这个操作本身没错,但有个细节:在只读模式下,ALTER DATABASE SET QUERY_STORE命令可能会失败。正确的处理步骤是:

-- 第一步:尝试直接调整大小 ALTER DATABASE [YourDatabaseName] SET QUERY_STORE (MAX_STORAGE_SIZE_MB = 4096); -- 如果上面失败,先清理数据再调整 ALTER DATABASE [YourDatabaseName] SET QUERY_STORE CLEAR; -- 然后再调整大小 ALTER DATABASE [YourDatabaseName] SET QUERY_STORE (MAX_STORAGE_SIZE_MB = 4096);

注意SET QUERY_STORE CLEAR会清空所有历史数据,这是个不可逆操作。如果历史数据对你很重要,在执行之前先考虑能不能通过其他方式腾出空间,比如先缩小STALE_QUERY_THRESHOLD_DAYS让旧数据被自动清理。

6.2 高并发场景下的性能开销控制

Query Store在高并发场景下确实会带来额外的开销,主要体现在两个方面:一是内存中的聚合操作,二是定期刷写到磁盘的IO。如果你的系统QPS非常高(比如每秒上万次查询),默认的15分钟刷写间隔可能会导致内存中积压大量待刷写数据。

我一般会这样调整:把DATA_FLUSH_INTERVAL_SECONDS从默认的900秒降到300秒甚至更短,让数据更频繁地刷写到磁盘,减少内存压力。同时把QUERY_CAPTURE_MODE设为AUTO,避免捕获那些执行一次就再也不用的即席查询。如果还是觉得开销大,可以考虑把INTERVAL_LENGTH_MINUTES调大,减少统计区间的数量。

注意:调整这些参数时一定要在测试环境先验证,观察调整前后的CPU和IO变化。不要在生产环境直接改,尤其是高负载系统。

6.3 Query Store与索引维护的配合

Query Store记录的执行计划会随着索引的变化而变化。当你创建、删除或重建索引时,Query Store会捕获到新的计划。这本身是好事,但有个坑:如果你强制了某个计划,然后重建了索引,强制计划可能会失效。因为重建索引会改变对象的内部标识,原计划引用的对象可能已经不存在了。

我的做法是:在做索引维护之前,先查一下有哪些被强制的计划,维护完成后逐一验证这些计划是否还有效。如果失效了,根据实际情况决定是重新强制还是取消强制。这个检查花不了几分钟,但能避免"维护完索引反而性能下降"的尴尬。

-- 索引维护前检查被强制的计划 SELECT q.query_id, p.plan_id, p.is_forced_plan, qt.query_sql_text FROM sys.query_store_plan p JOIN sys.query_store_query q ON p.query_id = q.query_id JOIN sys.query_store_query_text qt ON q.query_text_id = qt.query_text_id WHERE p.is_forced_plan = 1;

6.4 跨数据库查询的捕获限制

Query Store是数据库级别的功能,它只能捕获在当前数据库上下文中执行的查询。如果你的应用有跨数据库查询(比如在A数据库里查询B数据库的表),Query Store只会记录在A数据库上执行的那部分,B数据库上的执行情况需要去B数据库的Query Store里查。这个限制在排查跨库查询性能问题时经常被忽略,导致"只看到一半的真相"。

另外,对于包含大量动态SQL的应用,Query Store捕获的是实际执行的SQL文本,而不是拼接前的模板。这意味着如果动态SQL拼接方式导致每条SQL文本都不同,Query Store会为每条都生成独立记录。这种情况下,建议在应用层做参数化改造,或者用sp_executesql代替直接拼接。

7. 从Query Store数据出发的性能优化闭环

7.1 建立"发现-分析-修复-验证"的完整链路

Query Store最大的价值不在于它记录了什么,而在于它能帮你建立一个完整的性能优化闭环。我通常的工作流程是这样的:

发现阶段:每天定时查一次"Regressed Queries"报表,或者用前面提到的T-SQL查询找出最近24小时内性能下降的查询。这一步的目标是"发现问题"。

分析阶段:对发现的问题查询,查它的执行计划变化、等待统计、运行时趋势。搞清楚"为什么变慢了"——是计划变了、数据量涨了、还是资源争用了。

修复阶段:根据分析结果采取行动。如果是计划问题,考虑计划强制或加Hint;如果是索引问题,考虑调整索引;如果是查询写法问题,考虑改写查询。

验证阶段:修复之后,继续用Query Store观察该查询的性能是否恢复。Query Store会记录修复前后的对比数据,这是验证修复效果最直接的证据。

这个闭环看起来简单,但真正坚持做下来,你会发现数据库的性能问题越来越少,因为很多问题在萌芽阶段就被发现了。

7.2 用Query Store数据驱动索引优化

Query Store的运行时统计数据可以直接用来指导索引优化。比如,你可以找出那些逻辑读写特别高的查询,这些查询往往是缺少合适索引的候选者。然后结合执行计划,看看是否有全表扫描或索引扫描可以优化为索引查找。

-- 找出逻辑读写最高的查询 SELECT TOP 20 q.query_id, qt.query_sql_text, SUM(rs.avg_logical_io_reads * rs.count_executions) AS total_logical_reads, SUM(rs.count_executions) AS total_executions, AVG(rs.avg_duration) AS avg_duration_us FROM sys.query_store_runtime_stats rs JOIN sys.query_store_plan p ON rs.plan_id = p.plan_id JOIN sys.query_store_query q ON p.query_id = q.query_id JOIN sys.query_store_query_text qt ON q.query_text_id = qt.query_text_id GROUP BY q.query_id, qt.query_sql_text ORDER BY total_logical_reads DESC;

这个查询的结果可以直接作为索引优化的输入。逻辑读写高的查询,要么是扫描了大量不必要的数据页,要么是缺少覆盖索引导致回表次数过多。结合执行计划一看便知。

7.3 容量规划中的Query Store应用

除了性能排查,Query Store的数据还可以用于容量规划。通过分析历史趋势,你可以回答这些问题:过去一个月CPU使用率的增长趋势如何?哪些查询的CPU消耗增长最快?如果业务量翻倍,哪些查询会最先成为瓶颈?

我通常会按月导出Query Store的汇总数据,做成趋势图。这样在业务方说"下个月要搞大促"的时候,我能拿出数据来说话:按照当前增长趋势,大促期间CPU会达到多少,需要提前做哪些优化。

-- 按月统计整体资源消耗趋势 SELECT DATEPART(YEAR, rsi.start_time) AS year, DATEPART(MONTH, rsi.start_time) AS month, SUM(rs.avg_cpu_time * rs.count_executions) / 1000000.0 AS total_cpu_seconds, SUM(rs.avg_duration * rs.count_executions) / 1000000.0 AS total_duration_seconds, SUM(rs.count_executions) AS total_executions FROM sys.query_store_runtime_stats rs JOIN sys.query_store_runtime_stats_interval rsi ON rs.runtime_stats_interval_id = rsi.runtime_stats_interval_id GROUP BY DATEPART(YEAR, rsi.start_time), DATEPART(MONTH, rsi.start_time) ORDER BY year, month;

这个查询给出的是按月汇总的CPU秒数、持续时间秒数和执行次数。把这些数据画成折线图,趋势一目了然。

7.4 把Query Store纳入日常巡检清单

最后分享一个我自己的习惯:把Query Store的关键指标纳入日常巡检清单。每天早上花五分钟检查以下几项:

  • Query Store的actual_state_desc是否为READ_WRITE,空间使用率是否超过80%。
  • 过去24小时是否有新的性能回归查询。
  • 被强制的计划是否有force_failure_count增加的情况。
  • 等待统计中是否有异常的等待类别突增。

这五项检查加起来不到五分钟,但能帮你把绝大多数性能问题扼杀在萌芽阶段。比起等到用户投诉再手忙脚乱地排查,这五分钟的投入产出比高得离谱。

Query Store不是银弹,它不能自动帮你解决所有性能问题,但它给了你一双"看透历史"的眼睛。有了这双眼睛,你不再需要靠猜、靠碰运气来排查问题,而是可以基于数据做出判断。这才是它真正的价值所在。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询