用sys.dm_exec_query_stats与sys.dm_exec_sql_text联查,按total_elapsed_time或avg_logical_reads排序可快速定位慢查询,但需注意快照时效性、权限限制及即席sql无库名问题。

直接结论:用 sys.dm_exec_query_stats + sys.dm_exec_sql_text 联查,按 total_elapsed_time 或 avg_logical_reads 排序,能快速定位真实慢查询;但必须注意快照时效性、权限限制和即席SQL无库名的问题。
查最耗时的20条查询(sys.dm_exec_query_stats 基础用法)
这是最常用也最有效的起点。该视图缓存的是已执行语句的聚合统计,不是实时运行中的会话,所以它反映的是“历史累计开销”,而非当前卡住的语句。
常见错误现象:直接 SELECT TOP 10 * 不加 ORDER BY,结果毫无意义;或只看 execution_count 忽略单次耗时,把高频轻量查询误判为瓶颈。
实操建议:
- 始终用
ORDER BY total_elapsed_time DESC或avg_logical_reads DESC——前者找总耗时大户,后者找I/O压力源 - 加
WHERE last_execution_time > DATEADD(minute, -60, GETDATE())限定最近一小时,避免被冷数据干扰 - 用
CONVERT(varchar(20), qs.last_execution_time, 120)格式化时间,方便人工识别 - 注意
plan_handle和sql_handle是二进制值,不能直接读,需后续关联sys.dm_exec_sql_text
还原完整SQL文本并关联数据库名(sys.dm_exec_sql_text 和 sys.dm_exec_query_plan)
仅靠 sys.dm_exec_query_stats 只能看到哈希值和统计,真正要分析,必须还原出原始SQL和执行计划。
使用场景:你想确认某条高 total_logical_reads 的语句是不是写了 SELECT *、有没有隐式转换、是否触发了表扫描。
实操建议:
- 用
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle)获取文本,别用OUTER APPLY——缺失sql_handle的行(如某些系统内部语句)会导致整行丢失 - 对即席SQL(ad-hoc),
DB_NAME(st.dbid)往往返回NULL,这时得结合qs.plan_handle查sys.dm_exec_query_plan中的dbid字段,或从语句文本里 grep 库名(如FROM Sales.Orders) - 如果只需要执行计划XML,加
CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle),但注意大计划可能超长,建议用CONVERT(xml, qp.query_plan)配合 SSMS 的“以XML方式查看”功能
区分“慢一次”和“持续慢”——做两次快照取差值
sys.dm_exec_query_stats 是累积值,重启服务或清除缓存后归零。如果想判断“过去15分钟内哪些查询突然变慢了”,不能只看当前最大值。
容易踩的坑:直接拿当前TOP 10当“最近慢查询”,结果发现其中7条是凌晨3点跑的备份作业,和白天业务完全无关。
实操建议:
- 在问题发生前/后各执行一次快照插入临时表,例如
SELECT *, GETDATE() AS snap_time INTO #snap1 FROM sys.dm_exec_query_stats - 两表
JOIN ON plan_handle = plan_handle,计算total_elapsed_time差值,再排序 - 关键过滤条件:
qs1.last_execution_time BETWEEN @start AND @end,且qs2.last_execution_time > qs1.last_execution_time,避免跨快照误匹配 - 这个方法对短周期突发问题(如某报表每小时跑一次,但今天卡了)特别有效
权限与可见性限制(你查不到的那些查询)
即使你是 sysadmin,某些查询仍不会出现在 sys.dm_exec_query_stats 中——这不是bug,是设计行为。
性能 / 兼容性影响:低权限用户只能看到自己会话的缓存计划;而即席SQL、参数化不彻底的语句、或用 sp_executesql 但未重用计划的,可能高频生成新 plan_handle,导致统计分散、单条不显眼。
实操建议:
- 检查是否启用“强制参数化”:
SELECT is_parameterization_forced FROM sys.databases WHERE name = DB_NAME(),未开启时,WHERE id = 1和WHERE id = 2被视为两条不同语句 - 对应用层用ORM(如EF)的系统,优先查
sys.dm_exec_requests+sys.dm_exec_sessions,抓正在运行的慢语句,再反推其来源 - 若发现大量
NULL在database_id列,说明即席SQL泛滥,应推动应用改用存储过程或预编译语句
真正难的不是找出哪条SQL慢,而是判断它为什么慢——执行计划里有没有警告图标、是否用了非SARGable谓词、统计信息是否过期。DMV只给你线索,不替你推理;而那个 NULL 的 DatabaseName,往往就是业务逻辑散落在各处、缺乏统一治理的第一个信号。











