convert EXSITS to inner join gotcha and bug

From: Teodor Sigaev <teodor(at)sigaev(dot)ru>
To: Pgsql Hackers <pgsql-hackers(at)postgresql(dot)org>
Subject: convert EXSITS to inner join gotcha and bug
Date: 2017-04-28 09:11:19
Message-ID: f994fc98-389f-4a46-d1bc-c42e05cb43ed@sigaev.ru
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Hi!

Seems, there two issues:

1) Sometime conditions which for a first glance could be pushed down to scan are
leaved as join quals. And it could be a ~30 times performance loss.

2) Number of query result depend on enabe_seqscan variable.

The query
explain analyze
SELECT
*
FROM
t1
INNER JOIN t2 ON (
EXISTS (
SELECT
true
FROM
t3
WHERE
t3.id1 = t1.id AND
t3.id2 = t2.id
)
)
WHERE
t1.name = '5c5fec6a41b8809972870abc154b3ecd'
;

produces following plan:
Nested Loop (cost=6.42..1928.71 rows=1 width=99) (actual time=71.415..148.922
rows=162 loops=1)
Join Filter: (t3.id1 = t1.id)
Rows Removed by Join Filter: 70368
-> Index Only Scan using t1i2 on t1 (cost=0.28..8.30 rows=1 width=66)
(actual time=0.100..0.103 rows=1 loops=1)
Index Cond: (name = '5c5fec6a41b8809972870abc154b3ecd'::text)
Heap Fetches: 1
-> Hash Join (cost=6.14..1918.37 rows=163 width=66) (actual
time=0.370..120.971 rows=70530 loops=1)
(1) Hash Cond: (t3.id2 = t2.id)
(2) -> Seq Scan on t3 (cost=0.00..1576.30 rows=70530 width=66) (actual
time=0.017..27.424 rows=70530 loops=1)
-> Hash (cost=3.84..3.84 rows=184 width=33) (actual
time=0.273..0.273 rows=184 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 20kB
-> Seq Scan on t2 (cost=0.00..3.84 rows=184 width=33) (actual
time=0.017..0.105 rows=184 loops=1)
Planning time: 7.326 ms
Execution time: 149.115 ms

Condition (1) is not pushed to scan (2) which seemsly could be safely moved.
With seqscan = off condition is not pushed too but query returns only one row
instead of 162. Scan on t3 returns ~70000 rows but only ~150 rows are really
needed. I didn't found a combination of GUCs enable_* to push down that and it
seems to me there is reason for that which I don't see or support is somehow missed.

If pair of (t3.id1, t3.id2) is unique (see dump, there is a unique index on
them) the query could be directly rewrited to inner join and its plan is:
Nested Loop (cost=9.70..299.96 rows=25 width=66) (actual time=0.376..5.232
rows=162 loops=1)
-> Nested Loop (cost=9.43..292.77 rows=25 width=99) (actual
time=0.316..0.645 rows=162 loops=1)
-> Index Only Scan using t1i2 on t1 (cost=0.28..8.30 rows=1
width=66) (actual time=0.047..0.050 rows=1 loops=1)
Index Cond: (name = '5c5fec6a41b8809972870abc154b3ecd'::text)
Heap Fetches: 1
-> Bitmap Heap Scan on t3 (cost=9.15..283.53 rows=94 width=66)
(actual time=0.257..0.426 rows=162 loops=1)
Recheck Cond: (id1 = t1.id)
Heap Blocks: exact=3
-> Bitmap Index Scan on t3i1 (cost=0.00..9.12 rows=94 width=0)
(actual time=0.186..0.186 rows=162 loops=1)
Index Cond: (id1 = t1.id)
-> Index Only Scan using t2i1 on t2 (cost=0.27..0.29 rows=1 width=33)
(actual time=0.024..0.024 rows=1 loops=162)
Index Cond: (id = t3.id2)
Heap Fetches: 162
Planning time: 5.532 ms
Execution time: 5.457 ms

Second plan is ~30 times faster. But with turned off sequentual scan the first
query is not work correctly, which points to some bug in planner, I suppose.
Both 9.6 and 10devel are affected to addiction of query result on seqscan variable.

Dump to reproduce (subset of real data but obfucated), queries are in attachment
http://sigaev.ru/misc/exists_to_nested.sql.gz
--
Teodor Sigaev E-mail: teodor(at)sigaev(dot)ru
WWW: http://www.sigaev.ru/

Attachment Content-Type Size
query.sql text/plain 658 bytes

Responses

Browse pgsql-hackers by date

  From Date Subject
Next Message Amit Langote 2017-04-28 09:29:48 Re: Declarative partitioning - another take
Previous Message Kang Yuzhe 2017-04-28 09:03:26 Re: On How To Shorten the Steep Learning Curve Towards PG Hacking...