At this point, it is time to start looking at the query activity on this particular database, using the query shown in Listing 37.
LISTING 37: Top cached queries by execution count
-- Top cached queries by Execution Count (SQL Server 2012)
SELECT qs.execution_count, qs.total_rows, qs.last_rows, qs.min_rows, qs.max_rows,
qs.last_elapsed_time, qs.min_elapsed_time, qs.max_elapsed_time,
SUBSTRING(qt.TEXT,qs.statement_start_offset/2 +1,
(CASE WHEN qs.statement_end_offset = -1
THEN LEN(CONVERT(NVARCHAR(MAX), qt.TEXT)) * 2
ELSE qs.statement_end_offset END - qs.statement_start_offset)/2)
AS query_text
FROM sys.dm_exec_query_stats AS qs WITH (NOLOCK)
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS qt
ORDER BY qs.execution_count DESC OPTION (RECOMPILE);
-- Uses several new rows returned columns
-- to help troubleshoot performance problems
This query simply returns cached queries ordered
by execution count. This is useful for getting an idea about your
typical workload. Knowing which queries are executed the most often,
along with their range of execution times and range of rows returned
can be very useful information. Having the query text available enables
you to run a query in SSMS, where you can look at the execution plan
and the I/O statistics to determine whether there are any tuning
opportunities.
Next, you will take a look at a similar query that focuses on cached stored procedures. This query is shown in Listing 38.
LISTING 38: Top cached stored procedures by execution count
-- Top Cached SPs By Execution Count (SQL Server 2012)
SELECT TOP(250) p.name AS [SP Name], qs.execution_count,
ISNULL(qs.execution_count/DATEDIFF(Second, qs.cached_time, GETDATE()), 0)
AS [Calls/Second],
qs.total_worker_time/qs.execution_count AS [AvgWorkerTime],
qs.total_worker_time AS [TotalWorkerTime],qs.total_elapsed_time,
qs.total_elapsed_time/qs.execution_count AS [avg_elapsed_time],
qs.cached_time
FROM sys.procedures AS p WITH (NOLOCK)
INNER JOIN sys.dm_exec_procedure_stats AS qs WITH (NOLOCK)
ON p.[object_id] = qs.[object_id]
WHERE qs.database_id= DB_ID()
ORDER BY qs.execution_count DESC OPTION (RECOMPILE);
-- Tells you which cached stored procedures are called the most often
-- This helps you characterize and baseline your workload
This query returns the top cached stored
procedures ordered by execution count. It can help you determine which
stored procedures are called most often, and how often they are called.
Knowing this is very helpful for baseline purposes. For example, if you
know that your most frequently executed stored procedure is normally
called 50 times/second, but you later see it being called 300
times/second, you would want to start investigating. Perhaps there was
a change to your application(s) that is causing them to call that
stored procedure much more often. It might be by design, or it might be
a defect in the application, introduced in a recent update. It is also
possible that your applications are suddenly seeing more load, either
from legitimate users or from a denial-of-service attack.
One thing to keep in mind when examining all
these queries that are looking at cached stored procedures is that the
stored procedures may have gone into the cache at different times,
which skews the numbers for these queries. You should always look at
the cached_time in the query results (see Listing 39) to see how long the stored procedure has been in the cache.
LISTING 39: Top cached stored procedures by average elapsed time
-- Top Cached SPs By Avg Elapsed Time (SQL Server 2012)
SELECT TOP(25) p.name AS [SP Name], qs.total_elapsed_time/qs.execution_count
AS [avg_elapsed_time], qs.total_elapsed_time, qs.execution_count,
ISNULL(qs.execution_count/DATEDIFF(Second, qs.cached_time,
GETDATE()), 0) AS [Calls/Second],
qs.total_worker_time/qs.execution_count AS [AvgWorkerTime],
qs.total_worker_time AS [TotalWorkerTime], qs.cached_time
FROM sys.procedures AS p WITH (NOLOCK)
INNER JOIN sys.dm_exec_procedure_stats AS qs WITH (NOLOCK)
ON p.[object_id] = qs.[object_id]
WHERE qs.database_id= DB_ID()
ORDER BY avg_elapsed_time DESC OPTION (RECOMPILE);
-- This helps you find long-running cached stored procedures that
-- may be easy to optimize with standard query tuning techniques
This query captures the top cached stored
procedures by average elapsed time. This is useful because it can
highlight long-running (on average) stored procedures that might be
very good tuning candidates. If you can make some changes to a stored
procedure that previously took 90 seconds that result in it returning
in 5 seconds, you will look like a magician to your boss. Conversely,
if a long-running query is executed only once a day, optimizing it will
not really help your overall workload as much as you might expect. This
query is somewhat less sensitive to the cached_time issue, as you are sorting by average elapsed time.
Next, you are going to look at the most expensive stored procedures from an overall CPU perspective, with the query shown in Listing 40.
LISTING 40: Top cached stored procedures by total worker time
-- Top Cached SPs By Total Worker time (SQL Server 2012).
-- Worker time relates to CPU cost
SELECT TOP(25) p.name AS [SP Name], qs.total_worker_time AS [TotalWorkerTime],
qs.total_worker_time/qs.execution_count AS [AvgWorkerTime], qs.execution_count,
ISNULL(qs.execution_count/DATEDIFF(Second, qs.cached_time, GETDATE()), 0)
AS [Calls/Second],qs.total_elapsed_time, qs.total_elapsed_time/qs.execution_count
AS [avg_elapsed_time], qs.cached_time
FROM sys.procedures AS p WITH (NOLOCK)
INNER JOIN sys.dm_exec_procedure_stats AS qs WITH (NOLOCK)
ON p.[object_id] = qs.[object_id]
WHERE qs.database_id= DB_ID()
ORDER BY qs.total_worker_time DESC OPTION (RECOMPILE);
-- This helps you find the most expensive cached
-- stored procedures from a CPU perspective
-- You should look at this if you see signs of CPU pressure
This query returns the top cached stored
procedures ordered by total worker time. Worker time relates to the CPU
cost of a query or stored procedure. Especially if you see signs of CPU
pressure from looking at your top cumulative wait types or your CPU
utilization history, you should look very closely at the results of
this query to figure out which stored procedures were the most
expensive from a CPU perspective. The reason you sort by total worker
time is because this takes into account the total CPU cost of the
stored procedure since it has been cached. You might have a stored
procedure that is not particularly expensive for a single execution
that is called very frequently, resulting in a very high overall CPU
cost.
With this query, you do need to pay particular attention to the cached_time
column for each stored procedure. The length of time that a stored
procedure has been in the cache can have a huge effect on its
cumulative cost figures. That’s why I like to periodically clear out
the procedure cache so that the stored procedures that are part of my
regular workload will be recompiled and go back into the cache at
nearly the same time. This makes it much easier to accurately interpret
the results of all these stored procedure cost queries. It also has the
benefit of clearing out single-use, ad hoc query plans that may be
wasting a lot of space in your cache.