Monitoring the Plan Cache
You can view and get
information about the query plans currently in plan cache memory by
using some of the DMVs available in SQL Server 2008. Following are some
of the useful ones related to monitoring the plan cache:
sys.dm_exec_cached_plans— Returns general information about the query execution plans currently in the plan cache.
sys.dm_exec_query_stats— Returns aggregate performance statistics for cached query plans.
sys.dm_exec_sql_text— Returns the text of the SQL statement for a specified plan handle.
sys.dm_exec_cached_plan_dependent_objects— Returns one row for every dependent object of a compiled plan.
sys.dm_exec_plan_attributes— Returns one row per attribute associated with the plan for a specified plan handle.
sys.dm_exec_cached_plans
The sys.dm_exec_cached_plans
DMV provides information on all the execution plans currently in the
plan cache. Because the cache can have a large number of plans, you
usually want to limit the results returned from sys.dm_exec_cached_plans by using a filter on the cacheobjtype column and also using the TOP clause. For example, the query shown in Listing 1
returns the top 10 compiled plans currently in the plan cache, sorted
in descending order by the number of times the plan has been reused (usecounts).
Listing 1. Returning the Top 10 Compiled Plans, by Usage Count
select top 10 objtype, usecounts, size_in_bytes, plan_handle from sys.dm_exec_cached_plans where cacheobjtype = 'Compiled Plan' order by usecounts desc go
objtype usecounts size_in_bytes plan_handle --------- --------- ------------- -------------------------------------------------- Prepared 127 65536 0x06000100962E9C11B820A207000000000000000000000000 Adhoc 110 49152 0x06000100804AD300B8E02D0C000000000000000000000000 Adhoc 40 16384 0x060001006CC40F18B860D80A000000000000000000000000 Adhoc 26 8192 0x0600040023900901B820A106000000000000000000000000 Adhoc 26 8192 0x060004003E77102CB8E0A306000000000000000000000000 Proc 17 8192 0x05000400F578A275B8405F07000000000000000000000000 Adhoc 17 8192 0x06000400EBC44D2AB880A006000000000000000000000000 Adhoc 15 8192 0x060001001AF2320BB8801A08000000000000000000000000 Proc 12 212992 0x05000400744F1F67B8604F0E000000000000000000000000 Proc 12 49152 0x050004006A934A11B8C0550E000000000000000000000000
|
The types of plans in the plan cache are listed under the cacheobjtype column and can be any of the following:
Compiled Plan— The actual compiled plan generated that can be shared by sessions running the same procedure or query.
Compiled Plan Stub—
A small, compiled plan stub generated when a batch is compiled for the
first time and the Optimize for Ad Hoc Workloads option is enabled. It
helps to relieve memory pressure by not allowing the plan cache to
become filled with compiled plans that are not reused.
Executable Plan—
The actual execution plan and the environment settings for the session
that ran the compiled plan. Caching the environment settings for an
execution plan makes subsequent executions more efficient. Each
concurrent execution of the same compiled plan will have its own
executable plan. All executable plans are associated with a compiled
plan having the same plan_handle, but not all compiled plans have an associated executable plan.
Parse Tree— The internal parsed form of a query generated before compilation and optimization.
CLR Compiled Func— Execution plan for a CLR-based function.
CLR Compiled Proc— Execution plan for a CLR-based procedure.
Extended proc— The cached information for an extended stored procedure.
The type of object or query for which a plan is cached is stored in the objtype column. This column can contain one of the following values:
Proc— The cached plan is for a stored procedure or inline function.
Prepared— The cached plan is for queries submitted using sp_executesql or for queries using the prepare and execute method.
Adhoc— The cached plan is for queries that don’t fall into any other category.
ReplProc— The cached plan is for replication agents.
Trigger— The cached plan is for a trigger.
View—
The cached plan is for a view or a noninline function. You typically
see a parse tree only for a view or noninline function, not a compiled
plan. The view or function typically does not have its own separate plan
because it is expanded as part of another query.
UsrTab or SysTab— The cached plan is for a user or system table that has computed columns. This is typically associated with a parse tree.
Default, Check, or Rule—
The cached plan is simply a parse tree for these types of objects
because they are expanded as part of another query in which they are
applied.
To determine how often a plan is being reused, you can examine the value in the usecounts columns. The usecounts value is incremented each time the cached plan is looked up and reused.
sys.dm_exec_sql_text
Overall, the information returned by sys.dm_exec_cached_plans
is not overly useful unless you know what queries or stored procedures
these plans refer to. You can view the SQL text of these query plans by
writing a query that joins sys.dm_exec_cached_plans with the sys.dm_exec_sql_text DMV. For example, you can use the query shown in Listing 2 to return the SQL text for the top 10 largest ad hoc query plans currently in the plan cache.
Listing 2. Returning the Top 10 Largest Ad Hoc Query Plans
select top 10 objtype, usecounts, size_in_bytes, plan_handle, -- the following removes newline and carriage return from the sql text replace(replace( text, char(13), ' '), char(10), ' ') as sqltext from sys.dm_exec_cached_plans as p cross apply sys.dm_exec_sql_text (p.plan_handle) where cacheobjtype = 'Compiled Plan' and objtype = 'Adhoc' order by size_in_bytes desc, usecounts desc
|
sys.dm_exec_query_stats
The
plan cache also keeps track of useful statistics about each cached
plan, such as the amount of CPU or the number of reads and writes
performed by the query plan since it was placed into the plan cache.
This information can be examined using the sys.dm_exec_query_stats
DMV, which returns statistics for each statement in a stored procedure
or a SQL batch. To provide statistics for the procedure or batch as a
whole, you need to summarize the data. Listing 3
provides a sample query that returns the I/O, CPU, and elapsed time
statistics for the 10 most recently executed stored procedures.
Listing 3. Returning Query Plan Stats for the 10 Most Recently Executed Procedures
select TOP 10 usecounts, size_in_bytes, max(last_execution_time) as last_execution_time, sum(total_logical_reads) as total_logical_reads, sum(total_physical_reads) as total_physical_reads, sum(total_worker_time/1000) as total_CPU_time, sum(total_elapsed_time/1000) as total_elapsed_time, replace(substring (text, patindex('%create procedure%', text), datalength(text)), 'create procedure', '') as procname from sys.dm_exec_query_stats s join sys.dm_exec_cached_plans p on s.plan_handle = p.plan_handle CROSS APPLY sys.dm_exec_sql_text(p.plan_handle) as st where p.objtype = 'Proc' and p.cacheobjtype = 'Compiled Plan' group by usecounts, size_in_bytes, text order by max(last_execution_time) desc
|
Table 1 describes some of the most useful columns returned by the sys.dm_exec_query_stats DMV.
Table 1. Description of Columns for sys.dm_exec_query_stats
Column Name | Description |
---|
statement_start_offset | The
starting position of the query that the row describes within the text
of its batch or stored procedure, indicated in bytes, beginning with 0. |
statement_end_offset | The ending position of the query that the row describes within the text of its batch or stored proc. A value of -1 indicates the end of the batch. |
plan_generation_num | The number of times the plan has been recompiled while it has remained in the cache. |
plan_handle | A pointer to the plan. This value can be passed to the dm_exec_query_plan dynamic management function. |
creation_time | The time the plan was compiled. |
last_execution_time | The last time the plan was executed. |
execution_count | The number of times the plan has been executed since it was last compiled. |
total_worker_time | The total amount of CPU time, in microseconds, consumed by executions of this plan for the statement. |
last_worker_time | The CPU time, in microseconds, consumed the last time the plan was executed. |
min_worker_time | The minimum CPU time, in microseconds, this plan has ever consumed during a single execution. |
max_worker_time | The maximum CPU time, in microseconds, this plan has ever consumed during a single execution. |
total_physical_reads | The total number of physical reads performed by executions of this plan since it was compiled. |
last_physical_reads | The number of physical reads performed the last time the plan was executed. |
min_physical_reads | The minimum number of physical reads this plan has ever performed during a single execution. |
max_physical_reads | The maximum number of physical reads this plan has ever performed during a single execution. |
total_logical_writes | The total number of logical writes performed by executions of this plan since it was compiled. |
last_logical_writes | The number of logical writes performed the last time the plan was executed. |
min_logical_writes | The minimum number of logical writes this plan has ever performed during a single execution. |
max_logical_writes | The maximum number of logical writes this plan has ever performed during a single execution. |
total_logical_reads | The total number of logical reads performed by executions of this plan since it was compiled. |
last_logical_reads | The number of logical reads performed the last time the plan was executed. |
min_logical_reads | The minimum number of logical reads this plan has ever performed during a single execution. |
max_logical_reads | The maximum number of logical reads this plan has ever performed during a single execution. |
total_elapsed_time | The total elapsed time, in microseconds, for completed executions of this plan. |
last_elapsed_time | The elapsed time, in microseconds, for the most recently completed execution of this plan. |
min_elapsed_time | The minimum elapsed time, in microseconds, for any completed execution of this plan. |
max_elapsed_time | The maximum elapsed time, in microseconds, for any completed execution of this plan. |
query_hash | The binary hash value calculated on the query and used to identify queries with similar logic. |
query_plan_hash | The binary hash value calculated on the query execution plan and used to identify similar query execution plans. |
The query_hash and query_plan_hash
values are new for SQL Server 2008. You can use these values to
determine the aggregate resource usage for queries that differ only by
literal values or with similar execution plans. You can use these values
to write queries that you can use to help determine the aggregate
resource usage for similar queries and similar query execution plans.
For example, Listing 4 provides a query to find the query_hash and query_plan_hash values for queries that select from the titles table searching by ytd_sales.
Looking at the results, you can see that even with different search
arguments, each of the matching queries generates the same query hash
value, but they have different query plan hash values for queries that
use different query plans.
Listing 4. Returning Query and Query Plan Hash Values for a Query
SELECT convert(varchar(41), substring(st.text, 1, 42)) AS 'Query Text', qs.query_hash AS 'Query Hash', qs.query_plan_hash as 'Query Plan Hash' FROM sys.dm_exec_query_stats qs CROSS APPLY sys.dm_exec_sql_text (qs.sql_handle) st WHERE st.text like 'SELECT * from titles where ytd_sales%' go Query Text Query Hash Query Plan Hash -------------------------------------------------------------------------------- select * from titles where ytd_sales = 0 0x9AB21AC5889FE2D0 0x8D6DE6D258BABB2B select * from titles where ytd_sales = 0 0x9AB21AC5889FE2D0 0x8D6DE6D258BABB2B select * from titles where ytd_sales = 99 0x9AB21AC5889FE2D0 0xE889B5D23D917DFD select * from titles where ytd_sales = 10 0x9AB21AC5889FE2D0 0xE889B5D23D917DFD select * from titles where ytd_sales = 0 0x9AB21AC5889FE2D0 0x8D6DE6D258BABB2B select * from titles where ytd_sales = 0 0x9AB21AC5889FE2D0 0xE889B5D23D917DFD
|
This query hash or query plan
hash value can be used in a query to aggregate performance statistics
for like queries. For example, the following query returns the average
processing time and logical reads for the same queries that were
returned in Listing 2:
SELECT
SUM(total_worker_time) / SUM(execution_count)/1000. AS "Avg CPU Time(ms)",
SUM(total_logical_reads) / SUM(execution_count) AS "Avg Reads"
FROM
sys.dm_exec_query_stats
where query_hash = 0x9AB21AC5889FE2D0
go
Avg CPU Time(ms) Avg Reads
--------------------------------------- --------------------
164.092000 7
Listing 5
provides a sample query using the query hash value to return
information about the top 25 queries ranked by average processing time.
Listing 5. Returning Top 25 Queries Using Query Hash
SELECT TOP 25 query_stats.query_hash AS "Query Hash", SUM(query_stats.total_worker_time) / SUM(query_stats.execution_count) AS "Avg CPU Time", MIN(query_stats.statement_text) AS "Statement Text" FROM (SELECT QS.*, SUBSTRING(ST.text, (QS.statement_start_offset/2) + 1, ((CASE statement_end_offset WHEN -1 THEN DATALENGTH(ST.text) ELSE QS.statement_end_offset END - QS.statement_start_offset)/2) + 1) AS statement_text FROM sys.dm_exec_query_stats AS QS CROSS APPLY sys.dm_exec_sql_text(QS.sql_handle) as ST) as query_stats GROUP BY query_stats.query_hash ORDER BY 2 DESC; GO
|
sys.dm_exec_plan_attributes
If you want to get information about specific attributes of a specific query plan, you use sys.dm_exec_plan_attributes. This DMV takes a plan_handle as an input parameter (see Listing 1
for an example of a query that you can use to retrieve a query’s plan
handle) and returns one row for each attribute associated with the query
plan. These attributes include information such as the ID of the
database context the query plan was generated in, the ID of the user who
generated the query plan, session SET
options in effect at the time the plan was generated, and so on. Many
of these attributes are used as part of the cache lookup key for the
plan (indicated by the value 1 in the is_cache_key_column). Following is an example of the output for sys.dm_exec_plan_attributes:
select convert(varchar(30), attribute) as attribute,
convert(varchar(12), value) as value,
is_cache_key
FROM
sys.dm_exec_plan_attributes (0x06000400EBC44D2AB880A006000000000000000000000000)
where is_cache_key = 1
go
attribute value is_cache_key
--------------------------------------------------------
set_options 187 1
objectid 709739755 1
dbid 4 1
dbid_execute 0 1
user_id -2 1
language_id 0 1
date_format 1 1
date_first 7 1
compat_level 100 1
status 0 1
required_cursor_options 0 1
acceptable_cursor_options 0 1
merge_action_type 0 1
is_replication_specific 0 1
optional_spid 0 1
optional_clr_trigger_dbid 0 1
optional_clr_trigger_objid 0 1
Note
the attributes flagged as cache keys for the plan. If one of these
properties does not match the state of the current user session, the
plan cannot be reused for that session, and a new plan must be compiled
and stored in the plan cache. If you see multiple plans in cache for
what appears to be the same query, you can determine the key differences
between them by comparing the columns associated with the plan’s cache
keys to see where the differences lie.
Tip
If SQL Server has been
running for a while, with a lot of activity, the number of plans in the
plan cache can become quite large, resulting in a large number of rows
being returned by the plan cache DMVs. To run your own tests to
determine which query plans get cached and when specific query plans are
reused, you should clear out the cache occasionally. You can use the DBCC FREEPROCCACHE
command to clear all cached plans from memory. If you want to clear
only the cached plans for objects or queries in a specific database, you
execute the following command:
DBCC FLUSHPROCINDB (dbid)
Keep in mind that you should
run these commands only in a test environment. Running these commands in
production servers could impact the performance of the currently
running applications.