Optimizer: exists
現在の実装では、exists句のsubqueryはマージされないため、テーブルのJoin方法はNested loop joinになる。
Nested loop joinでは、Outer側(以下の例ではaccounts)から1行取り出すごとにsubqueryが実行されるため、Outer側のテーブルが大きい場合、検索が非常に遅くなる可能性がある。
select * from accounts a where exists (select 'X' from history h where a.aid = h.aid) Seq Scan on accounts a (cost=0.00..227655.00 rows=50000 width=100) Filter: (subplan) SubPlan -> Seq Scan on history h (cost=0.00..2.25 rows=1 width=0) Filter: ($0 = aid)
手作業でexistsをinに書き換えると以下のようになる。
select * from accounts a where a.aid in (select h.aid from history h) Hash IN Join (cost=2.25..3158.25 rows=100 width=100) Hash Cond: ("outer".aid = "inner".aid) -> Seq Scan on accounts a (cost=0.00..2655.00 rows=100000 width=100) -> Hash (cost=2.00..2.00 rows=100 width=4) -> Seq Scan on history h (cost=0.00..2.00 rows=100 width=4)
PostgreSQL 7.3まではinよりもexistsのほうが効率が良かったようであるが、7.4でinの処理が改善されため、7.4以降はinを使ったほうが効率がいいことが多いと思う。
Optimizerがexistsからinへの書き換えを行いinと同様の最適化がかかるようになればいいが、それまではexistsとinの両方を試して速い方を使うほうがいい。