Query Store 不捕获子查询为独立 query_id,仅记录外层语句;子查询性能问题需通过执行计划变更、等待统计突变(sys.query_store_wait_stats)及运行时指标(avg_duration、avg_logical_reads)对比定位。

查不到子查询的独立执行计划?先确认 Query Store 是否捕获了嵌套层级
Query Store 默认不会把子查询单独拎出来存为一个 query_id,它只对“顶层 SELECT/INSERT/UPDATE/DELETE 语句”生成记录。也就是说,哪怕你写了一个三层嵌套的 SELECT ... WHERE x IN (SELECT ... WHERE y IN (SELECT ...)),Query Store 里也只有一条对应最外层语句的 query_id,所有子查询都压缩在同一个 plan_id 的 XML 执行计划里。
所以别指望在 sys.query_store_query 中搜到子查询文本——它压根不作为独立查询注册。真正能定位子查询性能退化的地方,是看外层语句的执行计划变更 + 等待统计突变。
- 用
sys.query_store_plan查该query_id下不同时间点的plan_id,对比 plan_xml 中的 Nested Loops / Index Spool / Table Scan 出现位置和预估行数 - 重点看
sys.query_store_runtime_stats里同一query_id在不同plan_id下的avg_duration和avg_logi_reads是否跳变 - 如果子查询涉及参数(比如
WHERE id = @p),还要检查是否触发了参数敏感计划(Parameter Sensitive Plan),这时可能有多个plan_id对应同一query_id,但各自绑定不同参数值桶
子查询变慢却没换执行计划?盯紧 Query Store 的 wait stats 聚合
Azure SQL 和 SQL Server 2022 的 Query Store 支持按小时聚合等待类型,这对抓子查询退化特别有用。因为很多子查询卡顿不是计划变了,而是底层数据分布偏移、统计信息陈旧或并发资源争抢导致的等待飙升。
比如一个子查询 (SELECT TOP 1 name FROM users WHERE status = 1 ORDER BY created DESC),原本毫秒级返回,某天突然变 2s——但执行计划完全一样。这时候查 sys.query_store_wait_stats,很可能发现 PAGEIOLATCH_SH 或 LCK_M_S 平均等待时间从 0.3ms 涨到 420ms。
- 必须关联
sys.query_store_runtime_stats_interval看时间窗口,避免把白天高峰和夜间低峰混在一起平均 -
wait_category_desc字段比原始wait_type更实用,比如把ASYNC_NETWORK_IO和NETWORK_IO归到 “Network I/O” 类别下 - SQL Server 2022 开始,
query_store_wait_stats支持更细粒度的 wait_type 分组,但注意HADR_SYNC_COMMIT这类等待只在启用异地读副本时才真实反映子查询写依赖
想强制子查询走特定计划?QUERYTRACEON 是唯一可控入口
SQL Server 不允许对子查询单独加 USE PLAN 或 OPTIMIZE FOR,但你可以用查询级跟踪标志,在外层语句中为子查询部分“打补丁”。典型场景是子查询因参数嗅探选错索引,而你又不能改应用代码。
例如,外层查询是 SELECT * FROM orders WHERE cust_id IN (SELECT cust_id FROM customers WHERE region = @r),子查询本该走 IX_customers_region 却走了全表扫描。这时可在语句末尾加:
OPTION (QUERYTRACEON 4137)
这个标志会禁用参数嗅探,让优化器对子查询使用平均行估算而非当前参数值。其他常用组合:
-
QUERYTRACEON 2312:强制用新 Cardinality Estimator(CE),对含复杂 JOIN 的子查询更友好 -
QUERYTRACEON 9481:切回旧 CE,适合老系统中因 CE 升级导致子查询估算崩坏的情况 - 多个标志可叠加:
OPTION (QUERYTRACEON 4137, QUERYTRACEON 2312)
注意:QUERYTRACEON 只影响当前查询,不污染缓存,也不需要 DBA 权限(只要用户有查询权限即可)。
升级到 SQL Server 2022 后子查询监控反而更难?别漏掉并行缓冲扫描的副作用
SQL Server 2022 的并行缓冲池扫描(Parallel Buffer Pool Scan)确实大幅缩短了大内存实例重启后的首次查询延迟,但它会让 Query Store 的早期运行统计失真——尤其是刚重启后头几分钟内,大量子查询的 avg_duration 会被拉高,因为物理 IO 还没稳定,缓存未热,而 Query Store 按固定时间窗口聚合,容易把这波毛刺记成“持续退化”。
这不是 bug,是设计使然。并行扫描本身不改变子查询逻辑,但会影响其第一次执行的 IO 延迟表现。
- 判断是否受此影响:查
sys.dm_os_buffer_descriptors中is_modified = 0的页占比,若低于 60%,说明缓存还在灌入中 - 绕过误报:在 Query Store 报警逻辑里,对重启后 15 分钟内的
runtime_stats_interval加白名单过滤 - 真正要警惕的是:重启一小时后,同一子查询的
avg_logi_reads仍比基线高 3 倍以上——那大概率是统计信息没更新,而不是缓冲扫描问题










