Postgresql 重新运行时使用未执行计划的Postgres

Postgresql 重新运行时使用未执行计划的Postgres,postgresql,query-optimization,Postgresql,Query Optimization,我正在导入一个非圆形图,并将祖先按代码展平为一个数组。这可以正常工作(有一点):~900k边上的400k代码使用~45s 但是,在第一次成功执行后,Postgres决定停止使用嵌套循环,更新查询性能急剧下降:每个代码大约2个 我可以通过在更新之前设置一个真空来强制解决这个问题,但我很好奇为什么会出现这种未优化的情况 DROP TABLE IF EXISTS tmp_anc; DROP TABLE IF EXISTS tmp_rel; DROP TABLE IF EXISTS tmp_edges;

我正在导入一个非圆形图,并将祖先按代码展平为一个数组。这可以正常工作(有一点):~900k边上的400k代码使用~45s

但是,在第一次成功执行后,Postgres决定停止使用
嵌套循环
,更新查询性能急剧下降:每个代码大约2个

我可以通过在更新之前设置一个
真空
来强制解决这个问题,但我很好奇为什么会出现这种未优化的情况

DROP TABLE IF EXISTS tmp_anc;
DROP TABLE IF EXISTS tmp_rel;
DROP TABLE IF EXISTS tmp_edges;
DROP TABLE IF EXISTS tmp_codes; 

CREATE TABLE tmp_rel (
  from_id BIGINT,
  to_id   BIGINT,
);

COPY tmp_rel FROM 'rel.txt' WITH DELIMITER E'\t' CSV HEADER;

CREATE TABLE tmp_edges(
  start_node BIGINT,
  end_node   BIGINT
);

INSERT INTO tmp_edges(start_node, end_node) 
  SELECT from_id AS start_node, to_id AS end_node 
  FROM   tmp_rel;

CREATE INDEX tmp_edges_end ON tmp_edges (end_node);

CREATE TABLE tmp_codes (
  id     BIGINT,
  active SMALLINT,
);

COPY tmp_codes FROM 'codes.txt' WITH DELIMITER E'\t' CSV HEADER;

CREATE TABLE tmp_anc(
   code      BIGINT,
   ancestors BIGINT[]
);

INSERT INTO tmp_anc 
  SELECT DISTINCT(id) 
  FROM   tmp_codes 
  WHERE  active = 1;

CREATE INDEX tmp_anc_codes ON tmp_anc_codes (code);

VACUUM; -- Need this for the update to execute optimally

UPDATE tmp_anc sa SET ancestors = (
  WITH RECURSIVE ancestors(code) AS (
    SELECT start_node FROM tmp_edges WHERE end_node = sa.code
  UNION
    SELECT se.start_node
    FROM   tmp_edges se, ancestors a
    WHERE  se.end_node = a.code
  )
  SELECT array_agg(code) FROM ancestors
);
表统计:

tmp_rel     507 MB  0 bytes
tmp_edges   74 MB   37 MB
tmp_codes   32 MB   0 bytes
tmp_anc     22 MB   8544 kB
说明:

更新前没有真空:

Update on tmp_anc sa  (cost=10000000000.00..11081583053.74 rows=10 width=46) (actual time=38294.005..38294.005 rows=0 loops=1)
  ->  Seq Scan on tmp_anc sa  (cost=10000000000.00..11081583053.74 rows=10 width=46) (actual time=3300.974..38292.613 rows=10 loops=1)
        SubPlan 2
          ->  Aggregate  (cost=108158305.25..108158305.26 rows=1 width=32) (actual time=3829.253..3829.253 rows=1 loops=10)
                CTE ancestors
                  ->  Recursive Union  (cost=81.97..66015893.05 rows=1872996098 width=8) (actual time=0.037..3827.917 rows=45 loops=10)
                        ->  Bitmap Heap Scan on tmp_edges  (cost=81.97..4913.18 rows=4328 width=8) (actual time=0.022..0.022 rows=2 loops=10)
                              Recheck Cond: (end_node = sa.code)
                              Heap Blocks: exact=12
                              ->  Bitmap Index Scan on tmp_edges_end  (cost=0.00..80.89 rows=4328 width=0) (actual time=0.014..0.014 rows=2 loops=10)
                                    Index Cond: (end_node = sa.code)
                        ->  Merge Join  (cost=4198.89..2855105.79 rows=187299177 width=8) (actual time=163.746..425.295 rows=10 loops=90)
                              Merge Cond: (a.code = se.end_node)
                              ->  Sort  (cost=4198.47..4306.67 rows=43280 width=8) (actual time=0.012..0.016 rows=5 loops=90)
                                    Sort Key: a.code
                                    Sort Method: quicksort  Memory: 25kB
                                    ->  WorkTable Scan on ancestors a  (cost=0.00..865.60 rows=43280 width=8) (actual time=0.000..0.001 rows=5 loops=90)
                              ->  Materialize  (cost=0.42..43367.08 rows=865523 width=16) (actual time=0.010..337.592 rows=537171 loops=90)
                                    ->  Index Scan using tmp_edges_end on edges se  (cost=0.42..41203.27 rows=865523 width=16) (actual time=0.009..247.547 rows=537171 loops=90)
                ->  CTE Scan on ancestors  (cost=0.00..37459921.96 rows=1872996098 width=8) (actual time=1.227..3829.159 rows=45 loops=10)
Update on tmp_anc sa  (cost=0.00..2949980136.43 rows=387059 width=14) (actual time=74701.329..74701.329 rows=0 loops=1)
  ->  Seq Scan on tmp_anc sa  (cost=0.00..2949980136.43 rows=387059 width=14) (actual time=0.336..70324.848 rows=387059 loops=1)
        SubPlan 2
          ->  Aggregate  (cost=7621.50..7621.51 rows=1 width=8) (actual time=0.180..0.180 rows=1 loops=387059)
                CTE ancestors
                  ->  Recursive Union  (cost=0.42..7583.83 rows=1674 width=8) (actual time=0.005..0.162 rows=32 loops=387059)
                        ->  Index Scan using tmp_edges_end on tmp_edges  (cost=0.42..18.93 rows=4 width=8) (actual time=0.004..0.005 rows=2 loops=387059)
                              Index Cond: (end_node = sa.code)
                        ->  Nested Loop  (cost=0.42..753.14 rows=167 width=8) (actual time=0.003..0.019 rows=10 loops=2700448)
                              ->  WorkTable Scan on ancestors a  (cost=0.00..0.80 rows=40 width=8) (actual time=0.000..0.001 rows=5 loops=2700448)
                              ->  Index Scan using tmp_edges_end on tmp_edges se  (cost=0.42..18.77 rows=4 width=16) (actual time=0.003..0.003 rows=2 loops=12559395)
                                    Index Cond: (end_node = a.code)
                ->  CTE Scan on ancestors  (cost=0.00..33.48 rows=1674 width=8) (actual time=0.007..0.173 rows=32 loops=387059)
更新前使用真空吸尘器:

Update on tmp_anc sa  (cost=10000000000.00..11081583053.74 rows=10 width=46) (actual time=38294.005..38294.005 rows=0 loops=1)
  ->  Seq Scan on tmp_anc sa  (cost=10000000000.00..11081583053.74 rows=10 width=46) (actual time=3300.974..38292.613 rows=10 loops=1)
        SubPlan 2
          ->  Aggregate  (cost=108158305.25..108158305.26 rows=1 width=32) (actual time=3829.253..3829.253 rows=1 loops=10)
                CTE ancestors
                  ->  Recursive Union  (cost=81.97..66015893.05 rows=1872996098 width=8) (actual time=0.037..3827.917 rows=45 loops=10)
                        ->  Bitmap Heap Scan on tmp_edges  (cost=81.97..4913.18 rows=4328 width=8) (actual time=0.022..0.022 rows=2 loops=10)
                              Recheck Cond: (end_node = sa.code)
                              Heap Blocks: exact=12
                              ->  Bitmap Index Scan on tmp_edges_end  (cost=0.00..80.89 rows=4328 width=0) (actual time=0.014..0.014 rows=2 loops=10)
                                    Index Cond: (end_node = sa.code)
                        ->  Merge Join  (cost=4198.89..2855105.79 rows=187299177 width=8) (actual time=163.746..425.295 rows=10 loops=90)
                              Merge Cond: (a.code = se.end_node)
                              ->  Sort  (cost=4198.47..4306.67 rows=43280 width=8) (actual time=0.012..0.016 rows=5 loops=90)
                                    Sort Key: a.code
                                    Sort Method: quicksort  Memory: 25kB
                                    ->  WorkTable Scan on ancestors a  (cost=0.00..865.60 rows=43280 width=8) (actual time=0.000..0.001 rows=5 loops=90)
                              ->  Materialize  (cost=0.42..43367.08 rows=865523 width=16) (actual time=0.010..337.592 rows=537171 loops=90)
                                    ->  Index Scan using tmp_edges_end on edges se  (cost=0.42..41203.27 rows=865523 width=16) (actual time=0.009..247.547 rows=537171 loops=90)
                ->  CTE Scan on ancestors  (cost=0.00..37459921.96 rows=1872996098 width=8) (actual time=1.227..3829.159 rows=45 loops=10)
Update on tmp_anc sa  (cost=0.00..2949980136.43 rows=387059 width=14) (actual time=74701.329..74701.329 rows=0 loops=1)
  ->  Seq Scan on tmp_anc sa  (cost=0.00..2949980136.43 rows=387059 width=14) (actual time=0.336..70324.848 rows=387059 loops=1)
        SubPlan 2
          ->  Aggregate  (cost=7621.50..7621.51 rows=1 width=8) (actual time=0.180..0.180 rows=1 loops=387059)
                CTE ancestors
                  ->  Recursive Union  (cost=0.42..7583.83 rows=1674 width=8) (actual time=0.005..0.162 rows=32 loops=387059)
                        ->  Index Scan using tmp_edges_end on tmp_edges  (cost=0.42..18.93 rows=4 width=8) (actual time=0.004..0.005 rows=2 loops=387059)
                              Index Cond: (end_node = sa.code)
                        ->  Nested Loop  (cost=0.42..753.14 rows=167 width=8) (actual time=0.003..0.019 rows=10 loops=2700448)
                              ->  WorkTable Scan on ancestors a  (cost=0.00..0.80 rows=40 width=8) (actual time=0.000..0.001 rows=5 loops=2700448)
                              ->  Index Scan using tmp_edges_end on tmp_edges se  (cost=0.42..18.77 rows=4 width=16) (actual time=0.003..0.003 rows=2 loops=12559395)
                                    Index Cond: (end_node = a.code)
                ->  CTE Scan on ancestors  (cost=0.00..33.48 rows=1674 width=8) (actual time=0.007..0.173 rows=32 loops=387059)

第一个执行计划的估计值非常差(
tmp\u edges\u end上的位图索引扫描
估计值为4328,而不是2行),而第二个执行计划的估计值很好,因此选择了一个好的计划。 所以你上面引用的两次执行之间的某些东西一定改变了估计

此外,您说第一次执行
UPDATE
(我们没有
EXPLAIN(ANALYZE)
输出)很快

对于初始性能下降的唯一好解释是,autovacuum守护进程需要一些时间来收集新表的统计信息。这通常会提高查询性能,但当然也可以反过来工作

另外,
VACUUM
通常不能解决性能问题。是否您使用了
真空(分析)

在初次
更新之前收集统计数据时,了解情况会很有趣:

ANALYZE tmp_edges;
然而,当我更仔细地阅读您的查询时,我想知道为什么要使用相关子查询来实现这一点。也许这样做会更快:

UPDATE tmp_anc sa
   SET ancestors = a.codes
   FROM (WITH RECURSIVE ancestors(code, start_node) AS
            (SELECT tmp_anc.code, tmp_edges.start_node
                FROM tmp_edges
                   JOIN tmp_anc ON tmp_edges.end_node = tmp_anc.code
             UNION
             SELECT a.code, se.start_node
                FROM tmp_edges se
                   JOIN ancestors a ON se.end_node = a.code
            )
         SELECT code,
                array_agg(start_node) AS codes
            FROM ancestors
            GROUP BY (code)
        ) a
   WHERE sa.code = a.code;

(这未经测试,因此可能存在错误。)

第一个执行计划的估计值非常差(
tmp_边上的位图索引扫描
估计值为4328,而不是2行),而第二个执行计划的估计值很好,因此选择了一个好的计划。 所以你上面引用的两次执行之间的某些东西一定改变了估计

此外,您说第一次执行
UPDATE
(我们没有
EXPLAIN(ANALYZE)
输出)很快

对于初始性能下降的唯一好解释是,autovacuum守护进程需要一些时间来收集新表的统计信息。这通常会提高查询性能,但当然也可以反过来工作

另外,
VACUUM
通常不能解决性能问题。是否您使用了
真空(分析)

在初次
更新之前收集统计数据时,了解情况会很有趣:

ANALYZE tmp_edges;
然而,当我更仔细地阅读您的查询时,我想知道为什么要使用相关子查询来实现这一点。也许这样做会更快:

UPDATE tmp_anc sa
   SET ancestors = a.codes
   FROM (WITH RECURSIVE ancestors(code, start_node) AS
            (SELECT tmp_anc.code, tmp_edges.start_node
                FROM tmp_edges
                   JOIN tmp_anc ON tmp_edges.end_node = tmp_anc.code
             UNION
             SELECT a.code, se.start_node
                FROM tmp_edges se
                   JOIN ancestors a ON se.end_node = a.code
            )
         SELECT code,
                array_agg(start_node) AS codes
            FROM ancestors
            GROUP BY (code)
        ) a
   WHERE sa.code = a.code;

(这是未经测试的,因此可能会有错误。)

如果没有第一次执行的
解释(分析)
输出,很难说,但是可能
真空
刚好为自动分析留出了足够的时间来收集统计数据?如果在
更新
之前对表进行
分析
,是否仍能观察到效果?添加了解释分析,但必须将代码量从400k更改为10,以便运行到完成<代码>合并联接(cost=4198.89..2855105.79行=187299177
:(所有表似乎都没有主键(或唯一索引),您还应该在填充表后对表运行
vacuum analyze
,这将更新统计信息。用于更新的仅有两个表是
tmp\u anc
tmp\u edges
。我认为
tmp\u anc
不需要PK,因为它的
code
列是迭代的主题,而
tmp>是_anc
end_node
上有一个索引,该索引被大量使用。不过,为了排除这一点,添加了PKs,性能没有变化。实际上,只是想弄清楚如何在第一次执行时完美地执行查询,但后续执行需要一个真空。没有
解释(分析)很难说
第一次执行时的输出,但可能是
真空
刚好为自动分析留出了足够的时间来收集统计数据?如果在
更新之前
分析表
,您仍然可以观察效果吗?添加了解释分析,但必须将代码量从400k更改为10,以便运行到完成。
>合并联接(cost=4198.89..2855105.79行=187299177
:(所有表似乎都没有主键(或唯一索引),您还应该在填充表后对表运行
vacuum analyze
,这将更新统计信息。用于更新的仅有两个表是
tmp\u anc
tmp\u edges
。我认为
tmp\u anc
不需要PK,因为它的
code
列是迭代的主题,而
tmp>是_anc
end_node
上有一个索引,该索引被大量使用。不过,为了排除这种可能性,添加了PKs,性能没有变化。实际上,只是想弄清楚如何在第一次执行时完美地执行查询,但后续执行需要一个真空。第二个解释分析发布在上面(正确规划的查询)是第一次执行。之后的每一次尝试都是缓慢的。感谢您发布您对查询的优化,我将在今天晚些时候进行研究。只是重申一下,我第一次运行此查询时,查询采用了优化的路线,之后的每一次都会在不强制进行新统计的情况下,显示其性能显著下降。请解释“新统计信息”:你说你正在运行
VACUUM
,它不收集统计信息。关于你的第一个执行计划,另一个奇怪的事情是它看起来像
enable\u seqscan
被设置为
off
(观察顺序扫描的奇怪的高启动成本).您是否在以下两种情况下更改优化器参数