PosrgreSQL 学习计划——Vacuum 清理机制


什么是 Vacuum?

VACUUM — garbage-collect and optionally analyze a database

Vacuum 是垃圾回收机制,并能够对数据库进行分析(可选)。

在 PostgreSQL 中,Vacuum 机制是必不可少的,他能避免数据膨胀,解决事务ID环绕问题,并且能够提供分析统计信息供其它模块使用以便提升性能。

PostgreSQL 先行知识

Vacuum 会涉及到堆表的数据读取、索引和MVCC,此处简要介绍相关知识。

数据与索引的存储

在 PostgreSQL 中数据存储在堆表(table heap)中,与索引是分开存储的。

查询数据时,首先查询索引,找到记录后再到堆表中获取记录。

结合数据在磁盘上的存储,更具体的位置如下:

数据库所在位置

数据库以目录形式组织,路径为:PGDATA/base/oid,oid 可以从 pg_database 表查询得到。

postgres=# select oid, datname from pg_database;
  oid  |  datname
-------+-----------
 13395 | postgres
     1 | template1
 13394 | template0
(3 rows)

表所在位置

表以文件形式存储

postgres=# select pg_relation_filepath('foo');
 pg_relation_filepath
----------------------
 base/13395/16384
(1 row)

记录所在位置

记录存储在页(Page)中,每页一般 8K 大小。Item 指定了 Tuple 在页中的位置,Tuple 存储着具体的数据。

每条记录都有隐藏字段 ctid ,存储着记录所在位置

postgres=# select ctid, * from foo;
 ctid  | id | content
-------+----+----------
 (0,1) |  1 | dongdong
(1 row)

本例中 ctid 值为(0,1)说明记录存储在 page_id = 0,item_id = 1,进而通过 item_id 可以获取到 Tuple 数据。

MVCC 多版本并发控制

MVCC 是针对事务读取的优化,简单的讲就是:对数据库的任何修改的提交都不会直接覆盖之前的数据,而是产生一个新的版本与老版本共存,使得读取时可以完全不加锁。当开启事务后,事务内数据相当于创建了一个“快照”。

PostgreSQL 中的事务由 32 位组成,顺序产生,依次递增,在当前会话中如果没有数据变更(如INSERT、UPDATE、DELETE等操作),事务ID保持不变。

MVCC 机制下,数据库中每条记录中都有几个隐藏字段,最常见的如 xminxmax

  • xmin: 在创建记录时,此值设置为插入tuple的事务ID
  • xmax: 默认值为0,在删除tuple时,为删除的tuple的事务ID

查询数据时显示指定 xminxmax 能看到隐藏列

select user_id, task_id, updated, xmin, xmax from user_task where user_id = 'UUBXJXXXWCEUU'

结果

+---------------+-----------+----------------------------+------------+--------+
| user_id       | task_id   | updated                    | xmin       | xmax   |
|---------------+-----------+----------------------------+------------+--------|
| UUBXJXXXWCEUU | 1005      | 2021-05-31 10:23:23.69+00  | 2723516899 | 0      |
| UUBXJXXXWCEUU | 1003      | 2021-05-31 10:23:23.682+00 | 2723516885 | 0      |
| UUBXJXXXWCEUU | 6         | 2021-05-31 10:23:23.666+00 | 2723516837 | 0      |
| UUBXJXXXWCEUU | 4         | 2021-05-31 10:25:31.684+00 | 2723870831 | 0      |
| UUBXJXXXWCEUU | 2         | 2021-05-31 10:23:23.646+00 | 2723516783 | 0      |
| UUBXJXXXWCEUU | 1         | 2021-05-31 10:23:23.639+00 | 2723516764 | 0      |
| UUBXJXXXWCEUU | 1004      | 2021-05-31 10:23:23.686+00 | 2723516893 | 0      |
| UUBXJXXXWCEUU | 1002      | 2021-05-31 10:23:23.679+00 | 2723516867 | 0      |
| UUBXJXXXWCEUU | 7         | 2021-05-31 10:23:23.671+00 | 2723516844 | 0      |
| UUBXJXXXWCEUU | 5         | 2021-05-31 10:23:23.662+00 | 2723516827 | 0      |
| UUBXJXXXWCEUU | 11        | 2021-05-29 07:11:26.992+00 | 2285410093 | 0      |
| UUBXJXXXWCEUU | 8         | 2021-05-29 07:11:26.982+00 | 2285410054 | 0      |
| UUBXJXXXWCEUU | 3         | 2021-05-31 10:23:23.653+00 | 2723516800 | 0      |
| UUBXJXXXWCEUU | 1001      | 2021-05-31 10:24:18.9+00   | 2723669084 | 0      |
| UUBXJXXXWCEUU | 10        | 2021-05-29 08:57:36.131+00 | 2306009087 | 0      |
| UUBXJXXXWCEUU | 9         | 2021-05-29 08:57:33.943+00 | 2306002176 | 0      |
+---------------+-----------+----------------------------+------------+--------+

xmin 即为插入记录的事务ID,xmax 为 0 代表记录为最新有效版本。

Vacuum 机制的原因及理论

Vacuum 功能简介

Vacuum 有几项重要功能:

  1. 恢复或重用由更新或已删除的行占用的磁盘空间。

  2. 更新 PostgreSQL 查询规划器使用的数据统计信息。

  3. 更新可见性映射,从而加快仅索引扫描。(index-only scans

  4. 避免由于事务ID或者混合事务ID丢失历史数据。

这些原因都要求执行不同频率和范围的 VACUUM 操作。

1)恢复磁盘空间

垃圾来自哪里?

因着数据库的 MVCC 多版本并发控制的概念,PostgreSQL 在删除/更新记录时是这样处理的:

  • 删除:对数据记录进行标记删除,不会在物理存储中真实的删除它。
  • 更新:将记录标记删除,并且插入新的记录。

示例:PostgreSQL 垃圾数据

  1. 更新前,有一条记录 Tuple1(xmin=1834,xmax=0),这个记录是由事务:1834 插入的。
  2. 此处执行的更新操作 事务:1835,没有删除掉 Tuple1,修改了 Tuple1 的 xmax 为自己的事务ID(xmin=1384, xmax=1835)
  3. 事务:1835插入了一条修改后的记录Tuple2(xmin=1835,xmax=0)
  4. 事务:1834 结束后,将不会有人用到 Tuple1,Tuple1 变成了垃圾数据。

使用 Standard Vacuum 进行清理会移除表及索引中的无效记录,并标记了将来重复使用的空间,上例的 Tuple1 就会被 Vacuum 发现并处理。

空间膨胀

数据失效后被 Vacuum 标记后可以被重复使用,但是如果 Vacuum 没有及时清理,同时短时间内有大量的更新删除操作,将会导致数据库在堆中创建大量的 Page,即使后续使用标准 Vacuum 清理,但这些空间一般不会直接返还给操作系统 —— 这就造成了空间膨胀

空间膨胀后只能只用 Vacuum Full 进行恢复。

Vacuum Full 即运行 vacuum 命令的时候加上 full 参数—— vacuum full

使用 Vacuum Full 清理会利用磁盘空间创建一个紧凑的没有垃圾数据的新副本,可以最小化表的大小(近似的:例如膨胀后 100 个页,仅使用了一半空间,那么 Vacuum Full 会创建 50 个新页,将数据全量复制过去)——会消耗大量磁盘空间,且复制表非常慢

其它数据库也存在同样的问题吗?

是的,其它实现了 MVCC 数据库同样需要对失效的数据进行清理。

例如:Oracle 和 MySQL 的 InnoDB 引擎,与 PostgreSQL 的实现方式不同,它们将这些标记删除的数据移动到了回滚日志中,主表只保留行的最新数据。

MVCC 两种实现方式

一般 MVCC 的实现方式有 2 种

  1. 写新数据时,把旧数据转移到一个单独的地方,如回滚段中,其他人读数据时,从回滚段中把旧的数据读出来,如Oracle数据库和MySQL中的innodb引擎。

  2. 写新数据时,旧数据不删除,而是把新数据插入。PostgreSQL就是使用的这种实现方法。

第一种方案

优点

  • 不会像 PostgreSQL 一样造成空间膨胀,也不要需要 vacuum 机制进行清除;

缺点

  • 写入回滚日志会有新的“问题”,它会创造一个巨大的回滚段,需要定时的清理回滚日志。
  • 将数据写到回滚段会有更多的开销,且查询历史行版本数据也会相对缓慢。

第二种方案

优点

  • 无论事务进行了多少操作,事务回滚可以立即完成;
  • 数据可以进行很多更新,不必像 Oracle 和 MySQL 的 Innodb 引擎那样需要经常保证回滚段不被用完。

缺点

  • 旧版本的数据需要清理,否则会造成空间膨胀(在 PostgreSQL 9.x版本中已经增加了自动清理的辅助进程来定期清理);
  • 旧版本的数据可能会导致查询需要扫描的数据块增多,从而导致查询变慢。

两种实现各有利弊,不同的实现是数据库整体设计和侧重的考量。

2)生成分析信息

PostgreSQL 查询计划会利用一些统计数据来优化查询器,这些统计分析信息由 analyze 命令生成。

开启 autovacuum 后,它会自动调用命令进行分析。

需要注意的是 autovacuum 严格按照插入或更新的行数来调用,它不知道表上更新的字段是不是我们“感兴趣的”,也无法判断更新的分析是不是有意义的。

与用于空间恢复的清理一样,更高频率的更新往往更好。但也需要注意如果数据的分布没有太多的变化,也不需要特别频繁的更新。一个经验法则是考虑列内数据的最大值和最小值,例如“更新时间”字段,它的的最大值会一直增加,而其它字段可能变化区间范围很小。

可以在特定的表或者列上运行分析命令。但在实践中,最好只分析整个数据库,这是一个很快速的操作 —— 它会对表进行随机抽样,不会遍历整个表。

3)更新 Visibility Map(VM)

VM 用途

Vacuum 为每个表维护一个可见性映射,以跟踪哪些页只包含已知对所有活动事务可见的元组。这有两个目的:

  • 首先,vacuum 本身可以在下一次运行时跳过这些页面,因为没有什么东西需要清理。
  • 另外,可以被用于 Index-only Scan(仅索引扫描)

什么是仅索引扫描?

仅索引扫描(Index-only Scan)的意思是只读取索引而不需要查询表堆内的数据即可完成查询(最少的磁盘IO、效率最高)

查询如果满足两个条件,则将仅使用索引查询,不会出发随机堆访问。

  • 索引类型支持,如 B-Tree,或 GiST、SP-GIST 的部分操作符。
  • 查询仅使用索引列,例如:x, y 为索引列,z 不是索引列
select x, y from tab where x = 'key' and y < 42 -- 仅索引

select x, y from tab where x = 'key' and z < 42 -- 触发堆查询

扩展:如果有一两个字段作为数据项跟索引Key的联系十分紧密

SELECT y FROM tab WHERE x = 'key';

可以使用 INCLUDE 关键字在创建索引的时候将这个字段也包含在索引中

CREATE INDEX tab_x_y ON tab(x) INCLUDE (y);

仅索引扫描应用可见性地图的简化流程:

更新及使用时机

由于 PostgreSQL 中检索后需要校验 MVCC 快照可见性,哪些数据可见,哪些数据已标记删除。

在 PostgreSQL 中,数据和索引是单独存储的。因为索引不包含元组可见性信息,索引普通的索引扫描需要为每个匹配项获取堆元组,以检查当前事务是否应该看到它。

可见性地图存储着两位数据,第一位设置(1|0)表明堆页没有空闲空间,即所有页内数据都可见。第二位设置(0|1)表明页内数据都已被冻结,全都可见,不需要清理。

Vacuum 会在清理后设置 Page 在可见性地图中对应的标识位,任何的更新删除操作都将导致 VM 状态位复原,如果标志位没有复原,说明 Page 自上次清理后没有无效数据

有了可见性地图,仅索引扫描首先检查可见性地图,如果知道页面上的所有元组都是可见的,那么可以跳过堆获取。可见性映射比堆小得多,因此即使堆非常大,也可以轻松地缓存它(一页两位表示)。

简而言之,考虑到这两个基本要求,只进行索引扫描是可能的,但只有当表堆页的大部分都设置了全可见映射位时,才会成功。但是,其中大部分行是不变的表是非常常见的,这使得这种扫描在实践中非常有用。

4)防止事务ID环绕问题

PostgreSQL 的 MVCC 事务语义依赖于能够比较事务ID(XID)编号:如何插入的 XID 大于当前事务的 XID 的行版本,那么这个变更是“将来的”,不应该对当前事务可见。

但由于事务ID大小有限(32位),集群运行很长一段时间(超过40亿个事务)后会遭受事务ID环绕:XID 计数器变为零,会突然的导致过去的事务似乎在未来,简言之,造成所有数据丢失 (实际上数据还在那里,但你如果无法获取到它),为了避免这种情况,有必要每20亿个事务时,至少清理一次每个数据库中的每个表。

在 PostgreSQL 中解决事务ID环绕的方法:事务 ID 是环绕的,像一个钟表,当一个事务发起,总是认为有一半事务是“过去”,另一半“事务” 在将来,比较事务新旧看事务的“年龄”,年纪小的事务总是新创建的。



如果一个数据过老,如图所示,事务1 就会成为 事务4 将来的事务,导致对 事务4 不可见。

解决的办法是借助 Vacuum,它会识别出这些存在很长时间的数据,并将记录 “冷冻”,被冷冻的数据不遵循 XID 比较规则,它将对所有事务可见。这就解决了事务环绕ID的问题。

当前事务ID及环绕次数

查询当前事务ID

appserver_mars> select (txid_current() % (2^32)::bigint)::text::xid
+------------+
| xid        |
|------------|
| 1191485379 |
+------------+
SELECT 1
Time: 0.043s

查询环绕次数

appserver_mars> select (txid_current() >> 32) AS xid_epoch;
+-------------+
| xid_epoch   |
|-------------|
| 10          |
+-------------+
SELECT 1
Time: 0.046s

通过最新插入的记录,查询记录的 xmin 也能知道最新的事务ID:

appserver_mars> select user_id, xmin, xmax from user_biding where user_id = 'UUTEST001';
+-----------+------------+--------+
| user_id   | xmin       | xmax   |
|-----------+------------+--------|
| UUTEST001 | 1276864929 | 0      |
+-----------+------------+--------+
SELECT 1
Time: 0.065s

Vacuum 机制运行的全过程

Vacuum 与 Vacuum Full

Vacuum 分为 Standard VacuumVacuum Full 两种,区别如下

Standard Vacuum

  • 执行速度较快,能够与生产数据库并行运行(例如:SELECT、INSERT、UPDATE、DELETE 都不会受到影响,但是修改表的操作不能运行 ALTER TABLE)
  • 不能减小膨胀后的空间。
  • 运行时会找到很老的记录对它进行“冻结”。

Vacuum Full

  • 能够回收更多的磁盘空间,但需要占用最多一倍的磁盘空间来运行,且运行的非常缓慢
  • 会使用排他锁独占表,导致表不能读写,不能用于生产环境!
  • 运行后所有的记录都会被“冻结”。

Autovacuum 自动运行

PostgreSQL 有一个可选的、并且非常推荐开启的特性,即 Autovacuum

“自动清理的后台程序” 实际由多个进程组成,有一个持久的守护进程,负责启动进行执行任务,如果有 N 个数据库,会为每个数据库会启动一个进程进行处理。每个进程启动间隔参数为 autovacuum_naptime(默认一分钟间隔),autovacuum_max_workers 为同一时刻最多启动清理进程的数量。

配置概览

# 是否启用 Autovacuum
autovacuum = on

# 在规定时长内未完成的 vacuum 予以记录日志(单位:毫秒,“-1” 不不记录,“0”每次都记录)。
log_autovacuum_min_duration = -1

# 同一时刻最多启动清理进程的数量
autovacuum_max_workers = 3

# 两次 Vacuum 间隔时间
autovacuum_naptime = 1min

# 触发  Vacuum 所需的最小更新或删除的最小元组数量
# 触发规则(autovacuum_vacuum_scale_factor * table_size + autovacuum_vacuum_threshold)
autovacuum_vacuum_threshold = 50
autovacuum_vacuum_scale_factor = 0.2

# 触发 Vacuum 所需要插入的最小元组数量
autovacuum_vacuum_insert_threshold = 1000
autovacuum_vacuum_insert_scale_factor = 0.2

# 触发 Analyze 所需插入、更新、删除的最小元组数量
autovacuum_analyze_threshold = 50
autovacuum_analyze_scale_factor = 0.1

# 强制对数据库进行清理的 XID 上限值,表的 pg_class.relfrozenxid 字段可以实现的最大值。
# 即使禁用 Autovacuum,系统也将启动 Autovacuum 流程以防止事务环绕。
autovacuum_freeze_max_age = 200000000

# 指定表的 pg_class.relminmxid 字段可达的最大值。
#(Multixact:同一个元组相关联的事务ID可能有多个,为了在加锁(行共享锁)的时侯统一操作,PostgreSQL将与该元组相关联的多个事务ID组合起来用一个 MultiXactID 代替来管理)
autovacuum_multixact_freeze_max_age = 400000000

# 以毫秒计的时间长度,如果超过了开销限制,那么进程将睡眠此配置指定的时间
#(如果值为 “-1” 则默认使用 vacuum_cost_delay 值)
autovacuum_vacuum_cost_delay = 2ms

# 系统维护一个内部的记数器,跟踪所执行的各种 I/O 操作的近似开销。如果积累的开销达到了 vacuum_cost_limit 声明的限制,那么执行这个操作的进程将睡眠 vacuum_cost_delay 指定的时间。然后它会重置记数器然后继续执行。(如果值为 “-1” 则默认使用 vacuum_cost_limit 值)
autovacuum_vacuum_cost_limit = 400

# 指定每个自动清理工作者进程能使用的最大内存量。如果指定值时没有单位,则以千字节为单位。其默认值为 -1,表示转而使用 `maintenance_work_mem` 的值,当运行在其他上下文环境中时,这个设置对 Vacuum 的行为没有影响。
autovacuum_work_mem = -1

maintenance_work_mem = 64MB

关于 maintenance_work_mem 参数的补充:

maintenance_work_mem 参数

指定在维护性操作(例如VACUUM、CREATE INDEX 和 ALTER TABLE ADD FOREIGN KEY)中使用的最大的内存量。 如果没有制定单位,则以千字节为单位,其默认值是 64 兆字节(64MB)。在一个数据库会话中,一个时刻只有一个这样的操作可以被执行,并且一个数据库安装通常不会有太多这样的操作并发执行, 把这个数值设置得比 work_mem 大很多是安全的,更大的设置可以改进清理和恢复数据库转储的性能。

注意当自动清理运行时,可能会分配最多达这个内存的 autovacuum_max_workers 倍,因此要小心不要把该默认值设置得太高。 通过独立地设置autovacuum_work_mem可能会对控制这种情况有所帮助。

触发方式

Autovacuum 会在两种情况下会被触发

  1. 当 Update、Delete 的 Tuples 数量超过 autovacuum_vacuum_scale_factor * table_size + autovacuum_vacuum_threshold(Insert 配置参数同理)。
  2. 指定表上事务的最大年龄配置参数 autovacuum_freeze_max_age,默认为2亿,达到这个阀值将触发 autovacuum 进程,从而避免事务ID环绕。

Autovacuum 启动受到 autovacuum_vacuum_threshold(阈值) 和 autovacuum_vacuum_scale_factor(比例因子) 的影响。

每当死元组的数量,可以看作 pg_stat_all_tables.n_dead_tup,超过公式就会触发清理:

pg_stat_all_tables.n_dead_tup > autovacuum_vacuum_threshold + pg_class.reltuples * autovacuum_vacuum_scale_factor

当满足上面的公式时,该表将被视为需要清理,该公式表示在清理之前,高达20%的表可能是死元组(当系数为 0.2,50行的阈值是为了防止非常频繁地清理微小的表)默认的比例因子适用于中小型表,但对于非常大的表则没有那么多(在10GB表上,这大约是2GB的死元组,而在1TB表上则是~200GB)

如果是大表,通常将比例因子设置的很小(例如:0.01),这样表中的数据变化达到 1% 时触发 Autovacuum

autovacuum_vacuum_scale_factor = 0.01
autovacuum_vacuum_threshold = 50

或者放弃使用比例因子,完全用阈值来控制。(推荐)

autovacuum_vacuum_scale_factor = 0
autovacuum_vacuum_threshold = 10000

这将生成10000个死元组后触发清理,带来的问题是在 postgresql.conf 中更改这些参数会影响所有表,并且不利于影响小表的清理(例如系统表)

当清理小表时,最简单的解决方案是完全忽略问题,即使忽略问题,整体效果仍然非常明显。

清理过程

Vacuum 清理过程很简单,它从数据文件中读取页面(默认8kB数据块),并检查它是否需要清理,如果没有死元组,页面就会被丢弃而不做任何更改,否则它被清理(死元组被删除)

Vacuum 最佳实践

一些管理员倾向于禁用 Autovacuum,设置 Schedule Vacuuming 在晚上空闲的时间执行清理。

这种方式的优点是尽可能减少清理对线上的影响,但完全关闭 Autovacuum 在一个问题 —— 无法应对意想不到的尖峰,会导致空间过渡膨胀,不通过 Vacuum Full 则没办法恢复已经膨胀的磁盘空间。

较好的方式是调整 Autovacuum 参数,使其有较高的触发阈值,减少平时的自动清理次数,同时能够应对突增的修改和删除。

基于成本的 Vacuum 策略

此特性的目的是允许管理员减少这些命令对并发数据库活动的I/O影响。

像 Vacuum 和 Analyze 这样的维护命令不需要快速完成,并且这些命令通常应该显著干扰数据库的其它操作。

默认情况下,手动执行 Vacuum 命令没有开启此功能,要启用它,需将 vacuum_cost_delay 变量设置为非零值。

成本核算基于 postgresql.conf 定义三个配置,这三个配置值是“成本”的度量,没有具体的单位。

# 当页面是从 shared_buffers 读取时的花费
vacuum_cost_page_hit = 1

# 当页面在 shared_buffers 找不到,需要从操作系统中读取时的花费
vacuum_cost_page_miss = 10

# 当 Vacuum 将清理后的脏数据块写会磁盘时需要的花费
vacuum_cost_page_dirty = 20

如果数据库的性能、吞吐量较高,可以对数值进行调整,但通常不建议直接修改。

通过修改 vacuum_cost_limit 限制来进行成本限制就足够了。

设置清理者数量

  • autovacuum_max_workers 的建议值为 CPU核数/3。CPU 资源充足,I/O 性能较好时,可以适当加大。
  • vacuum_cost_limit 是对所有的工作者的整体限制,过多的增加工作者数量而不调整资源使用限制很可能会使清理速度降低。

设置 Autovacuum 的清理频率

比较理想的解决方案是建议在 postgresql.conf 中忽略比例因子,设置为较大的阈值:

autovacuum_vacuum_scale_factor = 0
autovacuum_vacuum_threshold = 10000

然后根据各个表的 delete 和 update 频繁程度以及表的数据量单独为每个表设置阈值:

ALTER TABLE t SET (autovacuum_vacuum_threshold = 100)

PostgreSQL 的默认参数一般都很保守,因为默认值通常基于几年前的机器评估的。

另外由于更新频繁的大表和不经常变动的小表触发频率很难保持同步,没有适合所有场景的最优配置参数,需要结合实际情况,根据机器的配置和表的更新频率进行调整。

建议开启自动清理,不使用比例因子,只使用阈值触发,同时配置定时任务在空闲时间段触发清理。

Tips 总结

  • 中等频率的 Standard Vacuum 比低频率的 Vacuum Full 更好。

  • 如果数据库运行了很久,并且从来没有打开过 Autovacuum,那么需要在打开 Autovacuum 之前全库手动运行vacuum 和 analyze(可能要非常久的时间)。

  • 请不要完全禁用 Autovacuum,除非真的知道自己在做什么,并且需要定期清理脚本,否则当问题发生时将不得不处理花费大量的时间处理,甚至可能需要停库、停机。

  • 对于更新频繁的交易系统,如果系统资源充足,可以缩小 autovacuum_vacuum_scale_factor 与 autovacuum_vacuum_threshold,让 vacuum 更频繁的清理。

  • analyze 的触发不宜过于频繁,需要进行的评估,因为每一次触发都会从头开始分析统计。

  • 使用 Alter table 设置参数要慎重,这会让系统变得复杂,不易维护。

Vacuum 线上示例

AWS Vacuum 资源消耗图例

一下截图为手动执行 vacuum 的资源消耗情况,蓝色为写入实例,橙色为读取实例

cpu

由上图可知,Vacuum 清理对于 CPU 的消耗很少,但 IO 的读取次数和吞吐量确实很高。

对比读取的 IO,可以看到 Vacuum 读取大量数据进行处理后会统一进行写回。

Mars 死亡元组与占比

SELECT
  relid,
  relname,
    n_dead_tup AS "死元组数",
    ( CASE WHEN n_live_tup > 0 THEN n_dead_tup :: float8 / n_live_tup :: float8 ELSE 0 END ) AS "死/活元组的比例" 
FROM
    pg_stat_all_tables
WHERE
    n_dead_tup > 50000;

结果

+---------+--------------------------+------------+----------------------+
| relid   | relname                  | 死元组数   | 死/活元组的比例      |
|---------+--------------------------+------------+----------------------|
| 520422  | user_event_count         | 61791      | 0.0288907289799853   |
| 193683  | user_card_collection     | 1848855    | 0.0305172031736089   |
| 193655  | user_activity_status     | 241437     | 0.0169121233916682   |
| 193584  | quiz_daily_coin_reward   | 180611     | 0.00956191620142077  |
| 193749  | user_poem_challenge      | 282308     | 0.0186522428504735   |
| 193611  | quiz_record              | 81272      | 0.0271187074101287   |
| 509654  | type_coin_history        | 784974     | 0.00961449261907086  |
| 193712  | user_daily_history       | 123150     | 0.0152117001752279   |
| 193728  | user_guess               | 55451      | 0.0110665309037032   |
| 193664  | user_asset               | 631261     | 0.0417559539480038   |
| 193771  | user_task                | 1023871    | 0.00541445520793488  |
| 193731  | user_pet_reward          | 197843     | 0.0166741492996399   |
| 279214  | quiz_total_coin_reward   | 78582      | 0.0287905527770013   |
| 193701  | user_coin                | 308302     | 0.0195438620246524   |
| 193483  | activities_daily_process | 88327      | 0.000704609598801467 |
| 193691  | user_challenge           | 255399     | 0.0168226854999805   |
| 193570  | koi_process              | 369251     | 0.0750457234012154   |
| 193762  | user_status              | 94934      | 0.00605523084395569  |
| 193759  | user_red_envelope        | 116598     | 0.00803769093721015  |
+---------+--------------------------+------------+----------------------+
SELECT 19

最近清理时间

pg_stat_all_tables 表中保存着最后一次自动清理和手动清理的执行时间。

appserver_mars> select relid, relname, last_vacuum, last_autovacuum from pg_stat_all_tables where relname = 'user_task'
+---------+-----------+---------------+------------------------------+
| relid   | relname   | last_vacuum   | last_autovacuum              |
|---------+-----------+---------------+------------------------------|
| 193771  | user_task | <null>        | 2021-07-05 01:38:18.87692+00 |
+---------+-----------+---------------+------------------------------+

参考: