在使用非 Equi 连接条件时避免使用 Nested Loop Join

Avoid using Nested Loop Join while using a non Equi join condition

当我在更新查询中使用非相等连接条件时,Postgres 正在使用嵌套循环连接算法。我知道 Nested Loop Join 可能会非常昂贵,因为根据 [https://www.postgresql.org/docs/8.3/planner-optimizer.html]

更新查询和执行计划如下。

查询

explain analyze
UPDATE target_tbl tgt
set descr     = stage.descr,
    prod_name = stage.prod_name,
    item_name = stage.item_name,
    url       = stage.url,
    col1_name = stage.col1_name,
    col2_name = stage.col2_name,
    col3_name = stage.col3_name,
    col4_name = stage.col4_name,
    col5_name = stage.col5_name,
    col6_name = stage.col6_name,
    col7_name = stage.col7_name,
    col8_name = stage.col8_name,
    flag      = stage.flag
from tbl1 stage
where tgt.col1 = stage.col1
  and tgt.col2 = stage.col2
  and coalesce(tgt.col3, 'col3'::text) = coalesce(stage.col3, 'col3'::text)
  and coalesce(tgt.col4, 'col4'::text) = coalesce(stage.col4, 'col4'::text)
  and stage.row_number::int >= 1::int
  and stage.row_number::int < 50001::int;

执行计划

Update on target_tbl tgt  (cost=0.56..3557.91 rows=1 width=813) (actual time=346153.460..346153.460 rows=0 loops=1)
  ->  Nested Loop  (cost=0.56..3557.91 rows=1 width=813) (actual time=4.326..163876.029 rows=50000 loops=1)
        ->  Seq Scan on tbl1 stage  (cost=0.00..2680.96 rows=102 width=759) (actual time=3.060..2588.745 rows=50000 loops=1)
              Filter: (((row_number)::integer >= 1) AND ((row_number)::integer < 50001))
        ->  Index Scan using tbl_idx on target_tbl tgt  (cost=0.56..8.59 rows=1 width=134) (actual time=3.152..3.212 rows=1 loops=50000)
              Index Cond: ((col1 = stage.col1) AND (col2 = stage.col2) AND (COALESCE(col3, 'col3'::text) = COALESCE(stage.col3, 'col3'::text)) AND (COALESCE(col4, 'col4'::text) = COALESCE(stage.col4, 'col4'::text)))
Planning time: 17.700 ms
Execution time: 346157.168 ms
  1. 有什么办法可以避免上面查询执行过程中的嵌套循环连接?

  2. 或者有什么方法可以帮助我降低嵌套循环扫描的成本,目前仅更新 50000 条记录需要 6-7 分钟?

在这种情况下,PostgreSQL 可以选择不同的连接策略。之所以没有,是因为顺序扫描中的毛mis-estimate:102而不是50000。

解决这个问题,事情会变得更好:

ANALYZE tbl1;

如果这还不够,请收集更详细的统计数据:

ALTER TABLE tbl1 ALTER row_number SET STATISTICS 1000;
ANALYZE tbl1;

所有这些都假定 row_number 是一个整数并且类型转换是多余的。如果您错误地使用了不同的数据类型,索引是您唯一的希望:

CREATE INDEX ON tbl1 ((row_number::integer));
ANALYZE tbl1;

I understand that the Nested Loop Join can be very costly as the right relation is scanned once for every row found in the left relation

但是这里的“右关系”是索引扫描,而不是全table的扫描。

您可以通过将连接条件的前导列更改为类似 where tgt.col1+0 = stage.col1 ... 的内容来使其停止使用索引。执行此操作后,它可能会更改为散列连接或合并连接,但您必须尝试一下,看看是否可以。此外,新计划实际上可能不会更快。 (如果可行的话,最好解决估计问题)

Or is there a way that can help me to reduce the cost of the the nested loop scan, currently it takes 6-7 minutes to update just 50000 records?

您的计划表明超过一半的时间花在了更新本身上,因此降低嵌套循环扫描的成本可能对总时间的影响很小。 table 上有很多索引吗?这些索引的维护可能是一个主要瓶颈。