web-dev-qa-db-ja.com

キャッシュされた実行プランで欠落しているインデックスを探す

SQLサーバーに何がキャッシュされているかを示す次のクエリがあります。

SELECT cp.objtype AS ObjectType,
OBJECT_NAME(st.objectid,st.dbid) AS ObjectName,
cp.usecounts AS ExecutionCount,
st.TEXT AS QueryText,
qp.query_plan AS QueryPlan
FROM sys.dm_exec_cached_plans AS cp
CROSS APPLY sys.dm_exec_query_plan(cp.plan_handle) AS qp
CROSS APPLY sys.dm_exec_sql_text(cp.plan_handle) AS st
WHERE 1=1
  --AND OBJECT_NAME(st.objectid,st.dbid) = 'YourObjectName'
  AND query_plan IS NOT NULL
ORDER BY ExecutionCount DESC

enter image description here

欠落しているインデックスを探すためにqueryPlanフィールドをクエリできる方法はありますか?

5

以下のスクリプトを実行して、キャッシュされた実行プランで欠落しているインデックスを見つけることができます here

SELECT qp.query_plan
, total_worker_time/execution_count AS AvgCPU 
, total_elapsed_time/execution_count AS AvgDuration 
, (total_logical_reads+total_physical_reads)/execution_count AS AvgReads 
, execution_count 
, SUBSTRING(st.TEXT, (qs.statement_start_offset/2)+1 , ((CASE qs.statement_end_offset WHEN -1 THEN datalength(st.TEXT) ELSE qs.statement_end_offset END - qs.statement_start_offset)/2) + 1) AS txt 
, qp.query_plan.value('declare default element namespace "http://schemas.Microsoft.com/sqlserver/2004/07/showplan"; (/ShowPlanXML/BatchSequence/Batch/Statements/StmtSimple/QueryPlan/MissingIndexes/MissingIndexGroup/@Impact)[1]' , 'decimal(18,4)') * execution_count AS TotalImpact
, qp.query_plan.value('declare default element namespace "http://schemas.Microsoft.com/sqlserver/2004/07/showplan"; (/ShowPlanXML/BatchSequence/Batch/Statements/StmtSimple/QueryPlan/MissingIndexes/MissingIndexGroup/MissingIndex/@Database)[1]' , 'varchar(100)') AS [DATABASE]
, qp.query_plan.value('declare default element namespace "http://schemas.Microsoft.com/sqlserver/2004/07/showplan"; (/ShowPlanXML/BatchSequence/Batch/Statements/StmtSimple/QueryPlan/MissingIndexes/MissingIndexGroup/MissingIndex/@Table)[1]' , 'varchar(100)') AS [TABLE]
FROM sys.dm_exec_query_stats qs
cross apply sys.dm_exec_sql_text(sql_handle) st
cross apply sys.dm_exec_query_plan(plan_handle) qp
WHERE qp.query_plan.exist('declare default element namespace "http://schemas.Microsoft.com/sqlserver/2004/07/showplan";/ShowPlanXML/BatchSequence/Batch/Statements/StmtSimple/QueryPlan/MissingIndexes/MissingIndexGroup/MissingIndex[@Database!="m"]') = 1
ORDER BY TotalImpact DESC

それはSQL Server 2008と2012で私にとってはうまくいきましたが、それでもSQL Server 2014で見ているように機能するかどうかを確認する必要があります。

また、

上記に加えて、次を読むことをお勧めします プランキャッシュ内のクエリが特定のインデックスを使用していることを確認する 、これは、現在の実行に使用されているこれらのインデックスの使用法を理解するのに非常に役立つジョナサンのすばらしいスクリプトですキャッシュ内の計画。

3
KASQLDBA

(バッチ全体ではなく)クエリで不足しているインデックスをバッチで検索する場合は、_sys.dm_exec_query_plan_ではなく sys.dm_exec_text_query_plan() を使用します。これは、バッチ全体(ストアドプロシージャや関数など)ではなく、_statement_start_offset_および_statement_end_offset_を使用して、実際のクエリのプランを返します。

_CREATE TABLE #query_cache
(
  PlanHandle      VARBINARY(64),
  DatabaseName    VARCHAR(255),
  SchemaName      VARCHAR(50),
  ObjectName      VARCHAR(50),
  ExecutionCount  BIGINT,
  StatementText   NVARCHAR(MAX),
  StatementStart  BIGINT,
  StatementEnd    BIGINT,
  QueryPlan       XML
);
_

まず、_plan_handle_、_statement_start_offset_、statement_end_offset`とともに、クエリのユニバースをキャッシュに構築します。これは、最後の日に実行されたすべてのものを取ります。

_SELECT
   deqs.plan_handle AS PlanHandle
  ,DB_NAME(CAST(depa.value AS SMALLINT)) AS DatabaseName
  ,OBJECT_SCHEMA_NAME(dest.objectid, CAST(depa.value AS INT)) AS SchemaName
  ,OBJECT_NAME(dest.objectid, CAST(depa.value AS INT)) AS ObjectName
  ,SUM(deqs.execution_count) AS ExecutionCount
  ,Q.statementtext 
  ,MAX(statement_start_offset) statement_start_offset
  ,MAX(statement_end_offset) statement_end_offset  
INTO #query_cache                         
FROM sys.dm_exec_query_stats AS deqs 
CROSS APPLY sys.dm_exec_sql_text(deqs.sql_handle) AS dest
CROSS APPLY sys.dm_exec_plan_attributes(plan_handle) AS depa
CROSS APPLY (VALUES (SUBSTRING(dest.text, (deqs.statement_start_offset/2)+1,
                          ((CASE deqs.statement_end_offset WHEN -1 THEN DATALENGTH(dest.text)
                                ELSE deqs.statement_end_offset
                              END - deqs.statement_start_offset)/2)+1))) AS Q(statementtext)                                  
WHERE deqs.last_execution_time > DATEADD(DAY, -1, GETDATE()) 
      AND depa.attribute = 'dbid'       
GROUP BY 
      dest.text, Q.statementtext, deqs.plan_handle, dest.objectid, depa.value;
_

残念ながら、_dm_exec_text_query_plan_はNVARCHAR(MAX)としてプランを返すため、ここにTRY_CAST()をXMLで実行するための中間ステップがあります(2012以降の場合)。どうやらNVARCHARXMLにキャストされない場合があるため、本番環境でこれを自動化している場合は、何かを壊さないようにすることをお勧めします。

このビットは少し遅いです...

_UPDATE qc 
  SET QueryPlan = TRY_CAST(detqp.query_plan AS XML)
FROM #query_cache AS qc
CROSS APPLY sys.dm_exec_text_query_plan(PlanHandle, qc.StatementStart, qc.StatementEnd) AS detqp 
WHERE qc.StatementText IS NOT NULL;
_

次に、キャッシュを使用して、警告の変換、インデックスの欠落、キーの検索など、さまざまなメトリックを取得できます。

_WITH 
XMLNAMESPACES (DEFAULT N'http://schemas.Microsoft.com/sqlserver/2004/07/showplan')
SELECT
    DatabaseName 
    ,SchemaName 
    ,ObjectName 
    ,ExecutionCount 
    ,StatementText 
    ,qc.QueryPlan.value('(/ShowPlanXML/BatchSequence/Batch/Statements/*/@StatementType)[1]', 'varchar(50)') StatementType
    ,qc.QueryPlan.value('(/ShowPlanXML/BatchSequence/Batch/Statements/*/@StatementOptmEarlyAbortReason)[1]', 'varchar(50)') StatementOptmEarlyAbortReason
    ,qc.QueryPlan.value('count(/ShowPlanXML/BatchSequence/Batch/Statements/*/QueryPlan/Warnings/PlanAffectingConvert)', 'int') ConvertWarnings
    ,qc.QueryPlan.value('count(/ShowPlanXML/BatchSequence/Batch/Statements/*/QueryPlan/Warnings/NoJoinPredicate)', 'int') NoJoinPredicateWarnings
    ,qc.QueryPlan.value('count(/ShowPlanXML/BatchSequence/Batch/Statements/*/QueryPlan/MissingIndexes/MissingIndexGroup/MissingIndex)', 'int') MissingIndexes
    ,qc.QueryPlan.value('count(.//RelOp[IndexScan[@Lookup="1"] and IndexScan/Object[@Schema!="[sys]"]])', 'int') KeyLookups
FROM #query_cache AS qc;
_

これの可能な改善:

  • mastermsdbなどの特定のデータベースを除外します。

  • 収集プロセスを自動化して定期的に収集する

  • IO/CPU /期間によって上位xクエリのみを返すようにフィルタリングします

3
Mark Sinkinson

最近、同様のニーズがありました。

answer by KASQLDBA は、最初に見つからないインデックスを計画から引き出します。複数存在する可能性があります。

そのため、次のコードを使用しました(ストアドプロシージャレベルの情報が必要でした-sys.dm_exec_query_stats の代わりに sys.dm_exec_procedure_statsこれが望ましくない場合)

WITH XMLNAMESPACES
    (DEFAULT 'http://schemas.Microsoft.com/sqlserver/2004/07/showplan') 
SELECT [Procedure] = quotename(object_schema_name(object_id)) + '.' + quotename(object_name(object_id)), 
       Impact = MissingIndexGroup.n.value('@Impact', 'float'), 
       [Database] = MissingIndex.n.value('@Database', 'nvarchar(130)'), 
       [Schema] = MissingIndex.n.value('@Schema', 'nvarchar(130)'), 
       [Table] = MissingIndex.n.value('@Table', 'nvarchar(130)'),
       EqualityColumns = SUBSTRING(eqColumns.list,2,8000),
       InEqualityColumns = SUBSTRING(ineqColumns.list,2,8000),
       IncludedColumns = SUBSTRING(incColumns.list,2,8000),
       qp.query_plan
FROM sys.dm_exec_procedure_stats ps
cross apply sys.dm_exec_query_plan(ps.plan_handle) qp
cross apply qp.query_plan.nodes('//MissingIndexes/MissingIndexGroup') MissingIndexGroup(n)
cross apply MissingIndexGroup.n.nodes('MissingIndex') MissingIndex(n)
cross apply (SELECT ',' + n.n.value('@Name','nvarchar(130)') FROM MissingIndex.n.nodes('./ColumnGroup[@Usage="EQUALITY"]/Column') n(n) FOR XML PATH('')) eqColumns(list)
cross apply (SELECT ',' + n.n.value('@Name','nvarchar(130)') FROM MissingIndex.n.nodes('./ColumnGroup[@Usage="INEQUALITY"]/Column') n(n) FOR XML PATH('')) ineqColumns(list)
cross apply (SELECT ',' + n.n.value('@Name','nvarchar(130)') FROM MissingIndex.n.nodes('./ColumnGroup[@Usage="INCLUDE"]/Column') n(n) FOR XML PATH('')) incColumns(list)
where ps.database_id = db_id()
2
Martin Smith