以前の投稿では、統計の自動更新を追跡して、それらがクエリのパフォーマンスに影響を与えているかどうかを判断するためのさまざまな方法を検討しました。投稿の後半には、オプションを含めました。その1つは、統計の非同期データベース設定の自動更新を有効にすることでした。この投稿では、クエリの実行前に自動更新が行われた場合にクエリのパフォーマンスがどのように変化するか、および更新が非同期の場合にパフォーマンスがどうなるかを確認したいと思います。
セットアップ
私はAdventureWorks2012データベースのコピーから始め、次にこのスクリプトを使用して2億行を超えるSalesOrderHeaderテーブルのコピーを作成しました。このテーブルには、SalesOrderIDにクラスター化されたインデックスがあり、CustomerID、OrderDate、SubTotalに非クラスター化されたインデックスがあります。 [注:繰り返しテストを行う場合は、時間を節約するために、この時点でこのデータベースのバックアップを作成してください]。データをロードして非クラスター化インデックスを作成した後、行数を確認し、自動更新を呼び出すために(およそ)変更する必要がある行数を計算しました。
SELECTOBJECT_NAME([p]。[object_id])[TableName]、[si]。[name] [IndexName]、[au]。[type_desc] [Type]、[p]。[rows] [RowCount]、 ([p]。[rows] *。20)+ 500 [UpdateThreshold]、[au] .total_pages [PageCount]、(([au]。[total_pages] * 8)/ 1024)/ 1024 [TotalGB] FROM [sys ]。[partitions][p]JOIN[sys]。[allocation_units][au]ON[p]。[partition_id]=[au]。[container_id]JOIN[sys]。[indexes][si]on [p] 。[object_id]=[si] .object_idand[p]。[index_id]=[si]。[index_id]WHERE[p]。[object_id]=OBJECT_ID(N'Sales.Big_SalesOrderHeader');
Big_SalesOrderHeaderCIXおよびNCI情報
インデックスの現在の統計ヘッダーも確認しました:
DBCC SHOW_STATISTICS('Sales.Big_SalesOrderHeader'、[IX_Big_SalesOrderHeader_CustomerID_OrderDate_SubTotal]);
NCI統計:開始時
次に、テストに使用するストアドプロシージャを作成しました。これは、Sales.Big_SalesOrderHeaderにクエリを実行し、分析のためにCustomerIDとOrderDateごとに販売データを集計する簡単な手順です。
CREATE PROCEDURE Sales.usp_GetCustomerStats @ CustomerID INT、@ StartDate DATETIME、@ EndDate DATETIMEASBEGIN SET NOCOUNT ON; SELECT CustomerID、DATEPART(YEAR、OrderDate)、DATEPART(MONTH、OrderDate)、COUNT([SalesOrderID])as ComputedFROM[Sales]。[Big_SalesOrderHeader]WHERECustomerID =@CustomerID AND OrderDate BETWEEN @StartDate and @EndDate GROUP BY CustomerID、 DATEPART(YEAR、OrderDate)、DATEPART(MONTH、OrderDate)ORDER BY DATEPART(YEAR、OrderDate)、DATEPART(MONTH、OrderDate); END
最後に、ストアドプロシージャを実行する前に、拡張イベントセッションを作成して、sp_statement_startingとsp_statement_completedを使用してクエリ期間を追跡できるようにしました。また、auto_statsイベントを追加しました。これは、更新が発生することを予期していなかったにもかかわらず、後で同じセッション定義を使用したかったためです。
CREATE EVENT SESSION [StatsUpdate_QueryPerf] ON SERVERADD EVENT sqlserver.auto_stats、ADD EVENT sqlserver.sp_statement_completed(SET collect_statement =(1))、ADD EVENT sqlserver.sp_statement_startingADD TARGET package0.event_file(SET filename =N'C:\ temp \ StatsUpdate_QueryPerf.xel')WITH(MAX_MEMORY =4096 KB、EVENT_RETENTION_MODE =ALLOW_SINGLE_EVENT_LOSS、MAX_DISPATCH_LATENCY =30 SECONDS、MAX_EVENT_SIZE =0 KB、MEMORY_PARTITION_MODE =NONE、TRACK_CAUSALITY =ON、STARTUP_STATE =テスト
拡張イベントセッションを開始し、さまざまなCustomerIDを使用してストアドプロシージャを複数回実行しました:
ALTER EVENT SESSION [StatsUpdate_QueryPerf] ON SERVERSTATE =START; GO EXEC Sales.usp_GetCustomerStats 11331、 '2012-08-01 00:00:00.000'、 '2012-08-31 23:59:59.997' GOEXEC Sales.usp_GetCustomerStats 11330、 '2013-01-01 00:00:00.000'、 '2013-01-31 23:59:59.997' GOEXEC Sales.usp_GetCustomerStats 11506、 '2012-11-01 00:00:00.000'、 '2012-11 -30 23:59:59.997'GOEXEC Sales.usp_GetCustomerStats 17061、' 2013-01-01 00:00:00.000'、' 2013-01-31 23:59:59.997'GOEXEC Sales.usp_GetCustomerStats 11711、' 2013-03- 01 00:00:00.000'、' 2013-03-31 23:59:59.997'GOEXEC Sales.usp_GetCustomerStats 15131、' 2013-02-01 00:00:00.000'、' 2013-02-28 23:59:59.997 'GOEXEC Sales.usp_GetCustomerStats 29837、' 2012-10-01 00:00:00.000'、' 2012-10-31 23:59:59.997'GOEXEC Sales.usp_GetCustomerStats 15750、' 2013-03-01 00:00:00.000 ' 、'2013-03-31 23:59:59.997' GOプロシージャキャッシュをクエリして、実行回数と計画を確認しました。
SELECTOBJECT_NAME([st]。[objectid])、[st]。[text]、[qs]。[execution_count]、[qs]。[creation_time]、[qs]。[last_execution_time]、[qs]。 [min_worker_time]、[qs]。[max_worker_time]、[qs]。[min_logical_reads]、[qs]。[max_logical_reads]、[qs]。[min_elapsed_time]、[qs]。[max_elapsed_time]、[qp]。[query_plan ]FROM[sys]。[dm_exec_query_stats][qs]CROSS APPLY [sys]。[dm_exec_sql_text]([qs] .plan_handle)[st] CROSS APPLY [sys]。[dm_exec_query_plan]([qs] .plan_handle)[qp] WHERE[st]。[text]LIKE'%usp_GetCustomerStats%' AND OBJECT_NAME([st]。[objectid])IS NOT NULL;
プランキャッシュ:開始時
SQL SentryPlanExplorerを使用したストアドプロシージャのクエリプラン計画は2014-04-0818:59:39.850に作成されたことがわかりました。プランをキャッシュに入れて、拡張イベントセッションを停止しました:
ALTER EVENT SESSION [StatsUpdate_QueryPerf] ON SERVERSTATE =STOP;次に、このスクリプトを使用して、現在の統計を無効にするために必要なしきい値をはるかに超える約4,700万行のデータをテーブルに追加しました。データを追加した後、テーブルの行数を確認しました:
Big_SalesOrderHeader CI:データの読み込み後ストアドプロシージャを再実行する前に、プランキャッシュをチェックして何も変更されていないことを確認し、統計がまだ更新されていないことを確認しました。この時点で統計が無効になっている場合でも、統計を使用するクエリが実行されるまで統計は更新されないことに注意してください(参照:統計が自動的に更新されるタイミングについて)。最後のステップとして、拡張イベントセッションを再度開始し、ストアドプロシージャを複数回実行しました。これらの実行後、プランキャッシュを再度確認しました:
キャッシュの計画:データの読み込み後execute_countは再び8になり、プランのcreate_timeを見ると、2014-04-08 19:32:52.913に変更されていることがわかります。計画を確認すると、計画が再コンパイルされたにもかかわらず、同じであることがわかります。
SQL SentryPlanExplorerを使用したストアドプロシージャのクエリプラン拡張イベント出力の分析
データが読み込まれる前に最初の拡張イベントファイルを取得し、それをSSMSで開いてから、フィルターを適用して、ストアドプロシージャのステートメントのみが一覧表示されるようにしました。
拡張イベント出力:最初のSP実行後ストアドプロシージャが8回実行され、クエリ期間がわずかに異なることがわかります。
データがロードされた後、2番目の拡張イベントファイルを取得してSSMSを開き、再度フィルタリングして、ストアドプロシージャのステートメントとauto_statsイベントのみが一覧表示されるようにしました。
拡張イベント出力:データロード後のSP実行主な結果を表示するためにすべてが必要なわけではないため、出力は切り捨てられます。青色で強調表示されたエントリは、ストアドプロシージャの最初の実行を表しており、複数のステップがあることに注意してください。統計の更新は実行の一部です。 SELECTステートメントが開始され(attach_activity_id.seq =3)、統計の更新が実行されます。この例では、実際には3つの統計が更新されています。最後の更新が完了すると(attach_activity_id.seq =11)、ストアドプロシージャが開始して完了します(attach_activity_id.seq=13およびattach_activity_id.seq=14)。興味深いことに、ストアドプロシージャには2番目のsp_statement_startingイベントがあり(おそらく最初のイベントは無視されます)、ストアドプロシージャの合計期間はなしで計算されます。 統計の更新。
このシナリオでは、統計をすぐに自動的に更新します。つまり、無効な統計を使用するクエリを実行すると、sp_statement_completedイベントに基づくクエリ期間が14000未満であっても、クエリの実行時間が長くなります。統計の更新の前後で計画がまったく同じであるため、クエリのパフォーマンスにメリットはありません。このシナリオでは、テーブルにデータが追加されてもクエリプランと実行期間は変更されないため、統計の更新はパフォーマンスの妨げになるだけです。次に、[統計を非同期に更新]オプションを有効にするとどうなるか見てみましょう。
テスト、バージョン2
最初のテストを開始する前に作成したバックアップに復元することから始めます。ストアドプロシージャを再作成してから、データベースオプションを変更して、統計を非同期で更新しました。
USE [master]; GOALTER DATABASE [AdventureWorks2012_Big] SET AUTO_UPDATE_STATISTICS_ASYNC ON WITH NO_WAITGO拡張イベントセッションを開始し、異なるCustomerIDを使用してストアドプロシージャを複数回実行しました:
ALTER EVENT SESSION [StatsUpdate_QueryPerf] ON SERVERSTATE =START; GO EXEC Sales.usp_GetCustomerStats11331、 '2012-08-01 00:00:00.000'、 '2012-08-31 23:59:59.997' GOEXEC Sales.usp_GetCustomerStats11330、 '2013-01-01 00:00:00.000'、 '2013-01-31 23:59:59.997' GOEXEC Sales.usp_GetCustomerStats11506、 '2012-11-01 00:00:00.000'、 '2012-11-30 23 :59:59.997'GOEXEC Sales.usp_GetCustomerStats17061、' 2013-01-01 00:00:00.000'、' 2013-01-31 23:59:59.997'GOEXEC Sales.usp_GetCustomerStats11711、' 2013-03-01 00:00: 00.000'、' 2013-03-31 23:59:59.997'GOEXEC Sales.usp_GetCustomerStats15131、' 2013-02-01 00:00:00.000'、' 2013-02-28 23:59:59.997'GOEXEC Sales.usp_GetCustomerStats29837、 '2012-10-01 00:00:00.000'、 '2012-10-31 23:59:59.997' GOEXEC Sales.usp_GetCustomerStats15750、 '2013-03-01 00:00:00.000'、 '2013-03-31 23 :59:59.997'GOプロシージャキャッシュをクエリして、実行回数と計画を確認しました。
キャッシュの計画:開始時、テスト2
SQL SentryPlanExplorerを使用したストアドプロシージャのクエリプランこのテストでは、計画は2014-04-08 21:15:55.490に作成されました。拡張イベントセッションを停止し、以前と同じクエリを使用して、テーブルに約4,700万行のデータを再度追加しました。
データが追加されたら、プランキャッシュをチェックして何も変更されていないことを確認し、統計がまだ更新されていないことを確認しました。最後に、拡張イベントセッションを再度開始し、ストアドプロシージャをさらに8回実行しました。プランキャッシュの最後のピークでは、16のexecution_countと2014-04-08 21:15:55.490のcreate_timeが示されました。プランがまだキャッシュからフラッシュされていないため、execution_countとcreate_timeは、統計が更新されていないことを示しています(フラッシュされている場合は、後でcreate_timeとexecution_countが8になります)。
キャッシュの計画:データの読み込み後、テスト2SSMSでのデータの読み込み後に拡張イベント出力を開き、再度フィルター処理して、ストアドプロシージャからのステートメントと、auto_statsイベントのみが表示されるようにすると、次のことがわかります(出力が2つのスクリーンショットに分割されていることに注意してください)。
拡張イベント出力:テスト2、データロード後のSP実行、パートI
拡張イベント出力:テスト2、データロード後のSP実行、パートIIストアドプロシージャの最初の呼び出しを実行するためのイベントは青色で強調表示されています。これらのイベントは2014-04-0821:54:14.9480607に開始され、7つのイベントがあります。 auto_statsイベントは3つありますが、[Statistics Asynchronouslyの自動更新]オプションが無効になっているときに見たように、実際にはどれも完了していません。統計の1つ(2014-04-08 21:54:14.9481288)の自動更新がほぼすぐに開始され、3つのイベントの横に赤いテキスト「StatUpdate#1」が表示されます。その統計の更新は、2014-04-08 21:54:16.5392219に終了します。これは、開始後2秒弱ですが、プロシージャの他のすべての実行が完了した後です。これが、sys.dm_exec_query_statsのexecution_countが16を示す理由です。XE出力から、他の統計更新が完了したことがわかります(統計更新#2および統計更新#3)。すべての更新は、最初のストアドプロシージャの実行とは非同期です。
概要
ご覧のとおり、統計の自動更新はクエリのパフォーマンスに悪影響を与える可能性があります。影響の程度は、統計を更新するために読み取る必要のあるデータの量とシステムリソースによって異なります。場合によっては、クエリのパフォーマンスはミリ秒単位でしか向上せず、ユーザーには気付かない可能性があります。また、期間が劇的に長くなり、エンドユーザーエクスペリエンスに影響を与える場合もあります。統計の更新後にクエリプランが変更されない場合は、[統計を非同期に更新]オプションを有効にして、クエリのパフォーマンスへの影響を軽減することを検討する価値があります。