web-dev-qa-db-ja.com

sys.databasesに結合されたクエリ統計と実行プランのDMVに対してクエリを実行する

私はもともとこれを別の質問としてTwitter #sqlhelpに投稿しましたが、ここに別の方法で投稿したかったのです。

DMV dm_exec_query_statsdm_exec_cached_plans、およびdm_exec_sql_text、および最初はsys.databases

発生したのは、このクエリがビジー状態のサーバーで実行されるのに常にではなく8分以上かかることでしたが、sys.databasesへの結合を削除するとすぐに9秒しかかかりませんでした。この期間のほとんどはCPU時間であり、待機時間はほとんどなく、ブロックもスキャンも行われず、13406の論理読み取り、1562のlob読み取りのみでした。

だから私が知りたいのは、sys.databasesに参加することで巨大なパフォーマンスが打たれるのはなぜですか?そして、なぜそれは矛盾していますか?

何が起こったかというと、次のテストクエリを実行したときに、明らかな理由もなく8分以上かかることもあれば、11秒未満で完了することもありました。

_SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SET NOCOUNT ON;
DECLARE @snapshot_timeoffset AS datetimeoffset(3) = CAST(SYSDATETIMEOFFSET() AS datetimeoffset(3));

SELECT
    @snapshot_timeoffset AS [snapshot_timeoffset]
    ,db.name AS [database_name]  
    ,OBJECT_SCHEMA_NAME(st.objectid, st.dbid) [schema_name] 
    ,OBJECT_NAME(st.objectid, st.dbid) [object_name]   
    ,cp.objtype
    ,cp.usecounts
    ,cp.refcounts
    -- find the offset of the actual statement being executed
    ,SUBSTRING(st.text, 
            CASE
                WHEN qs.statement_start_offset = 0 OR qs.statement_start_offset IS NULL THEN 1  
                ELSE qs.statement_start_offset/2 + 1
            END, 
            CASE 
                WHEN qs.statement_end_offset = 0 OR qs.statement_end_offset = -1 OR qs.statement_end_offset IS NULL THEN LEN(st.text)  
                ELSE qs.statement_end_offset/2 
            END - 
            CASE
                WHEN qs.statement_start_offset = 0 OR qs.statement_start_offset IS NULL THEN 1  
                ELSE qs.statement_start_offset/2
            END + 1 
        ) AS [statement]  
    ,qs.execution_count
    ,qs.total_logical_reads
    ,qs.last_logical_reads
    ,qs.min_logical_reads
    ,qs.max_logical_reads
    ,qs.total_logical_writes
    ,qs.last_logical_writes
    ,qs.min_logical_writes
    ,qs.max_logical_writes
    ,qs.total_physical_reads
    ,qs.last_physical_reads
    ,qs.min_physical_reads
    ,qs.max_physical_reads
    ,qs.total_worker_time
    ,qs.last_worker_time
    ,qs.min_worker_time
    ,qs.max_worker_time
    ,qs.total_clr_time
    ,qs.last_clr_time
    ,qs.min_clr_time
    ,qs.max_clr_time
    ,qs.total_elapsed_time
    ,qs.last_elapsed_time
    ,qs.min_elapsed_time
    ,qs.max_elapsed_time
    ,qs.total_rows
    ,qs.last_rows
    ,qs.min_rows
    ,qs.max_rows
    ,qs.last_execution_time
    ,qs.creation_time
    ,qs.sql_handle
    ,qs.plan_handle
    ,qs.statement_start_offset
    ,qs.statement_end_offset
INTO #QueryStats
FROM master.sys.dm_exec_query_stats qs   
INNER JOIN  master.sys.dm_exec_cached_plans cp 
ON qs.plan_handle = cp.plan_handle 
CROSS APPLY  master.sys.dm_exec_sql_text(qs.plan_handle) st 
INNER JOIN  master.sys.databases db
ON st.dbid = db.database_id;
_

Bad Plan

偶然にも、私はsys.databasesへの内部結合を削除し、代わりにデータベース名のルックアップをDB_NAME()関数に置き換えることを決定しました。これにより、一貫して9秒で実行されます。

_SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SET NOCOUNT ON;
DECLARE @snapshot_timeoffset AS datetimeoffset(3) = CAST(SYSDATETIMEOFFSET() AS datetimeoffset(3));

SELECT
    @snapshot_timeoffset AS [snapshot_timeoffset]
    ,DB_NAME(st.dbid) AS [database_name]  
    ,OBJECT_SCHEMA_NAME(st.objectid, st.dbid) [schema_name] 
    ,OBJECT_NAME(st.objectid, st.dbid) [object_name]   
    ,cp.objtype
    ,cp.usecounts
    ,cp.refcounts
    -- find the offset of the actual statement being executed
    ,SUBSTRING(st.text, 
            CASE
                WHEN qs.statement_start_offset = 0 OR qs.statement_start_offset IS NULL THEN 1  
                ELSE qs.statement_start_offset/2 + 1
            END, 
            CASE 
                WHEN qs.statement_end_offset = 0 OR qs.statement_end_offset = -1 OR qs.statement_end_offset IS NULL THEN LEN(st.text)  
                ELSE qs.statement_end_offset/2 
            END - 
            CASE
                WHEN qs.statement_start_offset = 0 OR qs.statement_start_offset IS NULL THEN 1  
                ELSE qs.statement_start_offset/2
            END + 1 
        ) AS [statement]  
    ,qs.execution_count
    ,qs.total_logical_reads
    ,qs.last_logical_reads
    ,qs.min_logical_reads
    ,qs.max_logical_reads
    ,qs.total_logical_writes
    ,qs.last_logical_writes
    ,qs.min_logical_writes
    ,qs.max_logical_writes
    ,qs.total_physical_reads
    ,qs.last_physical_reads
    ,qs.min_physical_reads
    ,qs.max_physical_reads
    ,qs.total_worker_time
    ,qs.last_worker_time
    ,qs.min_worker_time
    ,qs.max_worker_time
    ,qs.total_clr_time
    ,qs.last_clr_time
    ,qs.min_clr_time
    ,qs.max_clr_time
    ,qs.total_elapsed_time
    ,qs.last_elapsed_time
    ,qs.min_elapsed_time
    ,qs.max_elapsed_time
    ,qs.total_rows
    ,qs.last_rows
    ,qs.min_rows
    ,qs.max_rows
    ,qs.last_execution_time
    ,qs.creation_time
    ,qs.sql_handle
    ,qs.plan_handle
    ,qs.statement_start_offset
    ,qs.statement_end_offset
INTO #QueryStats
FROM master.sys.dm_exec_query_stats qs   
INNER JOIN  master.sys.dm_exec_cached_plans cp 
ON qs.plan_handle = cp.plan_handle 
CROSS APPLY  master.sys.dm_exec_sql_text(qs.plan_handle) st;
_

enter image description here

注:テーブル値関数_[SYSDMEXECCACHEDPLANS]_には、適切な計画の推定CPUコスト0.0522562、推定行数52256、および実際の行数52251がありました。ただし、前の計画では、同じ関数にActual Number of Rows 327605402があり、これは大きな差異です。

私はPaul Randalの Extended Event Session を実行しました。これは、彼の提案に基づいて特定のセッションの待機タイプをトレースし、次の出力を取得しました。

_Wait Type            Count Total Wait (ms)  Resource Wait (ms)  Signal Wait (ms)
NETWORK_IO            4807            1119                1119                0
CXPACKET              9449             649                 620               29
SOS_SCHEDULER_YIELD 119763             370                   0              370
SLEEP_TASK           11212               2                   0                2
LATCH_EX               782               1                   1                0
LATCH_SH                 2               0                   0                0
LCK_M_S                  4               0                   0                0
EXECSYNC                 3               0                   0                0
PAGEIOLATCH_EX           4               0                   0                0
PAGELATCH_EX             1               0                   0                0
PAGELATCH_SH             7               0                   0                0
PAGELATCH_UP            55               0                   0                0
_

待機時間はほとんどありませんが、CPU時間は継続時間とほぼ一致しています。 Paulはこれがスピンロックの競合である可能性があると述べましたが、SQL 2014 SP1 CU2で実行しており、バグ https://support.Microsoft.com/en-ca/kb/302608 はSP1ですでに修正されています。同様に、system_health.xelで何も飛び出していないのがわかります。

sys.databasesのクエリ自体は、常に非常に高速です。

6
Diane

SOS_SCHEDULER_YIELDの累積は、#sqlhelpで提案したとおりです。それらのそれぞれは、クエリの4msのCPU時間に相当し、リソース待機が関与しないため(スレッドはプロセッサを生成し、スケジューラの実行可能キューの最下部に直接移動します)、常にリソース待機時間は0と表示されます。

したがって、このクエリは、他のリソースを必要とせずにCPUを介して実行されていました。

これは、特定の待機タイプとして表示されないため(SOS_SCHEDULER_YIELD =スピンロックという一般的な誤解)、スピンロックとは何の関係もありませんでした。待機が発生していないとおっしゃったとき、私はそれを提案しました。

実行速度が遅い場合は、迅速に実行する場合よりも著しく多くの行を返しますか?

8
Paul S. Randal