私はもともとこれを別の質問としてTwitter #sqlhelpに投稿しましたが、ここに別の方法で投稿したかったのです。
DMV dm_exec_query_stats、dm_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;
_
偶然にも、私は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;
_
注:テーブル値関数_[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のクエリ自体は、常に非常に高速です。
SOS_SCHEDULER_YIELDの累積は、#sqlhelpで提案したとおりです。それらのそれぞれは、クエリの4msのCPU時間に相当し、リソース待機が関与しないため(スレッドはプロセッサを生成し、スケジューラの実行可能キューの最下部に直接移動します)、常にリソース待機時間は0と表示されます。
したがって、このクエリは、他のリソースを必要とせずにCPUを介して実行されていました。
これは、特定の待機タイプとして表示されないため(SOS_SCHEDULER_YIELD =スピンロックという一般的な誤解)、スピンロックとは何の関係もありませんでした。待機が発生していないとおっしゃったとき、私はそれを提案しました。
実行速度が遅い場合は、迅速に実行する場合よりも著しく多くの行を返しますか?