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])
その他
警告: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時間で完了します。
変更の概要
SAFE
なしのTimeZoneInfo
実装)。新しいインデックスを追加しました:
CREATE NONCLUSTERED INDEX [inx_MyIndex] ON [dbo] .MyTable(TypeNumber ASC、SessionId ASC)INCLUDE(SessionName、Price、Timestamp、Volume])
SessionNameは実際にはインデックスのキーとして優れていますが、CLR関数であるため、正確で確定的ではありますが、永続化しない限りキーにすることはできず、その列はほとんど静的ですが、十分ではありません。永続化する静的。
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
何よりもまず、12時間以上のクエリを削減するためにいくつかのことを試す必要があると思います。
最初にチェックするのはインデックスです。 TypeNumber, SessionId, dbo.udf_SessionName([timestamp])
にGROUP BYがありますが、インデックスは_SessionId asc, INCLUDE TypeNumber,Timestamp,Price
_にあります。つまり、順序は同じではありません(そのため、おそらくインデックスが無視され、テーブルがスキャンされているのです)。少なくとも、これらの列の_TypeNumber, SessionId
_の順序と一致するように_GROUP BY
_で開始するには、インデックスが必要です。そしてINCLUDE ([price], [timestamp], [EventNumber])
をカバーするインデックスにします。インデックスについて言うべきことは他にもありますが、それは次の部分につながります...
次はスカラーUDFです。これらはいくつかの理由で悪いことが知られています。また、_AT TIME ZONE
_の使用は、それほど高速ではありません。だから、考慮してください:
これをインラインTVFに変換すると、通常は不思議に機能しますが、それがインデックスに最適なものと競合するかどうかはわかりません
スカラーUDFが遅くなる原因の1つは、並列プランが妨げられることです。 DataAccess
およびSystemDataAccess
としてマークされたSQLCLRスカラーUDF = none
AND _IsDeterministic=true
_ されない並列プランを防止:-) 。 UTCから現地時間(またはその逆)に変換する場合は、SAFE
アセンブリで呼び出すことができるクラスを使用できます。さまざまなタイムゾーンから変換する必要がある場合は、TimezoneInfo
クラスを使用する必要があります(私はそう思います)。これには、アセンブリをUNSAFE
としてマークする必要があります。 UNSAFE
であっても、並列プランを許可するメリットは失われませんが、SAFE
メソッドを使用できる場合は、それを実行してください。
SQLCLR UDFアプローチで少し問題になるのは、テーブルへのルックアップを実行していることです:MarketSessions
。これはわずか5行だとおっしゃいました。これらの5行はかなり静的ですか?その場合は、おそらく、アセンブリに静的コレクションを作成してnotでデータアクセスを実行し、静的クラスコンストラクターのテーブルからデータを取り込むSQLCLR UDFを使用することで問題を回避できます。静的クラスコンストラクターは、アセンブリが読み込まれるたびに実行され、_udf_SessionName
_ UDFでのチェックに必要な値をコレクションに事前入力できます。唯一の問題は、静的クラスコンストラクターで使用可能な内部_context connection
_がないため、アセンブリを_EXTERNAL_ACCESS
_としてマークする必要があります。しかし、UDFはSqlConnection
を呼び出さず、静的コレクションから読み取るだけです:-)。
MarketSessions
内の値のほうが揮発性が高い場合は、常に同じメソッドを呼び出すSQLCLR UDFまたはストアドプロシージャを作成して、クラスコンストラクターが呼び出す静的コレクションにデータを設定できます。次に、このクエリを実行する直前にそれを実行して、内部静的コレクションにそのテーブルの「現在の」レコードが含まれるようにします。ただし、この場合、インデックス付きの値が古くなっている/正しくない可能性があるため、おそらく次の2つの手順を実行できません。しかし、それでも並行プランを作成できるというメリットは得られます。
(まだ追加していない場合は)_WITH SCHEMABINDING
_をT-SQL UDFに追加するかどうかに関係なくor上記のように設定された属性を持つSQLCLR UDFに変換するには、列を追加する必要がありますテーブルに対して、UDFへの呼び出しにすぎない非永続計算列になるようにします。
NONpersisted計算列が存在する場合、TypeNumber, SessionId, computedColumn INCLUDE ([price], [timestamp], [EventNumber])
に実際のインデックスを作成できます。 might SQLCLR UDFをSqlFunction
属性_IsPrecise=true
_にも設定して、インデックス付け可能にする必要があります。
あなたmightISNULL
を再考する必要があります。 price
は単なるINCLUDE
列である可能性があるため、ISNULL関数はここでのインデックスの使用に問題を生じない可能性がありますが、_price <> 0 AND price IS NOT NULL
_に分割する必要がある場合があります。
全体的なパフォーマンスへの影響はわかりませんが、_SELECT...INTO
_構造のファンではありません。最初にテーブルを作成してから_INSERT INTO...SELECT
_を実行することをお勧めします。
@LowlyDBAが質問のコメントで述べたように、財務値にREAL
(またはFLOAT
)を使用する場合は注意が必要です。はい、コンパクトになり、転送が速くなりますが、ローエンドで余分な値が得られることもあります。あなたが計算をしているなら、私は確かにそのデータ型を使用しません。ただし、単にアプリに戻るだけで十分な場合もあります。ただし、計算を行う場合は、DECIMAL()
またはMONEY
を使用する必要があります。