web-dev-qa-db-ja.com

2つの大きなテーブルでクエリを最適化する

システムに非常に重要なクエリがあり、テーブルに大量のデータがあるため、実行に時間がかかりすぎています。私はジュニアDBAであり、これを実現するために最適な最適化が必要です。テーブルにはそれぞれ約8000万行があります。

テーブルは次のとおりです。

tb_pd

   Column            |  Type   | Modifiers | Storage | Stats target | Description 
---------------------+---------+-----------+---------+--------------+-------------
 pd_id               | integer | not null  | plain   |              | 
 st_id               | integer |           | plain   |              | 
 status_id           | integer |           | plain   |              | 
 next_execution_date | bigint  |           | plain   |              | 
 priority            | integer |           | plain   |              | 
 is_active           | integer |           | plain   |              | 
Indexes:
    "pk_pd" PRIMARY KEY, btree (pd_id)
    "idx_pd_order" btree (priority, next_execution_date)
    "idx_pd_where" btree (status_id, next_execution_date, is_active)
Foreign-key constraints:
    "fk_st" FOREIGN KEY (st_id) REFERENCES tb_st(st_id)

tb_st

 Column |          Type          | Modifiers | Storage  | Stats target | Description 
--------+------------------------+-----------+----------+--------------+-------------
 st_id  | integer                | not null  | plain    |              | 
 st     | character varying(500) |           | extended |              | 
Indexes:
    "pk_st" PRIMARY KEY, btree (st_id)
Referenced by:
    TABLE "tb_pd" CONSTRAINT "fk_st" FOREIGN KEY (st_id) REFERENCES tb_st(st_id)

私のクエリは:

select s.st                                               
from tb_pd p inner join
     tb_st s on p.st_id = s.st_id
where p.status_id = 1 and
      p.next_execution_date < 1401402110830 and
      p.is_active = 1
order by priority, next_execution_date
limit 20000;

私が持っているインデックスで、私が得た最高のものは:

Limit  (cost=1.14..263388.65 rows=20000 width=45)
   ->  Nested Loop  (cost=1.14..456016201.43 rows=34627017 width=45)
         ->  Index Scan using idx_pd_order on tb_pd p  (cost=0.57..161388942.77 rows=34627017 width=16)
               Index Cond: (next_execution_date < 1401402110830::bigint)
               Filter: ((status_id = 1) AND (is_active = 1))
         ->  Index Scan using pk_st on tb_st s  (cost=0.57..8.50 rows=1 width=37)
               Index Cond: (st_id = p.st_id)

説明をよく理解できませんが、idx_pd_whereを使用してwhere句をフィルタリングしていません。 idx_pd_whereには、where句で使用されるすべての列があります。

データの詳細:
status_idは95%= 1
is_activeは90%= 1
next_execution_dateはミリ秒単位であり、さまざまです。比較される値は実行の瞬間です(現在の時間(ミリ秒))

フィルターされた列ごとに個別のインデックスを作成する必要がありますか、それとも別の種類のインデックスを使用する必要がありますか?たぶんDBMSのいくつかの設定?

4

これはトリッキーです。主な条件は_next_execution_date_ですが、出力は最初にpriorityでソートされます。 _status_id_および_is_active_の条件は、重要ではありません。

より良いインデックス

マルチカラムインデックスの先頭以外のカラムでのフィルタリングはあまり効率的ではないため、インデックス_idx_pd_order_は大きな助けにはなりません。 Postgresはそれを使用しています-順次スキャンよりもはるかに優れています。詳細はこちら:
複合インデックスは最初のフィールドのクエリにも適していますか?

_idx_pd_where_は良い選択かもしれませんが、良い選択でもありません。先頭の列_status_id_はまったく選択的ではなく、単にインデックスを膨らませます。末尾の列_is_active_についても同様です。また、priorityはインデックスに含まれておらず、テーブルからフェッチする必要があるため、インデックスのみのスキャンは不可能です。

まず、この部分的な複数列のインデックスをお勧めします。 (しかし、読み続けてください!

_CREATE INDEX idx_pd_covering ON tb_pd (next_execution_date, priority, st_id)
WHERE  status_id = 1 AND is_active = 1
_
  • _status_id = 1_および_is_active = 1_のある行のみに関心があるため、他の行をすぐにインデックスから除外します。サイズする問題。

  • 残りの(重要な)条件は_next_execution_date_であり、インデックスで最初に来る必要があります。

  • priorityと_st_id_は、可能な場合にのみ追加されます インデックスのみのスキャン (Postgres9.2 +)。それでもうまくいかない場合は、インデックスから列を削除して小さくしてください。

特別な困難

_idx_pd_covering_を使用して適格な行をすばやく見つけることができますが、残念ながらallの適格な行を確認する必要がありますpriorityが最も高いものを収集します。クエリプランが明らかにするように、Postgresは34627017行を処理すると推定します。 35M行をソートすると、コストが大きくなります。これは、冒頭で述べたトリッキーな部分です。私が話していることを示すために、クエリでEXPLAINを実行しますあり、なしpriority in _ORDER BY_:

_SELECT s.st                                               
FROM   tb_pd p
JOIN   tb_st s USING (st_id)
WHERE  p.status_id = 1
AND    p.is_active = 1
AND    p.next_execution_date < 1401402110830
ORDER  BY priority, next_execution_date
LIMIT  20000;_

それはあなたのクエリであり、わずかに簡略化されたフォーマットになっています。 hugeの違いが表示されます。

解決

ソリューションは、priorityの個別の値の数によって異なります。情報の不足とデモの目的のために、私は3つだけを想定します。優先度_1__2_および_3_

個別の優先順位値の数が少ない場合、簡単な解決策がありますthree部分インデックスを作成します。 それらすべてを合わせては、現在のインデックス_idx_pd_order_または_idx_pd_where_(これ以上必要としない場合があります)よりもまだ小さいです。

_CREATE INDEX idx_pd_covering_p1 ON tb_pd (next_execution_date, st_id)
WHERE  priority = 1 AND status_id = 1 AND is_active = 1;

CREATE INDEX idx_pd_covering_p2 ON tb_pd (next_execution_date, st_id)
WHERE  priority = 2 AND status_id = 1 AND is_active = 1;

CREATE INDEX idx_pd_covering_p3 ON tb_pd (next_execution_date, st_id)
WHERE  priority = 3 AND status_id = 1 AND is_active = 1;
_

このクエリを使用します。

_SELECT s.st
FROM  (
   (
   SELECT st_id
   FROM   tb_pd
   WHERE  status_id = 1
   AND    is_active = 1
   AND    priority  = 1
   AND    next_execution_date < 1401402110830
   ORDER  BY next_execution_date
   )
   UNION ALL
   (
   SELECT st_id
   FROM   tb_pd
   WHERE  status_id = 1
   AND    is_active = 1
   AND    priority  = 2
   AND    next_execution_date < 1401402110830
   ORDER  BY next_execution_date
   )
   UNION ALL
   (
   ...
   AND    priority  = 3
   ...
   )
   LIMIT  20000
   ) p
JOIN   tb_st s USING (st_id);_

これはdynamiteである必要があります。

  • 厳密に言うと、外部クエリに追加の_ORDER BY_句がないと、最終的な順序は保証されません。現在の実装では、外部クエリが同じくらい単純である限り、内部クエリからの順序が保持されます。 念のため、すぐに参加できます(少し遅いかもしれません):
_)
SELECT s.st
FROM   tb_pd p
JOIN   tb_st s USING (st_id)
WHERE  p.status_id = 1
AND    p.is_active = 1
AND    p.priority  = 1
AND    p.next_execution_date < 1401402110830
ORDER  BY p.next_execution_date
)
UNION ALL
(
...
)
LIMIT  20000;
_

..またはpriorityと_next_execution_date_を一緒に使用して、外側のクエリでもう一度注文します(確実に確認してください)。

  • すべての括弧が必要です! 関連回答

  • このクエリは、上記の部分インデックスの先頭からタプルを読み取るだけで、並べ替え手順はまったく必要ありません。すべての行は事前にソートされており、起動が効率的です。

  • 最後の_UNION ALL_のない_ORDER BY_クエリは、トップレベルLIMIT内の要求された行数がフェッチされるとすぐに停止します。したがって、最優先に十分な行がある場合、_UNION ALL_クエリの後続のレッグは実行されません。この方法では、小さい部分インデックスのみに触れる必要があります。

  • JOINを_tb_st_に変更すると、より効率的になります。

  • この場合も、列_st_id_は、インデックスのみのスキャンを期待してインデックスにのみ追加されます。それがうまくいくなら、クエリ全体がテーブル_tb_pd_に触れることすらありません。

任意の数の異なるpriority値の一般的な解決策

これは以前に解決しました。部分インデックスと関数の作成を自動化するための完全なレシピがあります。
空間インデックスは「範囲-並べ替え-制限」クエリに役立ちます

テーブルを最適化

パフォーマンスを最適化しようとしていて、テーブルが大きいため、テーブルのレイアウトを少し変更することをお勧めします_tb_pd_:

_   Column            |  Type
---------------------+--------
 pd_id               | integer
 st_id               | integer
 next_execution_date | bigint
 priority            | integer  -- or smallint? -- or "char"?
 status_id           | smallint -- or "char"
 is_active           | boolean_

現在の設計では60バイトが必要ですが、これはディスク上で1行あたり52バイトを占めます。インデックスも利益をもたらします。詳細:
読み取りパフォーマンスのためのPostgreSQLの構成

もちろん、すべての パフォーマンス最適化の基本的なアドバイス も適用されます。

_"char"_ について:

タイプ_"char"_(引用符に注意)は、1バイトのストレージしか使用しないという点でchar(1)とは異なります。これは、単純な列挙型としてシステムカタログで内部的に使用されます。

9

試しましたか

index(status_id, is_active,   -- in either order
      next_execution_date)    -- last, since it is a range

これは、フィルタリングで効率的になる可能性が高いです。それでも、再ソートしてLIMITを実行する必要があります。 WHEREORDER BYの両方を処理する効率的なインデックスを作成する方法はありません。

0
Rick James