Find Top N costly query plans in adhoc batches or modules
- sys.dm_exec_query_stats - Returns performance statistics for cached query plans. This contains one row per query plan so if a stored procedure or batch contains two SELECT statements you may get two rows here
- sys.dm_exec_sql_text - Returns the text of the sql statement based on the SQL handle
- sys.dm_exec_query_plan - Returns the showplan in XML format for a batch or module based on the plan handle
from sys.dm_exec_query_stats as qs
cross apply sys.dm_exec_sql_text(qs.sql_handle) as st
, substring(st.text, (qs.statement_start_offset/2)+1
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
, qp.query_plan
from sys.dm_exec_query_stats as qs
cross apply sys.dm_exec_query_plan (qs.plan_handle) as qp
as
begin
declare @spid int
set @spid = @@spid
select count(*) from master.sys.sysprocesses where spid = @spid
select count(*) from master.sys.syslockinfo where req_spid = @spid
end
- StatementText="create procedure testproc
as
begin
 declare @spid int
 set @spid = @@spid
 "
StatementId="1"
StatementType="ASSIGN" - StatementText=" select count(*) from master.sys.sysprocesses where spid = @spid
 "
StatementId="2"
StatementType="SELECT" - StatementText=" select count(*) from master.sys.syslockinfo where req_spid = @spid
"
StatementId="3"
StatementType="SELECT"
set @top_n_plans = 25;
with XMLNAMESPACES ('http://schemas.microsoft.com/sqlserver/2004/07/showplan' as sql)
select top(@top_n_plans)
qst.text as sql_text, qp.query_plan
, qst.statement_id
, qst.statement_text as select_statement
, qps2.statement_optimization_level
, qps2.statement_optimization_early_abort_reason
, (
select sum(ro.SubTreeCost.value(N'@EstimatedTotalSubtreeCost', 'float'))
from qp.query_plan.nodes(
) as Totalcost
, qps2.statement_sub_tree_cost
, qst.creation_time, qst.last_execution_time, qst.execution_count
, qst.total_elapsed_time, qst.last_elapsed_time, qst.min_elapsed_time, qst.max_elapsed_time
, qst.total_worker_time, qst.last_worker_time, qst.min_worker_time, qst.max_worker_time
, qst.total_physical_reads, qst.last_physical_reads
, qst.total_logical_writes, qst.last_logical_writes
, qst.total_logical_reads, qst.last_logical_reads
, qst.sql_handle, qst.plan_handle
from (
select *
, substring(st.text, (qs.statement_start_offset/2)+1
when -1 then datalength(st.text)
else qs.statement_end_offset
end - qs.statement_start_offset)/2) + 1) as statement_text
, ROW_NUMBER() OVER(PARTITION BY qs.plan_handle
from sys.dm_exec_query_stats as qs
cross apply sys.dm_exec_sql_text(qs.sql_handle) as st
) as qst
cross apply sys.dm_exec_query_plan (qst.plan_handle) as qp
cross apply (
-- Since sys.dm_exec_query_stats doesn't have statement id,
-- on the statement start offset in sys.dm_exec_query_stats.
-- This allows us to match the row from showplan with that of the query stats.
-- This is a problem for batches containing multiple SELECT statements
select ROW_NUMBER() OVER(ORDER BY qps1.statement_id) as rel_statement_id
, qps1.statement_optimization_level, qps1.statement_sub_tree_cost
from (
select sel.StmtSimple.value('@StatementId', 'int')
, sel.StmtSimple.value('@StatementSubTreeCost', 'float')
, sel.StmtSimple.value('@StatementOptmLevel' , 'varchar(30)')
, sel.StmtSimple.value('@StatementOptmEarlyAbortReason', 'varchar(30)')
from qp.query_plan.nodes(
) as qps1(statement_id, statement_sub_tree_cost
) as qps2
where qps2.rel_statement_id = qst.statement_id
-- and qst.text like ... /* can be used to filter only particular statemetns */
order by Totalcost desc, qst.plan_handle, qst.statement_id;
Lastly, let me highlight the new relational, TSQL and Xquery features of SQL Server 2005 as used in the query.
- TOP clause - Support for variables or expressions in the TOP clause
- APPLY operator - Used to join table against table-valued function in the query
- ROW_NUMBER analytic function - To generate sequence numbers based on a particular order and optionally partition the rows into different groups
- Xquery nodes method - To get the matching nodes based on a XPath expression for each instance of a query plan
- Xquery value method - To get a particular Xml attribute as a typed column
- WITH XMLNAMESPACES - To use namespace prefix in XPath expressions easily in a query
- TOP (Transact-SQL) ms-help://MS.SQLCC.v9/MS.SQLSVR.v9.en/tsqlref9/html/da983c0a-06c5-4cf8-a6a4-7f9d66f34f2c.htm
- FROM (Transact-SQL) ms-help://MS.SQLCC.v9/MS.SQLSVR.v9.en/tsqlref9/html/36b19e68-94f6-4539-aeb1-79f5312e4263.htm
- ROW_NUMBER (Transact-SQL) ms-help://MS.SQLCC.v9/MS.SQLSVR.v9.en/tsqlref9/html/82fa9016-77db-4b42-b4c8-df6095b81906.htm
- OVER Clause (Transact-SQL) ms-help://MS.SQLCC.v9/MS.SQLSVR.v9.en/tsqlref9/html/ddcef3a6-0341-43e0-ae73-630484b7b398.htm
- sys.dm_exec_query_stats ms-help://MS.SQLCC.v9/MS.SQLSVR.v9.en/tsqlref9/html/eb7b58b8-3508-4114-97c2-d877bcb12964.htm
- sys.dm_exec_sql_text ms-help://MS.SQLCC.v9/MS.SQLSVR.v9.en/tsqlref9/html/61b8ad6a-bf80-490c-92db-58dfdff22a24.htm
- sys.dm_exec_query_plan ms-help://MS.SQLCC.v9/MS.SQLSVR.v9.en/tsqlref9/html/e26f0867-9be3-4b2e-969e-7f2840230770.htm
- XML Showplans ms-help://MS.SQLCC.v9/MS.SQLSVR.v9.en/udb9/html/8f8fa597-1c57-496f-84cc-275c2b80fd8f.htm
- Adding Namespaces Using WITH XMLNAMESPACES ms-help://MS.SQLCC.v9/MS.SQLSVR.v9.en/udb9/html/2189cb5e-4460-46c5-a254-20c833ebbfec.htm
- WITH XMLNAMESPACES (Transact-SQL) ms-help://MS.SQLCC.v9/MS.SQLSVR.v9.en/tsqlref9/html/3b32662b-566f-454d-b7ca-e247002a9a0b.htm
- nodes() Method (xml Data Type) ms-help://MS.SQLCC.v9/MS.SQLSVR.v9.en/udb9/html/7267fe1b-2e34-4213-8bbf-1c953822446c.htm
- value() Method (xml Data Type) ms-help://MS.SQLCC.v9/MS.SQLSVR.v9.en/udb9/html/298a7361-dc9a-4902-9b1e-49a093cd831d.htm
More things from comments about the implicit convertion in sql plan.
You can get the SELECT statements that have an expression with CONVERT_IMPLICIT in many ways. Below is a query that uses the sys.dm_exec_query_plan and XQuery to get the information:
with XMLNAMESPACES ('http://schemas.microsoft.com/sqlserver/2004/07/showplan' as sql)
select qs.sql_handle, qs.plan_handle, qs.total_elapsed_time, qs.last_elapsed_time, qs.min_elapsed_time, qs.max_elapsed_time
, db_name(qp.dbid) as database_name
, quotename(object_schema_name(qp.objectid, qp.dbid)) + N'.' + quotename(object_name(qp.objectid, qp.dbid)) as obj_name
, qp.query_plan.value( N'(/sql:ShowPlanXML/sql:BatchSequence/sql:Batch/sql:Statements/sql:StmtSimple[@StatementType = "SELECT"])[1]/@StatementText', 'nvarchar(max)' ) as plan_stmt_text
, qp.query_plan.value( N'(/sql:ShowPlanXML/sql:BatchSequence/sql:Batch/sql:Statements/sql:StmtSimple[@StatementType = "SELECT"]/sql:QueryPlan/sql:RelOp/descendant::*/sql:ScalarOperator[contains(@ScalarString, "CONVERT_IMPLICIT")])[1]/@ScalarString', 'nvarchar(4000)' ) as scalar_string
from sys.dm_exec_query_stats as qs
cross apply sys.dm_exec_query_plan(qs.plan_handle) as qp
where qp.query_plan.exist( N'/sql:ShowPlanXML/sql:BatchSequence/sql:Batch/sql:Statements/sql:StmtSimple[@StatementType = "SELECT"]/sql:QueryPlan/sql:RelOp/descendant::*/sql:ScalarOperator[contains(@ScalarString, "CONVERT_IMPLICIT")]' ) = 1;
You can also use the new DMV sys.dm_exec_text_query_plan and do a text-based search which is less accurate.
select qs.sql_handle, qs.plan_handle, qs.total_elapsed_time, qs.last_elapsed_time, qs.min_elapsed_time, qs.max_elapsed_time
, db_name(tqp.dbid) as database_name
, quotename(object_schema_name(tqp.objectid, tqp.dbid)) + N'.' + quotename(object_name(tqp.objectid, tqp.dbid)) as obj_name
, tqp.query_plan
from sys.dm_exec_query_stats as qs
cross apply sys.dm_exec_text_query_plan( qs.plan_handle, qs.statement_start_offset, qs.statement_end_offset ) as tqp
where tqp.query_plan like '%"%CONVERT_IMPLICIT%"%';
Note that you need to change the query (OBJECT_NAME and OBJECT_SCHEMA_NAME usage) if you are not running on SQL Server 2005 SP2. I will be discussing this particular question in a new BLOG article to explain further.
浙公网安备 33010602011771号