Postgres EXPLAIN ANALYZE 成本估算行数大大高于实际行数。没有吸尘?

Posted

技术标签:

【中文标题】Postgres EXPLAIN ANALYZE 成本估算行数大大高于实际行数。没有吸尘?【英文标题】:Postgres EXPLAIN ANALYZE cost estimate row count massively higher than actual row count. No Vacuuming? 【发布时间】:2018-12-11 08:22:15 【问题描述】:

我在 Django 项目中的 Heroku 上运行了一个 Postgres 9.4.18 数据库。我注意到查询变得越来越慢,所以我对一个查询运行了“解释分析”,并注意到对于一个节点,行估计大大高于实际行数:

->  Seq Scan on listings_listing u1  (cost=0.00..1536692.01 rows=5030003 width=8) (actual time=0.811..11263.410 rows=173537 loops=1)

然后我在表上运行“VACUUM FULL ANALYZE”,然后在查询中重新运行“EXPLAIN ANALYZE”并得到:

->  Seq Scan on listings_listing u1  (cost=0.00..23554.61 rows=173537 width=8) (actual time=0.001..33.884 rows=173537 loops=1)

现在执行时间快了 100 倍。

所以两个问题是:A)自动吸尘不应该防止这种情况发生吗? (我如何检查它是否已启用?)B)假设没有执行真空吸尘,它是如何做到的?

--------------------------------更新

我从 heroku 中找到了这个给出 autovacuum 统计信息的命令,这是输出(不幸的是,我在手动清理之后运行了它。

heroku pg:vacuum_stats DATABASE_URL

schema |                  table                  | last_vacuum | last_autovacuum  |    rowcount    | dead_rowcount  | autovacuum_threshold | expect_autovacuum 
--------+-----------------------------------------+-------------+------------------+----------------+----------------+----------------------+-------------------
 public | listings_listing                        |             | 2018-06-27 15:36 |        173,537 |              0 |         34,757       | 

似乎指示的阈值应该在很久以前就导致它运行真空。

此外,这里是 Heroku 页面,其中包含有关吸尘设置的文档: https://devcenter.heroku.com/articles/managing-vacuum-on-heroku-postgres

【问题讨论】:

【参考方案1】:

要查看 autovacuum 是否已按应有的方式启用,请运行

SHOW autovacuum;

要查看是否为您的特定表禁用了 autovacuum,请运行

SELECT reloptions FROM pg_class WHERE relname = 'listings_listing';

B) 的答案很简单:

如果 autovacuum 没有运行,每个UPDATEDELETE 都会在表中创建一个“死元组”(或“死行版本”)。除非您手动运行 VACUUM,否则这些将永远不会被清除,这会导致表增长,从而导致顺序扫描变慢。

A)的答案更难:

有几件事可以阻止 autovacuum 完成其工作:

此表的更改率可能非常高,以至于默认运行缓慢的 autovacuum 无法跟上正常活动。

在这种情况下,您应该将 autovacuum 调整为对该表更具侵略性:

ALTER TABLE listings_listing SET (
   autovacuum_vacuum_cost_limit = 1000,
   toast.autovacuum_vacuum_cost_limit = 1000
);

如果这还不够好,你可以

ALTER TABLE listings_listing SET (
   autovacuum_vacuum_cost_delay = 0,
   toast.autovacuum_vacuum_cost_delay = 0
);

有并发的长事务。

Autovacuum 只能删除比最旧的正在运行的事务更早的死元组,因此长事务可能会使其无法正常工作。

还有更多的故事;阅读this blog post。

但是,这也会使 VACUUM (FULL) 无法正常工作,所以这可能不是您的问题。

该表经常被SHARE UPDATE EXCLUSIVE 或更强大的锁锁定,例如通过运行“LOCK listings_listing”。

当 autovacuum 遇到这样的锁定时,它会退出而不是阻止用户活动。

确定发生了什么的一种有用方法是像这样查询pg_stat_user_tables

SELECT n_live_tup, n_dead_tup, last_vacuum, last_autovacuum
FROM pg_stat_user_tables
WHERE relname = 'listings_listing';

但是现在你已经运行了VACUUM (FULL),这个证据可能已经被销毁了。

另一件好事是将log_autovacuum_min_duration 设置为-1 以外的值并偶尔查看日志。

【讨论】:

谢谢。运行您的命令显示此表的 autovacuum 已打开且未禁用。 pg_stat_user_tables 显示 last_autovacuum 是在 2018-06-27 完成的。我还运行了一个显示 autovacuum_threshold 为 34,757 行的 heroku 命令。所以我不明白为什么它还没有运行。还是与 autovacuum_vacuum_cost_delay 不同?我不在此表上执行长锁。 也许只是批量删除。监控表膨胀,看看会发生什么。 我每 15 分钟运行一次删除陈旧列表的任务,因此不太可能进行批量删除。我将尝试监控膨胀。改变 cost_delay 的想法是否可能是它试图吸尘但它一直在睡觉?我的服务不是那么受欢迎,所以我很难相信。 Autovacuum 默认变慢,它经常需要休息。除非你知道有必要,否则不要调整它。【参考方案2】:

Laurenz Albe 的回答非常适合解释自动吸尘的原因,但我现在想回答我后来发现的为什么我的死元组数量激增。

基本上由于我的代码中的错误,我每 15 分钟更新一次数据库中的每一行,而不仅仅是匹配过滤器的行。每次更新都会创建一个死元组,并且它膨胀得如此之快,以至于吸尘无法跟上。我花了一段时间才找到错误,因为我只查看代码中的删除而不是更新,因为我(当时)没有意识到它们也会创建死元组。

修复后无需更改任何自动吸尘设置。肿胀增加是正常的。

【讨论】:

以上是关于Postgres EXPLAIN ANALYZE 成本估算行数大大高于实际行数。没有吸尘?的主要内容,如果未能解决你的问题,请参考以下文章

pg_flame postgresql EXPLAIN ANALYZE 火焰图工具

PostgreSQL学习系列—EXPLAIN ANALYZE查询计划解读

14.1.2. EXPLAIN ANALYZE

MySQL 8.0中的 explain analyze(译)

MySQL 8.0中的 explain analyze(译)

尝试使用回滚运行 EXPLAIN ANALYZE