大規模なデータセットでの時間ごとのグループ化


12

MS SQL 2008を使用して、250万件のレコードから平均フィールドを選択しています。各レコードは1秒を表します。MyFieldは、これらの1秒のレコードの1時間ごとの平均です。もちろん、サーバーのCPUが100%に達し、選択に時間がかかりすぎます。SQLが各リクエストでこれらのレコードをすべて選択する必要がないように、これらの平均値を保存する必要があります。何ができますか?

  SELECT DISTINCT
         CONVERT(VARCHAR, [timestamp], 1)+' '+ CAST(DATEPART(Hh,[timestamp]) as VARCHAR) AS TimeStampHour,
         MIN([timestamp]) as TimeStamp,
         AVG(MyField) As AvgField
    FROM MyData
   WHERE TimeStamp > '4/10/2011'
GROUP BY CONVERT(VARCHAR, [timestamp], 1)+' '+ CAST(DATEPART(Hh,[timestamp]) as VARCHAR)
ORDER BY TimeStamp

6
タイムスタンプはクラスター化インデックスの一部ですか?それは

@antisanity-なぜですか?彼はディスクioではなくCPUを
使い果たしている

回答:


5

クエリの一部が長時間CPUを使い果たしているのは、GROUP BY句の機能と、このインスタンスでは常にインデックスなしの並べ替えが必要になるという事実です。タイムスタンプフィールドのインデックスは、最初のフィルターに役立ちますが、フィルターが一致するすべての行でこの操作を実行する必要があります。これを高速化すると、より効率的なルートを使用して、Alexが提案するのと同じジョブを実行できますが、クエリプランナーを使用する関数の組み合わせが思い付かないため、依然として非常に非効率です。インデックスの助けとなるものであるため、最初に関数を実行してグループ化値を計算するすべての行を実行する必要があり、その後のみデータを順序付け、結果のグループ化に対して集計を計算できます。

そのため、解決策は、何らかの方法でインデックスを使用できるプロセスグループを作成するか、一致するすべての行を一度に考慮する必要をなくすことです。

時間に丸められた時間を含む行ごとに追加の列を維持し、そのようなクエリで使用するためにこの列にインデックスを付けることができます。これはデータの非正規化であるため、「ダーティ」な感じがするかもしれませんが、将来の使用のためにすべての集計をキャッシュする(およびベースデータが変更されるとそのキャッシュを更新する)よりもきれいになります。追加の列は、データを挿入したり、タイムスタンプ列または既存の行を更新する可能性のある現在および将来のすべての場所で新しいデータの一貫性のあるデータを保証するため、他の場所のロジックではなく、トリガーまたは永続的な計算列で維持する必要がありますカラム。MIN(タイムスタンプ)は引き続き取得できます。この方法でクエリが発生するのは、まだすべての行をたどることです(これは避けられないことは明らかです)が、インデックスの順序はできますが、グループ化/集計を実行する前に、インデックスなしの並べ替え操作の行セット全体を覚える必要はなく、インデックス内の次の値に到達するたびに各グループ化の行を出力します。また、以前にグループ化した値の行を覚えておく必要がないため、現在使用している行または残りの行を処理するため、メモリの使用量も少なくなります。

そのメソッドは、結果セット全体のメモリのどこかを見つける必要性を取り除き、グループ操作のインデックスなしソートを行い、大きなクエリからグループ値の計算を削除します(そのジョブを生成する個々のINSERT / UPDATEに移動します)データ)、集計された結果の個別のストアを維持する必要なく、そのようなクエリを許容できる程度に実行できるようにする必要があります。

しない方法データを非正規化しますが、それでも追加の構造が必要な場合は、「タイムテーブル」を使用します。この場合、考慮する可能性のあるすべての時間について1時間に1行が含まれます。このテーブルは、DBまたはかなりのサイズで大量のスペースを消費しません-100年のタイムスパンをカバーするために、2つの日付(「2011-01-01 @ 00:00:00.0000 '、' 2011-01-01 @ 00:00:59.9997 '、 "9997"は、DATETIMEフィールドが次の秒に切り上げられない最小ミリ秒数です)クラスター化された主キーには、最大14 MBのスペースが必要です(行ごとに8 + 8バイト* 24時間/日* 365.25日/年* 100に加えて、クラスター化インデックスのツリー構造のオーバーヘッド用のビットが必要ですが、そのオーバーヘッドは大きくありません) 。

SELECT CONVERT(VARCHAR, [timestamp], 1)+' '+ CAST(DATEPART(Hh,[timestamp]) as VARCHAR) AS TimeStampHour
     , MIN([timestamp]) as TimeStamp
     , AVG(MyField) As AvgField
FROM TimeRangeByHours tt
INNER JOIN MyData md ON md.TimeStamp BETWEEN tt.StartTime AND tt.EndTime
WHERE tt.StartTime > '4/10/2011'
GROUP BY tt.StartTime
ORDER BY tt.StartTime

これは、クエリプランナーが使用するMyData.TimeStampのインデックスを調整できることを意味します。クエリプランナーは、MyData.TimeStampのインデックスと調和して飼いならされたテーブルをたどることができるように十分に明るく、グループ化ごとに1行を出力し、次のグループ化値に達すると各セットまたは行を破棄する必要があります。すべての中間行をRAMのどこかに保存してから、インデックスなしの並べ替えを実行することはありません。もちろん、この方法ではタイムテーブルを作成し、前後に十分に及ぶことを確認する必要がありますが、タイムテーブルを使用して、さまざまなクエリの多くの日付フィールドに対するクエリを使用できます。この方法でフィルタリング/グループ化する必要がある各日付フィールドの追加の計算列、およびテーブルのサイズが小さい(10に及ぶ必要がない限り)

タイムテーブルメソッドには、現在の状況と計算列ソリューションと比較して(非常に有利な場合があります)余分な違いがあります。上記のクエリ例のINNER JOINを変更するだけで、データがない期間の行を返すことができます左外側になります。

一部の人々は、物理的なタイムテーブルを持たず、代わりに常にテーブルを返す関数からそれを返すことを提案します。これは、タイムテーブルの内容がディスクに保存されない(またはディスクから読み取る必要がない)ことを意味し、関数が適切に記述されていれば、タイムテーブルが時間内を行き来する時間を心配する必要はありませんが、すべてのクエリでメモリ内テーブルを作成するためのCPUコストは、物理タイムテーブルを作成する(および、初期​​バージョンの制限を超えてタイムスパンを延長する必要がある場合に維持する)面倒な労力を少し節約する価値があります。

補足説明:元のクエリにもDISTINCT句は必要ありません。グループ化により、これらのクエリは考慮中の期間ごとに1行のみを返すようになるため、DISTINCTはCPUをもう少しスピンする以外は何もしません(クエリプランナーが、この区別がノーオペレーションであることに気付かない限り)無視して、余分なCPU時間を使用しないでください)。


3

この質問を参照してください(日付を床にする)また、なぜすべてを文字列に変換するのが面倒なのか-後で行うことができます(必要な場合)。

  SELECT DISTINCT
         dateadd(hour,datediff(hour,0,[timestamp]),0) AS TimeStampHour,
         MIN([timestamp]) as TimeStamp,
         AVG(MyField) As AvgField
    FROM MyData
   WHERE TimeStamp > '4/10/2011'
GROUP BY dateadd(hour,datediff(hour,0,[timestamp],0);
ORDER BY TimeStamp

1

クエリを高速化したいですか、それともデータのスナップショットを作成して保存する方法を尋ねていますか?

より高速にしたい場合は、タイムスタンプフィールドのインデックスが必ず必要です。また、これを使用して時間に変換することをお勧めします:

select convert(varchar(13), getdate(), 121)

スナップショットinsert intoを作成して再利用する必要がある場合は、クエリの結果を使用して新しいテーブルを作成するために後で使用します。インデックステーブルに従って使用します。私が理解していることから、TimeStampHourのインデックスが必要になります。

また、新しい集計テーブルに毎日のデータを集計するジョブを設定できます。


-1

group by句をそのような文字列に変換することにより、基本的にデータベース内のすべての行に対するインデックスなしのヒットにします。これはあなたのパフォーマンスを殺しているものです。適切な中途半端なサーバーは、インデックスが適切に使用されていれば、100万レコードのような単純な集計を処理できます。クエリを変更し、タイムスタンプにクラスター化インデックスを配置します。これでパフォーマンスの問題が解決しますが、1時間ごとにデータを計算するだけで問題は解決します。


1
-1-いいえ、「データベース内のすべての行へのインデックスなしヒットにする」ではありません-インデックスTimeStampは引き続き行のフィルタリングに使用されます
ジャックはtry topanswers.xyz

-3

リレーショナルデータベースモデルを使用してこの種の計算を実装するという考えを放棄することを検討します。特に、毎秒値を収集する多くのデータポイントがある場合。

お金があれば、次のような専用のプロセスデータヒストリアンの購入を検討できます。

  1. ハネウェルユニフォームPHD
  2. Osisoft PI
  3. Aspentech IP21

これらの製品は、膨大な量の非常に高密度の時系列データを(独自の形式で)格納すると同時に、データ抽出クエリの迅速な処理を可能にします。クエリでは、多くのデータポイント(タグとも呼ばれます)、長い時間間隔(月/年)を指定でき、さらに、さまざまな要約データの計算(平均を含む)を実行できます。

..および一般的な注意事項:私は常に、DISTINCTSQLを記述するときにキーワードを使用しないようにしています。これは決して良い考えではありません。あなたの場合は、句にDISTINCT追加MIN([timestamp])することで、同じ結果をドロップして取得できるはずGROUP BYです。


1
これは本当に正確ではありません。リレーショナルデータベースは、250万件のレコードに対してまったく問題ありません。そして、彼は多くのテーブルで結合さえしていません。データを非正規化するか、非リレーショナルシステムに移行する必要があることの最初の兆候は、多くのテーブルで大規模で複雑な結合を行っている場合です。実際、ポスターのデータセットは、リレーショナルデータベースシステムの完全に受け入れられる使用のように聞こえます。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.