「log」という名前のテーブルがあるとします。その中には巨大なレコードがあります。
アプリケーションは通常、単純な SQL によってデータを取得します。
SELECT *
FROM log
WHERE logLevel=2 AND (creationData BETWEEN ? AND ?)
logLevel
およびcreationData
インデックスがありますが、レコードの数によってデータの取得に時間がかかります。
どうすればこれを修正できますか?
「log」という名前のテーブルがあるとします。その中には巨大なレコードがあります。
アプリケーションは通常、単純な SQL によってデータを取得します。
SELECT *
FROM log
WHERE logLevel=2 AND (creationData BETWEEN ? AND ?)
logLevel
およびcreationData
インデックスがありますが、レコードの数によってデータの取得に時間がかかります。
どうすればこれを修正できますか?
実行計画/「EXPLAIN PLAN」の結果を見てください。大量のデータを取得している場合、パフォーマンスを向上させるためにできることはほとんどありません。SELECT
ステートメントを変更して、関心のある列のみを含めることができますが、実行している論理読み取りの数は変更されないため、パフォーマンスへの影響はごくわずかであると思われます。
少数のレコードのみを取得する場合は、LogLevel のインデックスと CreationDate のインデックスでうまくいくはずです。
更新: SQL サーバーは、ほとんどの場合、大規模なデータベースの小さなサブセットをクエリすることを目的としています (たとえば、数百万のデータベースから単一の顧客レコードを返すなど)。本当に大規模なデータセットを返すように調整されているわけではありません。あなたが返すデータの量が本当に多い場合、あなたができることは一定量しかないので、私は尋ねる必要があります:
あなたが実際に達成しようとしていることは何ですか?
ユーザーにログ メッセージを表示している場合、ユーザーは一度に小さなサブセットにしか関心がないため、SQL データを効率的にページングする方法を検討することもできます。一度に記録するか、それでも非常に高速である必要があります。
ある種の統計分析を行おうとしている場合は、統計分析により適したデータ ストアにデータを複製することをお勧めします。(ただし、それは私の専門分野ではありません)
1: 絶対に使用Select *
しないでください 2: インデックスが正しく、統計が最新であることを確認してください
3: (オプション) 特定の時間以降のログ データを見ていない場合 (私の経験では、発生した場合1 週間以上前であれば、おそらくログは必要ないでしょう) それをバックアップにアーカイブするジョブを設定し、未使用のレコードを削除します。これにより、テーブルのサイズが小さくなり、テーブルの検索にかかる時間が短縮されます。
使用している SQL データベースの種類によっては、Horizaontal Partitioningを調べることができます。多くの場合、これはデータベース側で完全に実行できるため、コードを変更する必要はありません。
すべての列が必要ですか? 最初のステップは、実際に取得する必要があるものだけを選択することです。
もう 1 つの側面は、データがアプリケーションに到着した後にデータをどう処理するかです (データ セットにデータを入力する/順番に読み取る/?)。
処理アプリケーション側で改善の可能性がある場合があります。
次の質問に答える必要があります。
返されたすべてのデータを一度にメモリに保持する必要がありますか? 取得側の行ごとにどのくらいのメモリを割り当てますか? 一度にどれくらいのメモリが必要ですか? メモリを再利用できますか?
他の回答と同様に、本当にすべてのフィールドが必要な場合を除いて、「select*」は使用しないでください。
logLevelとcreationDataにはインデックスがあります
両方の値を持つ単一のインデックスが必要です。それらをどの順序で配置するとパフォーマンスに影響しますが、可能なログレベル値の数が少ない(そしてデータが歪んでいない)と仮定すると、creationDataを最初に配置するとパフォーマンスが向上します。
最適なインデックスは、log(N)へのクエリのコストを削減します。つまり、レコードの数が増えるにつれて、インデックスの速度は低下します。
C。
あなたにできることは2つあります。
日付列に基づいてテーブルを水平に分割します
事前集計の概念を使用します。
事前集計: preagg には、「logs」テーブル、「logs_temp」テーブル、「logs_summary」テーブル、および「logs_archive」テーブルがあります。logs と logs_temp テーブルの構造は同じです。アプリケーションの流れは次のようになります。すべてのログがログ テーブルに記録され、1 時間ごとに次のことを行う cron ジョブが実行されます。
a. ログ テーブルから「logs_temp」テーブルにデータをコピーし、ログ テーブルを空にします。これは、Shadow Table トリックを使用して実行できます。
b. logs_temp テーブルからその特定の時間のログを集計します
c. 集計結果を集計表に保存する
d. レコードを logs_temp テーブルから logs_archive テーブルにコピーしてから、logs_temp テーブルを空にします。
このようにして、結果は要約表に事前に集計されます。
結果を選択したいときはいつでも、要約テーブルから選択します。
この方法では、データが 1 時間ごとに事前に集計されているため、レコード数がはるかに少ないため、選択は非常に高速です。しきい値を 1 時間から 1 日に増やすこともできます。それはすべてあなたのニーズに依存します。
ログ テーブルには過去 1 時間分のデータしか保持されないため、ログ テーブルのデータ量はそれほど多くないため、挿入も高速になります。挿入を高速にします。
シャドー テーブル トリックの詳細については、こちらをご覧ください。
wordpressで構築したニュースサイトで事前集計方式を採用しました。最近人気のある (過去 3 日間に人気のある) ニュース項目を表示するニュース Web サイト用のプラグインを開発する必要があり、1 日あたり 10 万件のヒットがあり、この事前集計機能は非常に役立ちました。クエリ時間は 2 秒以上から 1 秒未満に短縮されました。プラグインを近日中に公開する予定です。
いくつかのこと
すべての列が必要ですか?SELECT *
テーブルにある 15 列のうち 5 列をリストするのが面倒なので、通常はそうします。
RAM を増やしてください。RAM が多ければ多いほど、より多くのデータをキャッシュに保存できます。これは、ディスクから読み取るよりも 1000 倍高速です。
creationData
私はあなたが意味することを本当に願っていますcreationDate
。
まず、 と にインデックスを付けるだけでは十分ではありません。2 つの個別のインデックスがある場合、Oracle は 1 つしか使用できません。必要なのは、両方のフィールドの単一のインデックスです。logLevel
creationData
CREATE INDEX i_log_1 ON log (creationData, logLevel);
最初に creationData を置いていることに注意してください。このように、そのフィールドを WHERE 句に入れるだけでも、インデックスを使用できます。(日付のみでのフィルタリングは、ログ レベルのみでのシナリオの可能性が高いようです)。
次に、テーブルにデータ (本番環境で使用するのと同じ量のデータ) が入力されていることを確認し、テーブルの統計を更新します。
テーブルが大きい場合 (少なくとも数十万行)、次のコードを使用して統計を更新します。
DECLARE
l_ownname VARCHAR2(255) := 'owner'; -- Owner (schema) of table to analyze
l_tabname VARCHAR2(255) := 'log'; -- Table to analyze
l_estimate_percent NUMBER(3) := 5; -- Percentage of rows to estimate (NULL means compute)
BEGIN
dbms_stats.gather_table_stats (
ownname => l_ownname ,
tabname => l_tabname,
estimate_percent => l_estimate_percent,
method_opt => 'FOR ALL INDEXED COLUMNS',
cascade => TRUE
);
END;
それ以外の場合、テーブルが小さい場合は使用します
ANALYZE TABLE log COMPUTE STATISTICS FOR ALL INDEXED COLUMNS;
さらに、テーブルが大きくなる場合は、creationDate 列の範囲で分割することを検討する必要があります。詳細については、次のリンクを参照してください。