【问题标题】:SQL: ON vs. WHERE in sub JOINSQL:子连接中的 ON 与 WHERE
【发布时间】:2021-04-22 12:59:56
【问题描述】:

在使用外部引用时,在子连接中使用 ON 和 WHERE 有什么区别?

以这两条 SQL 语句为例(寻找 10 个人没有关闭的任务,使用 person_task 与多对多关系):

select p.name
from person p
where exists (
    select 1
    from person_task pt
    join task t on pt.task_id = t.id 
               and t.state <> 'closed'
               and pt.person_id = p.id -- ON
)
limit 10

select p.name
from person p
where exists (
    select 1
    from person_task pt
    join task t on pt.task_id = t.id and t.state <> 'closed'
    where pt.person_id = p.id -- WHERE
)
limit 10

它们产生相同的结果,但带有 ON 的语句要快得多。

这里对应EXPLAIN (ANALYZE)语句:

-- USING ON
Limit  (cost=0.00..270.98 rows=10 width=8) (actual time=10.412..60.876 rows=10 loops=1)
  ->  Seq Scan on person p  (cost=0.00..28947484.16 rows=1068266 width=8) (actual time=10.411..60.869 rows=10 loops=1)
        Filter: (SubPlan 1)
        Rows Removed by Filter: 68
        SubPlan 1
          ->  Nested Loop  (cost=1.00..20257.91 rows=1632 width=0) (actual time=0.778..0.778 rows=0 loops=78)
                ->  Index Scan using person_taskx1 on person_task pt  (cost=0.56..6551.27 rows=1632 width=8) (actual time=0.633..0.633 rows=0 loops=78)
                      Index Cond: (id = p.id)
                ->  Index Scan using taskxpk on task t  (cost=0.44..8.40 rows=1 width=8) (actual time=1.121..1.121 rows=1 loops=10)
                      Index Cond: (id = pt.task_id)
                      Filter: (state <> 'open')
Planning Time: 0.466 ms
Execution Time: 60.920 ms

-- USING WHERE
Limit  (cost=2818814.57..2841563.37 rows=10 width=8) (actual time=29.075..6884.259 rows=10 loops=1)
  ->  Merge Semi Join  (cost=2818814.57..59308642.64 rows=24832 width=8) (actual time=29.075..6884.251 rows=10 loops=1)
        Merge Cond: (p.id = pt.person_id)
        ->  Index Scan using personxpk on person p  (cost=0.43..1440340.27 rows=2136533 width=16) (actual time=0.003..0.168 rows=18 loops=1)
        ->  Gather Merge  (cost=1001.03..57357094.42 rows=40517669 width=8) (actual time=9.441..6881.180 rows=23747 loops=1)
              Workers Planned: 2
              Workers Launched: 2
              ->  Nested Loop  (cost=1.00..52679350.05 rows=16882362 width=8) (actual time=1.862..4207.577 rows=7938 loops=3)
                    ->  Parallel Index Scan using person_taskx1 on person_task pt  (cost=0.56..25848782.35 rows=16882362 width=16) (actual time=1.344..1807.664 rows=7938 loops=3)
                    ->  Index Scan using taskxpk on task t  (cost=0.44..1.59 rows=1 width=8) (actual time=0.301..0.301 rows=1 loops=23814)
                          Index Cond: (id = pt.task_id)
                          Filter: (state <> 'open')
Planning Time: 0.430 ms
Execution Time: 6884.349 ms

因此是否应该始终使用 ON 语句来过滤 sub JOIN 中的值?或者发生了什么? 我在这个例子中使用了 Postgres。

【问题讨论】:

    标签: sql postgresql performance join select


    【解决方案1】:

    条件and pt.person_id = p.id 不引用连接表t 的任何列。在内部连接中,这在语义上没有多大意义,我们可以将此条件从 ON 移动到 WHERE 以使查询更具可读性。

    您是对的,因此,这两个查询是等效的,并且应该产生相同的执行计划。由于情况并非如此,因此 PostgreSQL 的优化器似乎存在问题。

    在外部联接中,ON 中的此类条件可能有意义,并且与 WHERE 不同。我认为这就是优化器通常为ON 寻找不同计划的原因。一旦它检测到ON 中的条件,它就会去另一条路线,忘记连接类型(所以我的假设)。不过,我很惊讶,这会带来一个更好的计划;我宁愿期待一个更糟糕的计划。

    这可能表明该表的统计信息不是最新的。请分析表格以确保。或者它可能是 PostgreSQL 开发人员可能想要处理的优化器代码中的痛处。

    【讨论】:

    • 我分析了表格,统计数据是最新的。但是,它仍然产生与以前相同的执行计划。
    • 那是优化器的一个弱点。
    • 有趣,你能想出一种方法来“帮助”优化器找到使用WHERE 版本的高性能执行计划吗?
    • 不,抱歉。我不太了解 PostgreSQL。不知道能不能给点提示。性能损失严重吗?如果没有,我暂时只能忍受它。也许您可以将其作为优化错误提交到某处并希望得到修复。
    • 没问题。不过谢谢。是的,不幸的是。最多需要 20 分钟。
    猜你喜欢
    • 2011-02-25
    • 2012-10-19
    • 1970-01-01
    • 1970-01-01
    • 2015-03-29
    • 2015-12-11
    • 1970-01-01
    • 1970-01-01
    • 2015-07-19
    相关资源
    最近更新 更多