前回の投稿では、CXPACKETの待機と、並列処理を防止または制限する方法について説明しました。また、並列操作の制御スレッドが常にCXPACKET待機を登録する方法と、非制御スレッドがCXPACKET待機を登録する場合があることも説明しました。これは、スレッドの1つがリソースの待機をブロックされている場合(他のすべてのスレッドがリソースの前に終了し、CXPACKETの登録も待機している場合)、またはカーディナリティの見積もりが正しくない場合に発生する可能性があります。この投稿では、後者について説明します。
カーディナリティの見積もりが正しくない場合、クエリ作業を実行する並列スレッドには、実行する作業の量が不均一になります。典型的なケースは、1つのスレッドにすべての作業が与えられるか、他のスレッドよりもはるかに多くの作業が与えられる場合です。つまり、最も遅いスレッドの前に行の処理を終了したスレッド(指定されている場合でも)は、終了した瞬間から最も遅いスレッドが終了するまで、CXPACKETを登録します。この問題は、CXPACKET待機の爆発的な発生につながる可能性があり、一般にスキュー並列処理と呼ばれます。 、並列スレッド間の作業の分散が偏っているため、均等ではありません。
SQL Server2016SP2およびSQLServer2017 RTM CU3では、コンシューマースレッドはCXPACKET待機を登録しなくなったことに注意してください。それらはCXCONSUMER待機を登録しますが、これは無害であり、無視できます。これは、生成されるCXPACKET待機の数を減らすためであり、残りの待機は実行可能である可能性が高くなります。
歪んだ並列処理の例
そのようなケースを特定する方法を示すために、考案された例を見ていきます。
まず、 UPDATE STATISTICS の行とページの数を手動で設定して、テーブルの統計が非常に不正確になるシナリオを作成します。 ステートメント(本番環境ではこれを行わないでください!):
USE [master]; GO IF DB_ID (N'ExecutionMemory') IS NOT NULL BEGIN ALTER DATABASE [ExecutionMemory] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; DROP DATABASE [ExecutionMemory]; END GO CREATE DATABASE [ExecutionMemory]; GO USE [ExecutionMemory]; GO CREATE TABLE dbo.[Test] ( [RowID] INT IDENTITY, [ParentID] INT, [CurrentValue] NVARCHAR (100), CONSTRAINT [PK_Test] PRIMARY KEY CLUSTERED ([RowID])); GO INSERT INTO dbo.[Test] ([ParentID], [CurrentValue]) SELECT CASE WHEN ([t1].[number] % 3 = 0) THEN [t1].[number] – [t1].[number] % 6 ELSE [t1].[number] END, 'Test' + CAST ([t1].[number] % 2 AS VARCHAR(11)) FROM [master].[dbo].[spt_values] AS [t1] WHERE [t1].[type] = 'P'; GO UPDATE STATISTICS dbo.[Test] ([PK_Test]) WITH ROWCOUNT = 10000000, PAGECOUNT = 1000000; GO
したがって、私のテーブルには数千行しかありませんが、1,000万行あるように偽造しました。
次に、上位500行を選択するための工夫されたクエリを作成します。これは、スキャンする行が数百万行あると見なされるため、並行して実行されます。
USE [ExecutionMemory]; GO SET NOCOUNT ON; GO DECLARE @CurrentValue NVARCHAR (100); WHILE (1=1) SELECT TOP (500) @CurrentValue = [CurrentValue] FROM dbo.[Test] ORDER BY NEWID() DESC; GO
そして、それを実行するように設定します。
CXPACKET待機の表示
これで、簡単なスクリプトを使用して発生しているCXPACKET待機を確認し、 sys.dm_os_waiting_tasksを確認できます。 DMV:
SELECT [owt].[session_id], [owt].[exec_context_id], [owt].[wait_duration_ms], [owt].[wait_type], [owt].[blocking_session_id], [owt].[resource_description], [er].[database_id], [eqp].[query_plan] FROM sys.dm_os_waiting_tasks [owt] INNER JOIN sys.dm_exec_sessions [es] ON [owt].[session_id] = [es].[session_id] INNER JOIN sys.dm_exec_requests [er] ON [es].[session_id] = [er].[session_id] OUTER APPLY sys.dm_exec_sql_text ([er].[sql_handle]) [est] OUTER APPLY sys.dm_exec_query_plan ([er].[plan_handle]) [eqp] WHERE [es].[is_user_process] = 1 ORDER BY [owt].[session_id], [owt].[exec_context_id];
これを数回実行すると、最終的には偏った並列処理を示す結果が表示されます(わかりやすくするために、クエリプランのハンドルリンクを削除し、リソースの説明を省略しました。必要に応じて、SQLテキストを取得するためのコードを挿入しました。あまりにも):
session_id | exec_context_id | wait_duration_ms | wait_type | blocking_session_id | resource_description | database_id |
---|---|---|---|---|---|---|
56 | 0 | 1 | CXPACKET | NULL | exchangeEvent | 13 |
56 | 1 | 1 | CXPACKET | 56 | exchangeEvent | 13 |
56 | 3 | 1 | CXPACKET | 56 | exchangeEvent | 13 |
56 | 4 | 1 | CXPACKET | 56 | exchangeEvent | 13 |
56 | 5 | 1 | CXPACKET | 56 | exchangeEvent | 13 |
56 | 6 | 1 | CXPACKET | 56 | exchangeEvent | 13 |
56 | 7 | 1 | CXPACKET | 56 | exchangeEvent | 13 |
制御スレッドは、 exec_context_idを持つスレッドです。 0に設定します。他の並列スレッドは、 exec_context_idを持つスレッドです。 0より大きく、すべてCXPACKETが1つを除いて待機していることを示しています(exec_context_id = 2
に注意してください) リストにありません)。それらはすべて独自のsession_idをリストしていることに気付くでしょう。 それらをブロックしているものとして、そしてそれは正しいです。なぜなら、すべてのスレッドが自分の session_idからの別のスレッドを待っているからです。 完了します。 database_id は、クエリが実行されているコンテキストのデータベースであり、必ずしも問題が発生しているデータベースではありませんが、クエリが3つの部分からなる名前付けを使用して別のデータベースで実行されていない限り、通常はそうです。
カーディナリティ推定問題の表示
query_planを使用 クエリ出力の列(わかりやすくするために削除しました)をクリックすると、グラフィカルプランが表示され、右クリックして[SQL SentryPlanExplorerで表示]を選択できます。これは次のように表示されます:
クラスタ化インデックススキャンの実際の行は10,000,000の推定(推定)行と比較してわずか2,048であるため、カーディナリティの推定の問題があることがすぐにわかります。
スクロールすると、使用された並列スレッド全体の行の分布を確認できます。
見よ、計画の並行部分では1つのスレッドだけが作業を行っていました。これは、 sys.dm_os_waiting_tasksに表示されなかったスレッドです。 上記の出力。
この場合、修正はテーブルの統計を更新することです。
テーブルに変更が加えられていないため、機能しない私の考案した例では、 UPDATE STATISTICS を省略して、セットアップスクリプトを再実行します。 声明。
クエリプランは次のようになります。
カーディナリティの問題も並列性の問題もない場合–問題は解決しました!
概要
CXPACKET待機が発生している場合は、上記の方法を使用して、偏った並列処理を簡単に確認できます。私が見たすべてのケースは、ある種のカーディナリティ推定の問題が原因であり、多くの場合、それは単に統計を更新するケースです。
一般的な待機統計に関する限り、パフォーマンスのトラブルシューティングにそれらを使用する方法の詳細については、次のURLを参照してください。
- 待機統計から始まる私のSQLskillsブログ投稿シリーズ、またはどこが痛いのか教えてください
- 待機タイプとラッチクラスのライブラリはこちら
- 私のPluralsightオンライントレーニングコースSQLServer:待機統計を使用したパフォーマンスのトラブルシューティング
- SQL Sentry
次回まで、トラブルシューティングをお楽しみください!