web-dev-qa-db-ja.com

SQL Server 2016の同時実行制限? DB同時実行性のチューニング

10億回程度のタイムスタンプ付きレコードのテーブルがあり、各レコードはセッションテーブルへのFK(1日あたり1セッション&1日あたり3-500,000レコード)を保持しているため、特定の日のレコードを検索することは、単純な整数結合です。

この表のデータ(セッションごとにグループ化されたデータ)を分析しようとしています。クライアントマシンからC#コンソールアプリを使用すると、70分で完全な分析(すべてのレコード)を実行できます。 TSQLで同様の分析を直接実行しようとすると、12時間以上かかります。 TSQLクエリはスカラー関数とカスタム集計(clr)を使用するため、多少のペナルティが予想されます。

私の質問:C#では、同時実行を最大化および調整する方法を理解しているため、70分は調整された数値です。 SQLで最大同時実行性のクエリを直接調整することは可能ですか、それともC#APIに任せた方がよいですか? (私はこの作業をR、db、または外部で行うこともできますが、.Net同時実行APIの方が優れています。)

クエリ:

SELECT TypeNumber, SessionId, dbo.udf_SessionName([timestamp]) SessionName, 
CAST(max(price)-min(price) AS REAL) as Variance, sum(EventNumber) as Volume, 
dbo.Direction(price,[timestamp]) as MoveDirection
INTO temp.AnalysisResults
FROM MyTable
WHERE ISNULL(price,0)<>0
GROUP BY TypeNumber, SessionId, dbo.udf_SessionName([timestamp])

その他

  • 挿入により、このクエリで一括ログ記録が有効になりました
  • 主キーはこのクエリでは使用されません(3つのフィールドにまたがる複合キーであり、ここでは必要ありません。ただし、クエリプランは、このインデックスがスキャンされていることを示しています。以下で言及するインデックスではありません(プランで最初にお勧めしました)。 )。
  • 行レベルの圧縮が有効になっています
  • データは5年間で、各月の読み取り専用ファイルグループ(月ごとに分割)があります。すべてのファイルグループは同じSSDに常駐します(すばらしいとは言えません)
  • インデックス:TypeNumber、Timestamp、Priceを含むSessionId ascで非クラスター化
  • 4つのCPUコアが利用可能
  • スカラー関数は各タイムスタンプを受け取り、それをAT TIME ZONE(2回の呼び出し)でローカル時間に変換し、5レコードのテーブルで検索します。
  • カスタム集計は、10進数とdatetime2を取り込んで文字列を返すカスタムシリアライザーを使用します。シリアライザは、解析する必要がある文字列を渡します(これは素晴らしいことではありません)。
  • クエリプラン(挿入を削除した)を見ると、これまでで最も負荷の高い操作は並べ替えです(コストの98%、明示的に開始した唯一の並べ替えはclrアグリゲーター関数内です): query plan

警告:CLR集計を使用すると、クエリ時間と圧縮にコストがかかることを知っています。コンソールアプリを使用する場合、すべての分析作業をより強力なマシンにオフロードして、dbサーバーにIOのみを任せることができます。これは「明白な答え」ですか、それとも私はこの作業のほとんどをデータベースに保持できますか(通常、データベースで直接実行できるほど、優れています)。


このデータベースの設定方法とその圧縮設定により、CPUよりもIOの方がチューニングされていることがわかります。純粋なパフォーマンスと同等のパフォーマンスを期待することはできません。 dbがIOのみを実行するCソリューションですが、dbが実行できるCPU作業を最大化することで得られることはたくさんあります。

udf_SessionName:

create function dbo.[udf_SessionName](@timestamp datetime2)
returns nvarchar(100)
begin
declare @localTime time = CAST(@timestamp at time zone 'UTC' at time zone 'Pacific Standard Time' as time)
declare @result nvarchar(100) = (select top 1 sessionname from MarketSessions where @localTime>=StartTime and @localTime < EndTime)
if (@result is null) set @result = 'European'
return @result
end

SQL Fiddle のテーブル構造


アクション後のレポート:@SolomonRutzkyの提案を実装しました。クエリは12時間以上に対して3時間で完了します。

変更の概要

  1. タイムゾーンの操作をスカラーudfからclr関数に変更しました(SAFEなしのTimeZoneInfo実装)。
  2. そのclr関数を非永続計算列にロールバックしました。
  3. 新しいインデックスを追加しました:

    CREATE NONCLUSTERED INDEX [inx_MyIndex] ON [dbo] .MyTable(TypeNumber ASC、SessionId ASC)INCLUDE(S​​essionName、Price、Timestamp、Volume])

SessionNameは実際にはインデックスのキーとして優れていますが、CLR関数であるため、正確で確定的ではありますが、永続化しない限りキーにすることはできず、その列はほとんど静的ですが、十分ではありません。永続化する静的。

  1. 削除済みISNULL

変更されたクエリ

INSERT INTO temp.AnalysisResults
SELECT TypeNumber, SessionId, SessionName, 
CAST(max(price)-min(price) AS REAL) as Variance, sum(EventNumber) as Volume, 
dbo.Direction(price,[timestamp]) as MoveDirection
FROM MyTable
WHERE price <> 0 AND price IS NOT NULL
GROUP BY TypeNumber, SessionId, SessionName

新しい実行プランUpdated Execution Plan

6
K_foxer9

何よりもまず、12時間以上のクエリを削減するためにいくつかのことを試す必要があると思います。

  1. 最初にチェックするのはインデックスです。 TypeNumber, SessionId, dbo.udf_SessionName([timestamp])にGROUP BYがありますが、インデックスは_SessionId asc, INCLUDE TypeNumber,Timestamp,Price_にあります。つまり、順序は同じではありません(そのため、おそらくインデックスが無視され、テーブルがスキャンされているのです)。少なくとも、これらの列の_TypeNumber, SessionId_の順序と一致するように_GROUP BY_で開始するには、インデックスが必要です。そしてINCLUDE ([price], [timestamp], [EventNumber])をカバーするインデックスにします。インデックスについて言うべきことは他にもありますが、それは次の部分につながります...

  2. 次はスカラーUDFです。これらはいくつかの理由で悪いことが知られています。また、_AT TIME ZONE_の使用は、それほど高速ではありません。だから、考慮してください:

    1. これをインラインTVFに変換すると、通常は不思議に機能しますが、それがインデックスに最適なものと競合するかどうかはわかりません

    2. スカラーUDFが遅くなる原因の1つは、並列プランが妨げられることです。 DataAccessおよびSystemDataAccessとしてマークされたSQLCLRスカラーUDF = none AND _IsDeterministic=true_ されない並列プランを防止:-) 。 UTCから現地時間(またはその逆)に変換する場合は、SAFEアセンブリで呼び出すことができるクラスを使用できます。さまざまなタイムゾーンから変換する必要がある場合は、TimezoneInfoクラスを使用する必要があります(私はそう思います)。これには、アセンブリをUNSAFEとしてマークする必要があります。 UNSAFEであっても、並列プランを許可するメリットは失われませんが、SAFEメソッドを使用できる場合は、それを実行してください。

    3. SQLCLR UDFアプローチで少し問題になるのは、テーブルへのルックアップを実行していることです:MarketSessions。これはわずか5行だとおっしゃいました。これらの5行はかなり静的ですか?その場合は、おそらく、アセンブリに静的コレクションを作成してnotでデータアクセスを実行し、静的クラスコンストラクターのテーブルからデータを取り込むSQLCLR UDFを使用することで問題を回避できます。静的クラスコンストラクターは、アセンブリが読み込まれるたびに実行され、_udf_SessionName_ UDFでのチェックに必要な値をコレクションに事前入力できます。唯一の問題は、静的クラスコンストラクターで使用可能な内部_context connection_がないため、アセンブリを_EXTERNAL_ACCESS_としてマークする必要があります。しかし、UDFはSqlConnectionを呼び出さず、静的コレクションから読み取るだけです:-)。

      MarketSessions内の値のほうが揮発性が高い場合は、常に同じメソッドを呼び出すSQLCLR UDFまたはストアドプロシージャを作成して、クラスコンストラクターが呼び出す静的コレクションにデータを設定できます。次に、このクエリを実行する直前にそれを実行して、内部静的コレクションにそのテーブルの「現在の」レコードが含まれるようにします。ただし、この場合、インデックス付きの値が古くなっている/正しくない可能性があるため、おそらく次の2つの手順を実行できません。しかし、それでも並行プランを作成できるというメリットは得られます。

    4. (まだ追加していない場合は)_WITH SCHEMABINDING_をT-SQL UDFに追加するかどうかに関係なくor上記のように設定された属性を持つSQLCLR UDFに変換するには、列を追加する必要がありますテーブルに対して、UDFへの呼び出しにすぎない非永続計算列になるようにします。

    5. NONpersisted計算列が存在する場合、TypeNumber, SessionId, computedColumn INCLUDE ([price], [timestamp], [EventNumber])に実際のインデックスを作成できます。 might SQLCLR UDFをSqlFunction属性_IsPrecise=true_にも設定して、インデックス付け可能にする必要があります。

  3. あなたmightISNULLを再考する必要があります。 priceは単なるINCLUDE列である可能性があるため、ISNULL関数はここでのインデックスの使用に問題を生じない可能性がありますが、_price <> 0 AND price IS NOT NULL_に分割する必要がある場合があります。

  4. 全体的なパフォーマンスへの影響はわかりませんが、_SELECT...INTO_構造のファンではありません。最初にテーブルを作成してから_INSERT INTO...SELECT_を実行することをお勧めします。

  5. @LowlyDBAが質問のコメントで述べたように、財務値にREAL(またはFLOAT)を使用する場合は注意が必要です。はい、コンパクトになり、転送が速くなりますが、ローエンドで余分な値が得られることもあります。あなたが計算をしているなら、私は確かにそのデータ型を使用しません。ただし、単にアプリに戻るだけで十分な場合もあります。ただし、計算を行う場合は、DECIMAL()またはMONEYを使用する必要があります。

9
Solomon Rutzky