| From: | ld_zju <ld_zju(at)126(dot)com> |
|---|---|
| To: | "Tender Wang" <tndrwang(at)gmail(dot)com> |
| Cc: | pgsql-bugs(at)lists(dot)postgresql(dot)org, "Tom Lane" <tgl(at)sss(dot)pgh(dot)pa(dot)us> |
| Subject: | Re:Re: DO NOT pull up a sublink when it has no join condition with the upper relation |
| Date: | 2026-07-31 14:54:02 |
| Message-ID: | 4a771336.7ba7.19fb8ab0129.Coremail.ld_zju@126.com |
| Views: | Whole Thread | Raw Message | Download mbox | Resend email |
| Thread: | |
| Lists: | pgsql-bugs |
Thank you for your quick response.
The reason why I thought it was a bug is only because oracle optimizer can generate a plan seems to be more reasonable. Its execution plan goes like "select * from t1 where exists(select 1 from t2, t3 where t3.b=t1.b and t2.a=t3.a);"
I have tested the suggested approach with "offset 0" in our test environment. It does resolve the immediate issue we encountered, and the performance impact is acceptable.
At 2026-07-31 10:52:55, "Tender Wang" <tndrwang(at)gmail(dot)com> wrote:
>ld_zju <ld_zju(at)126(dot)com> 于2026年7月31日周五 00:16写道:
>>
>> Hi,
>>
>> I've encountered a scenario where pulling up a sublink not only brings no benefit but actually degrades the final plan significantly.
>>
>> Here is the test case:
>>
>> create table t1(a int,b int,c int,d int);
>> create table t2(a int,b int,c int,d int);
>> create table t3(a int,b int,c int,d int);
>> insert into t1 select i,i,i,i from generate_series(1,1000) i;
>> insert into t2 select i,i,i,i from generate_series(1,1000) i;
>> insert into t3 select i,i,i,i from generate_series(1,10) i;
>>
>> explain select * from t1 where exists(select 1 from t2 where t2.a in(select t3.a from t3 where t3.b=t1.b));
>> QUERY PLAN
>> -------------------------------------------------------------------
>> Nested Loop Semi Join (cost=0.00..28418232.67 rows=925 width=16)
>> Join Filter: (ANY (t2.a = (SubPlan any_1).col1))
>> -> Seq Scan on t1 (cost=0.00..28.50 rows=1850 width=16)
>> -> Materialize (cost=0.00..37.75 rows=1850 width=4)
>> -> Seq Scan on t2 (cost=0.00..28.50 rows=1850 width=4)
>> SubPlan any_1
>> -> Seq Scan on t3 (cost=0.00..33.12 rows=9 width=4)
>> Filter: (b = t1.b)
>> (8 rows)
>>
>> The EXISTS sublink is pulled up and joined with t1 via a Nested Loop Semi Join. However, since there is no join condition between t1 and the sublink (the condition t3.b = t1.b is inside the subplan), this results in a Cartesian product between t1 and t2, followed by filtering through the subplan. With t1 and t2 both having 1000 rows, this produces a large intermediate result set (1,000,000 rows) when the actual result set is much smaller.
>>
>> Would it be possible that the sublink is pulled up only when it has any join conditions with the upper relation? If no such conditions exist, a Cartesian product is likely and pulling up should be avoided.
>>
>> Any thoughts or suggestions would be appreciated!
>
>You can add "offset 0" into the subquery; then the plan should be what you want.
>postgres=# explain select * from t1 where exists(select 1 from t2
>where t2.a in(select t1.b from t3 where t3.b=t1.b) offset 0);
> QUERY PLAN
>------------------------------------------------------------------
> Seq Scan on t1 (cost=0.00..19651.00 rows=500 width=16)
> Filter: EXISTS(SubPlan exists_1)
> SubPlan exists_1
> -> Nested Loop Semi Join (cost=0.00..19.64 rows=1 width=4)
> -> Seq Scan on t2 (cost=0.00..18.50 rows=1 width=0)
> Filter: (a = t1.b)
> -> Seq Scan on t3 (cost=0.00..1.12 rows=1 width=0)
> Filter: (b = t1.b)
>(8 rows)
>
>And the Execution Time: 178.877 ms; without "offset 0", it is 4048.970
>ms on my machine.
>
>In convert_EXISTS_sublink_to_join(), we have:
> /*
> * On the other hand, the WHERE clause must contain some Vars of the
> * parent query, else it's not gonna be a join.
> */
> if (!contain_vars_of_level(whereClause, 1))
> return NULL;
>
>When we recurse into the third sublink in
>contain_vars_of_level_walker(), the levelsup was +1(i.e. 2)
>t1.b in "t3.b = t1.b" is Var [varno=1 varattno=2 vartype=23
>varlevelsup=2 varreturningtype=VAR_RETURNING_DEFAULT varnosyn=1
>varattnosyn=2]
>You can see that varlevelsup is 2, so
>contain_vars_of_level(whereClause, 1) returns true. Then the sublink
>is pulled up.
>
>I made some attempts.
>
>#1
>We can't simply remove the"(*sublevels_up)++; " in
>contain_vars_of_level_walker(); because some other places also call
>this function.
>If you do this, the regression will crash.
>#2
>I rewrote a separate version based on the current implementation
>specifically for SubLink pull-up. My goal was simply to see whether it
>would cause any regression test failures.
>The attached is my test. It's only for testing.
>To my surprise, all the regression tests passed.
>
>I'm not sure it is a bug. The code was committed 17 years ago by Tom.
>And I'm not sure you're the first to report this issue.
>I feel that in most cases, the second query will refer to the top
>query's column, and the third query will refer to the second query's
>column.
>
>--
>Thanks,
>Tender Wang
| From | Date | Subject | |
|---|---|---|---|
| Next Message | Zexin Li | 2026-08-01 01:40:14 | Re: BUG #19583: macaddr input accepts octet fields longer than 8 hex digits |
| Previous Message | Matheus Alcantara | 2026-07-31 11:48:29 | Re: BUG #19572: Redundant predicate changes JIT decision and causes an 18x performance difference |