Takeaway: This post is for me. This post is a handy place for me to put some queries that I use often. If you find them useful too, that’s wonderful, bookmark this page with https://michaeljswart.com/go/Top20.
These queries will give the top 20 resource consumers for cached queries based on a few different metrics. I’ve posted queries like this before, and others have written many other versions of this query. All these queries are based on sys.dm_exec_query_stats.
But I’ve tweaked my own queries recently based on a couple things I’ve learned. So you could say that I gave my old queries a new coat of paint.
Here what I’ve added to these queries recently:
- I added OPTION (RECOMPILE) to the query. It prevents these queries from showing up in their own results on quiet systems.
- Joe Sack pointed out that query_hash can be used to aggregate queries that only differ by literal values. I’m doing that here. What’s extra awesome about this technique, is that it can also be used to aggregate the same queries that were executed in different procedures or in different databases. This is critical if we want to measure the impact of a single query on a server regardless of where the query is called.
- I’m ignoring (for now) those queries with query_hash equal to 0x0. Again, Joe Sack points out that these are cursor fetches.
- You may notice that the database name is sometimes null. It’s null when the query is not part of a stored procedure. I suppose if I was keen, I could pick out a arbitrary database name from the accompanying query plan.
- Update September 27, 2013: Actually, In the comment section of this post, Ian Stirk gave me a really nice way to retrieve the database name from the dmvs. I’ve updated the queries below accordingly.
- Remember that the stats found in sys.dm_exec_query_stats are only as good as what’s in cache. The cache is not a permanent store. It changes and it gets reset every server restart.
(Download all queries)
Or check them out individually below:
Top 20 Executed Queries
These are queries that run often. Frequent queries can be vulnerable to concurrency problems.
;with frequent_queries as ( select top 20 query_hash, sum(execution_count) executions from sys.dm_exec_query_stats where query_hash <> 0x0 group by query_hash order by sum(execution_count) desc ) select @@servername as server_name, coalesce(db_name(st.dbid), db_name(cast(pa.value AS INT)), 'Resource') AS [DatabaseName], coalesce(object_name(st.objectid, st.dbid), '<none>') as [object_name], qs.query_hash, qs.execution_count, executions as total_executions_for_query, SUBSTRING(st.[text],(qs.statement_start_offset + 2) / 2, (CASE WHEN qs.statement_end_offset = -1 THEN LEN(CONVERT(NVARCHAR(MAX), st.text)) * 2 ELSE qs.statement_end_offset + 2 END - qs.statement_start_offset) / 2) as sql_text, qp.query_plan from sys.dm_exec_query_stats qs join frequent_queries fq on fq.query_hash = qs.query_hash cross apply sys.dm_exec_sql_text(qs.sql_handle) st cross apply sys.dm_exec_query_plan (qs.plan_handle) qp outer apply sys.dm_exec_plan_attributes(qs.plan_handle) pa where pa.attribute = 'dbid' order by fq.executions desc, fq.query_hash, qs.execution_count desc option (recompile) |
Top 20 I/O Consumers
Specifically logical reads and writes. Still my favorite metric.
;with high_io_queries as ( select top 20 query_hash, sum(total_logical_reads + total_logical_writes) io from sys.dm_exec_query_stats where query_hash <> 0x0 group by query_hash order by sum(total_logical_reads + total_logical_writes) desc ) select @@servername as servername, coalesce(db_name(st.dbid), db_name(cast(pa.value AS INT)), 'Resource') AS [DatabaseName], coalesce(object_name(st.objectid, st.dbid), '<none>') as [object_name], qs.query_hash, qs.total_logical_reads + total_logical_writes as total_io, qs.execution_count, cast((total_logical_reads + total_logical_writes) / (execution_count + 0.0) as money) as average_io, io as total_io_for_query, SUBSTRING(st.[text],(qs.statement_start_offset + 2) / 2, (CASE WHEN qs.statement_end_offset = -1 THEN LEN(CONVERT(NVARCHAR(MAX),st.[text])) * 2 ELSE qs.statement_end_offset + 2 END - qs.statement_start_offset) / 2) as sql_text, qp.query_plan from sys.dm_exec_query_stats qs join high_io_queries fq on fq.query_hash = qs.query_hash cross apply sys.dm_exec_sql_text(qs.sql_handle) st cross apply sys.dm_exec_query_plan (qs.plan_handle) qp outer apply sys.dm_exec_plan_attributes(qs.plan_handle) pa where pa.attribute = 'dbid' order by fq.io desc, fq.query_hash, qs.total_logical_reads + total_logical_writes desc option (recompile) |
Top 20 CPU Consumers
Another popular metric for measuring work done.
;with high_cpu_queries as ( select top 20 query_hash, sum(total_worker_time) cpuTime from sys.dm_exec_query_stats where query_hash <> 0x0 group by query_hash order by sum(total_worker_time) desc ) select @@servername as server_name, coalesce(db_name(st.dbid), db_name(cast(pa.value AS INT)), 'Resource') AS [DatabaseName], coalesce(object_name(st.objectid, st.dbid), '<none>') as [object_name], qs.query_hash, qs.total_worker_time as cpu_time, qs.execution_count, cast(total_worker_time / (execution_count + 0.0) as money) as average_CPU_in_microseconds, cpuTime as total_cpu_for_query, SUBSTRING(st.[text],(qs.statement_start_offset + 2) / 2, (CASE WHEN qs.statement_end_offset = -1 THEN LEN(CONVERT(NVARCHAR(MAX),st.[text])) * 2 ELSE qs.statement_end_offset + 2 END - qs.statement_start_offset) / 2) as sql_text, qp.query_plan from sys.dm_exec_query_stats qs join high_cpu_queries hcq on hcq.query_hash = qs.query_hash cross apply sys.dm_exec_sql_text(qs.sql_handle) st cross apply sys.dm_exec_query_plan (qs.plan_handle) qp outer apply sys.dm_exec_plan_attributes(qs.plan_handle) pa where pa.attribute = 'dbid' order by hcq.cpuTime desc, hcq.query_hash, qs.total_worker_time desc option (recompile) |
Top 20 Queries By Elapsed Time
Results found here, but not in the results of the other queries, usually suffer from things like excessive blocking or ASYNC_NETWORK_IO.
;with long_queries as ( select top 20 query_hash, sum(total_elapsed_time) elapsed_time from sys.dm_exec_query_stats where query_hash <> 0x0 group by query_hash order by sum(total_elapsed_time) desc ) select @@servername as server_name, coalesce(db_name(st.dbid), db_name(cast(pa.value AS INT)), 'Resource') AS [DatabaseName], coalesce(object_name(st.objectid, st.dbid), '<none>') as [object_name], qs.query_hash, qs.total_elapsed_time, qs.execution_count, cast(total_elapsed_time / (execution_count + 0.0) as money) as average_duration_in_microseconds, elapsed_time as total_elapsed_time_for_query, SUBSTRING(st.[text],(qs.statement_start_offset + 2) / 2, (CASE WHEN qs.statement_end_offset = -1 THEN LEN(CONVERT(NVARCHAR(MAX),st.[text])) * 2 ELSE qs.statement_end_offset + 2 END - qs.statement_start_offset) / 2) as sql_text, qp.query_plan from sys.dm_exec_query_stats qs join long_queries lq on lq.query_hash = qs.query_hash cross apply sys.dm_exec_sql_text(qs.sql_handle) st cross apply sys.dm_exec_query_plan (qs.plan_handle) qp outer apply sys.dm_exec_plan_attributes(qs.plan_handle) pa where pa.attribute = 'dbid' order by lq.elapsed_time desc, lq.query_hash, qs.total_elapsed_time desc option (recompile) |
Using These Queries
By the way. This post seems to be really popular. So I waive any copyright I have on these four queries. Copy them without attribution wherever you like. Profit if you can. Go nuts.