システムに非常に重要なクエリがあり、テーブルに大量のデータがあるため、実行に時間がかかりすぎています。私はジュニア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%= 1is_active
は90%= 1next_execution_date
はミリ秒単位であり、さまざまです。比較される値は実行の瞬間です(現在の時間(ミリ秒))
フィルターされた列ごとに個別のインデックスを作成する必要がありますか、それとも別の種類のインデックスを使用する必要がありますか?たぶんDBMSのいくつかの設定?
これはトリッキーです。主な条件は_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)
とは異なります。これは、単純な列挙型としてシステムカタログで内部的に使用されます。
試しましたか
index(status_id, is_active, -- in either order
next_execution_date) -- last, since it is a range
これは、フィルタリングで効率的になる可能性が高いです。それでも、再ソートしてLIMIT
を実行する必要があります。 WHERE
とORDER BY
の両方を処理する効率的なインデックスを作成する方法はありません。