テーブルがあり、「foos」と呼びましょう。約600万レコードが含まれています。次のクエリを実行しています。
SELECT "foos".*
FROM "foos"
INNER JOIN "bars" ON "foos".bar_id = "bars".id
WHERE (("bars".baz_id = 13266))
ORDER BY "foos"."id" DESC
LIMIT 5 OFFSET 0;
このクエリの実行には非常に長い時間がかかります(Railsは実行中にタイムアウトします)。問題のすべてのIDにインデックスがあります。興味深いのは、ORDER BY
句またはLIMIT
句を使用すると、ほとんど瞬時に実行されます。
両方の存在を前提としていますORDER BY
とLIMIT
により、PostgreSQLはクエリ計画でいくつかの悪い選択をします。誰でもこれを修正する方法について何かアイデアがありますか?
それが役立つ場合は、3つのケースすべてのEXPLAIN
を次に示します。
//////// Both ORDER and LIMIT
SELECT "foos".*
FROM "foos"
INNER JOIN "bars" ON "foos".bar_id = "bars".id
WHERE (("bars".baz_id = 13266))
ORDER BY "foos"."id" DESC
LIMIT 5 OFFSET 0;
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------
Limit (cost=0.00..16663.44 rows=5 width=663)
-> Nested Loop (cost=0.00..25355084.05 rows=7608 width=663)
Join Filter: (foos.bar_id = bars.id)
-> Index Scan Backward using foos_pkey on foos (cost=0.00..11804133.33 rows=4963477 width=663)
Filter: (((NOT privacy_protected) OR (user_id = 67962)) AND ((status)::text = 'DONE'::text))
-> Materialize (cost=0.00..658.96 rows=182 width=4)
-> Index Scan using index_bars_on_baz_id on bars (cost=0.00..658.05 rows=182 width=4)
Index Cond: (baz_id = 13266)
(8 rows)
//////// Just LIMIT
SELECT "foos".*
FROM "foos"
INNER JOIN "bars" ON "foos".bar_id = "bars".id
WHERE (("bars".baz_id = 13266))
LIMIT 5 OFFSET 0;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------
Limit (cost=0.00..22.21 rows=5 width=663)
-> Nested Loop (cost=0.00..33788.21 rows=7608 width=663)
-> Index Scan using index_bars_on_baz_id on bars (cost=0.00..658.05 rows=182 width=4)
Index Cond: (baz_id = 13266)
-> Index Scan using index_foos_on_bar_id on foos (cost=0.00..181.51 rows=42 width=663)
Index Cond: (foos.bar_id = bars.id)
Filter: (((NOT foos.privacy_protected) OR (foos.user_id = 67962)) AND ((foos.status)::text = 'DONE'::text))
(7 rows)
//////// Just ORDER
SELECT "foos".*
FROM "foos"
INNER JOIN "bars" ON "foos".bar_id = "bars".id
WHERE (("bars".baz_id = 13266))
ORDER BY "foos"."id" DESC;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------
Sort (cost=36515.17..36534.19 rows=7608 width=663)
Sort Key: foos.id
-> Nested Loop (cost=0.00..33788.21 rows=7608 width=663)
-> Index Scan using index_bars_on_baz_id on bars (cost=0.00..658.05 rows=182 width=4)
Index Cond: (baz_id = 13266)
-> Index Scan using index_foos_on_bar_id on foos (cost=0.00..181.51 rows=42 width=663)
Index Cond: (foos.bar_id = bars.id)
Filter: (((NOT foos.privacy_protected) OR (foos.user_id = 67962)) AND ((foos.status)::text = 'DONE'::text))
(8 rows)
LIMITとORDER BYの両方がある場合、オプティマイザは、残りの基準で5つの一致が得られるまで、キーの降順でfooのフィルタリングされていないレコードをリンプする方が速いと判断しました。それ以外の場合は、ネストされたループとしてクエリを実行し、すべてのレコードを返します。
問題は、PGがさまざまなIDのjointディストリビューションを処理しないことであり、そのため、計画は非常に最適ではありません。
考えられる解決策:最近ANALYZEを実行したと仮定します。そうでない場合は、そうします。これは、高速に戻るバージョンでも推定時間が長い理由を説明している可能性があります。問題が解決しない場合は、おそらくORDER BYを副選択として実行し、外側のクエリでLIMITをオンにします。
クエリプランがフィルターを示しています
(((NOT privacy_protected) OR (user_id = 67962)) AND ((status)::text = 'DONE'::text))
sELECTに表示されないもの-それはどこから来たのですか?
また、式は「フィルター」としてリストされており、「インデックス条件」ではなく、それに適用されているインデックスがないことを示しているように見えることに注意してください。
注文する前に選択するために発生する可能性があります。結果を外側のすべて選択でソートしようとしないのはなぜですか?のようなもの:SELECT * FROM(SELECT ... INNER JOIN ETC ...)ORDER BY ... DESC
「foos」で全表スキャンを実行している可能性があります。テーブルの順序を変更して、代わりに内部結合ではなく左結合を使用して、結果がより速く表示されるかどうかを確認しましたか。
いう...
SELECT "bars"."id", "foos".*
FROM "bars"
LEFT JOIN "foos" ON "bars"."id" = "foos"."bar_id"
WHERE "bars"."baz_id" = 13266
ORDER BY "foos"."id" DESC
LIMIT 5 OFFSET 0;