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 没有运行,每个UPDATE
或DELETE
都会在表中创建一个“死元组”(或“死行版本”)。除非您手动运行 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查询计划解读
MySQL 8.0中的 explain analyze(译)