典型流程1. 用户反馈接口慢 2. 用 Query Store 查最近 1 小时总耗时最高 SQL 3. 找到对应 SQL 4. 打开实际执行计划 5. 看慢在哪里扫描、回表、排序、哈希、估计偏差 6. 调整 SQL 或索引 7. 用 STATISTICS IO/TIME 对比优化前后 8. 再看 Query Store 后续表现是否下降我们可以打开SSMS直接执行这个SQL模板定位慢SQL是哪一条。SELECT TOP (20) LEFT(qt.query_sql_text, 500) AS [SQL文本], rs.count_executions AS [执行次数], CONVERT(decimal(18,2), rs.avg_duration / 1000.0) AS [平均耗时_毫秒], CONVERT(decimal(18,2), (rs.avg_duration * rs.count_executions) / 1000.0) AS [总耗时_毫秒], CONVERT(decimal(18,2), rs.avg_cpu_time / 1000.0) AS [平均CPU_毫秒], CONVERT(decimal(18,2), rs.avg_logical_io_reads) AS [平均逻辑读取次数], rsi.start_time AS [统计开始时间], rsi.end_time AS [统计结束时间], p.plan_id AS [执行计划ID] FROM sys.query_store_query_text qt JOIN sys.query_store_query q ON q.query_text_id qt.query_text_id JOIN sys.query_store_plan p ON p.query_id q.query_id JOIN sys.query_store_runtime_stats rs ON rs.plan_id p.plan_id JOIN sys.query_store_runtime_stats_interval rsi ON rsi.runtime_stats_interval_id rs.runtime_stats_interval_id WHERE rsi.start_time DATEADD(hour, -1, SYSUTCDATETIME()) ORDER BY rs.avg_duration * rs.count_executions DESC;这个可以作为Query Store 慢 SQL 定位通用模板。它的作用是查看最近 1 小时内总耗时最高的前 20 条 SQL。排序依据是平均耗时 × 执行次数 总耗时所以它不是只找“单次最慢”的 SQL而是找“对系统整体影响最大”的 SQL。我们直接执行这个sql模板本次 Query Store 结果Query Store 查到 1 条主要 SQLSELECT TOP (100) p.ProductId, p.ProductName, SUM(oi.LineAmount) AS Revenue FROM dbo.Products p JOIN dbo.OrderItems oi ON oi.ProductId p.ProductId GROUP BY p.ProductId, p.ProductName ORDER BY Revenue DESC对应结果执行次数1 平均耗时25191.88 ms 总耗时25191.88 ms 平均 CPU188.33 ms 平均逻辑读取次数594 统计时间2026-07-08 07:30:00 到 07:45:00 UTC 执行计划 ID31这条 SQL 的业务含义这条 SQL 是商品销售额排行榜从订单明细 OrderItems 中统计每个商品的销售额 再关联 Products 拿商品名称 按销售额从高到低排序 取前 100 个商品。它代表一种常见报表场景商品销售排行 聚合统计 JOIN 查询 ORDER BY 排序 TOP N 排行榜为什么它会出现在慢 SQL 里这条 SQL 天然比普通查询更重因为它需要读取 OrderItems 订单明细 按 ProductId 分组 计算 SUM(LineAmount) JOIN Products 获取商品名称 按 Revenue 排序 取 TOP 100它不是按主键查一行而是典型的统计报表 SQL。本次结果里最明显的是平均耗时 25191.88 ms也就是单次执行约 25 秒。但同时平均 CPU 188.33 ms 平均逻辑读取次数 594CPU 和逻辑读取并不算特别夸张说明这次 25 秒耗时可能还受到其他因素影响例如等待 阻塞 磁盘读取 首次执行缓存未命中 客户端接收结果 系统负载所以不能只看平均耗时还要结合执行计划 等待信息 STATISTICS IO/TIME 是否被阻塞 是否首次执行怎么分析这条 SQL第一步看主要表OrderItems 是核心表因为销售额来自 SUM(oi.LineAmount) Products 是维表用来取 ProductName第二步看主要操作JOIN GROUP BY SUM ORDER BY TOP第三步看执行计划里有没有OrderItems 扫描 Hash Match / 哈希匹配 Stream Aggregate / 流聚合 Sort / 排序 Index Scan / 索引扫描第四步看消息里的读取OrderItems 逻辑读取次数 Products 逻辑读取次数 CPU 时间 占用时间可能的优化方向如果这是高频接口或报表优化方向通常不是只靠一个索引。可以考虑1. 给 OrderItems(ProductId) INCLUDE(LineAmount) 建覆盖索引 2. 给 Products(ProductId) 保持主键索引 3. 增加时间条件比如只统计最近 30 天 4. 把销售额提前汇总到日报/月报表 5. 对排行榜结果做缓存 6. 避免每次请求都实时扫订单明细做全量聚合示例索引CREATE INDEX IX_OrderItems_ProductId_LineAmount ON dbo.OrderItems(ProductId) INCLUDE (LineAmount);但如果数据量很大更推荐预聚合表 / 汇总表 / 缓存例如维护一张ProductSalesSummary里面提前保存ProductId TotalRevenue SoldQuantity 统计日期查询排行榜时直接查汇总表而不是每次实时扫OrderItems。本次学习结论Query Store 可以帮助我们从历史执行记录中找出最值得关注的 SQL。 本次定位到的是商品销售额排行榜 SQL。 它慢的原因不是简单的 WHERE 查询慢而是典型的报表聚合JOIN GROUP BY SUM ORDER BY。 对于这种 SQL要重点看 OrderItems 的读取、聚合和排序成本。 如果是生产高频场景优先考虑覆盖索引、时间范围过滤、预聚合和缓存。一句话总结这个 Query Store 模板用于先找到“最影响系统的 SQL”然后再结合执行计划和 STATISTICS IO/TIME另外关于执行后可能出现没有任何结果的情况如果最近 1 小时没有 Query Store 记录或者 Query Store 还没刷新统计就会没有结果。先按这个顺序排查。1. 先确认 Query Store 是否正常SELECT actual_state_desc AS [当前状态], readonly_reason AS [只读原因], current_storage_size_mb AS [当前占用MB], max_storage_size_mb AS [最大空间MB] FROM sys.database_query_store_options;正常希望看到当前状态 READ_WRITE原因找到了你的 Query Store 当前是关的。当前状态 OFF所以你执行 Query Store 模板没有结果是正常的因为数据库根本没有记录查询历史。只读原因 0说明不是空间满或只读问题就是单纯没开启。开启 Query Store在当前数据库执行ALTER DATABASE CURRENT SET QUERY_STORE ON;然后建议设置成可读写ALTER DATABASE CURRENT SET QUERY_STORE ( OPERATION_MODE READ_WRITE );开启后验证SELECT actual_state_desc AS [当前状态], readonly_reason AS [只读原因], current_storage_size_mb AS [当前占用MB], max_storage_size_mb AS [最大空间MB] FROM sys.database_query_store_options;你希望看到当前状态 READ_WRITE