【9】PostgreSQL 之 vacuum 死元组清理
PostgreSQL 之 vacuum 死元组清理
- 前言
- vacuum
- 手动 vacuum
- 自动 vacuum (autovacuum)
前言
初识 vacuum
先说下,什么是死元组?
前置说明:
表的死元组可通过查询pg_stat_user_tables
,也有其他视图可查,当前不做过多说明。
例如:
有一张业务表 tb_client
其内有 5条 数据,此时做 1次 update
操作,将会产生 1个死元组。
--- (1) 业务表初始数据
postgres=# select * from tb_client;id | name
------+-------1001 | A10011002 | B10021003 | C10031004 | D10041005 | E1005
(5 rows)postgres=# -- (2) 检查死元组情况
postgres=# select * from pg_stat_user_tables where relname = 'tb_client';
-[ RECORD 1 ]-------+----------
relid | 24587
schemaname | public
relname | tb_client
seq_scan | 1
seq_tup_read | 5
idx_scan |
idx_tup_fetch |
n_tup_ins | 5
n_tup_upd | 0 --- 更新情况
n_tup_del | 0
n_tup_hot_upd | 0
n_live_tup | 5
n_dead_tup | 0 ---- 注意这里表示死元组数
n_mod_since_analyze | 5
last_vacuum |
last_autovacuum |
last_analyze |
last_autoanalyze |
vacuum_count | 0
autovacuum_count | 0
analyze_count | 0
autoanalyze_count | 0postgres=# -- (3) 表数据,做 一次 Update
postgres=# update tb_client set name = '君九' where id = 1003;
UPDATE 1
postgres=# -- (4) 检查死元组情况
postgres=# select * from pg_stat_user_tables where relname = 'tb_client';
-[ RECORD 1 ]-------+----------
relid | 24587
schemaname | public
relname | tb_client
seq_scan | 2
seq_tup_read | 10
idx_scan |
idx_tup_fetch |
n_tup_ins | 5
n_tup_upd | 1
n_tup_del | 0
n_tup_hot_upd | 1
n_live_tup | 5
n_dead_tup | 1 --- 死元组数为 1 了;
n_mod_since_analyze | 6
last_vacuum |
last_autovacuum |
last_analyze |
last_autoanalyze |
vacuum_count | 0
autovacuum_count | 0
analyze_count | 0
autoanalyze_count | 0postgres=#
? 思考
在文章最开始就说了:
有一张业务表 tb_client 其内有5条
数据,此时做1次
update 操作,将会产生1个
死元组。
? 问题: 那同样的一条数据,如果做多次 update,那还会产生死元组吗?
演示:将 同一条数据, 做3次 update
postgres=# update tb_client set name = '君九' where id = 1003;
UPDATE 1
postgres=# update tb_client set name = '君九' where id = 1003;
UPDATE 1
postgres=# update tb_client set name = '君九' where id = 1003;
UPDATE 1
postgres=#
此时,在查看死元组情况:
如下查看,发现 死元组 数又累计 3个。
postgres=# select * from pg_stat_user_tables where relname = 'tb_client';
-[ RECORD 1 ]-------+----------
relid | 24587
schemaname | public
relname | tb_client
seq_scan | 5
seq_tup_read | 25
idx_scan |
idx_tup_fetch |
n_tup_ins | 5
n_tup_upd | 4
n_tup_del | 0
n_tup_hot_upd | 4
n_live_tup | 5
n_dead_tup | 4 --- 注意这里,死元组又 累计 3个。
n_mod_since_analyze | 9
last_vacuum |
last_autovacuum |
last_analyze |
last_autoanalyze |
vacuum_count | 0
autovacuum_count | 0
analyze_count | 0
autoanalyze_count | 0postgres=#
之所以此情况,和PostgreSQL数据存储有关,后续会说明,当前先 粗略理解 如下:
(a). 初始数据如下,放在一个数组里面。
(b). 当(update)修改 C1003
为君九
数据时,并不是在原来的数据上修改的(即不是修改数组下标 2 的位置上直接修改),而是在数组下标 5 的位置上新增了一条数据。
?问题:那原来的数据(数组下标 2)怎么办?
=》将其设置为不可见(注意:是不可见,并不是删除。),这个不可见的数据,即死元组
。
当使用select
语句查询数据时,PostgreSQL底层会自动做处理让其 只能 查找可见的数据。
但是,
如果 你的 select
语句进行了全表扫,则会扫描到那些不可见的数据。
例如:
若一张业务表,tb_client 有 100万 条数据,死元组数 有200万;
select * from tb_client where mobile = xxxx;
查询时,mobile 的索引刚好失效了,进而进行了全表扫,此时数据库在底层查询时扫描的数据是 100万 + 200万,共计300万。必然会影响查询性能。
扩展:
死元组数过大也有可能会引起事务号回卷问题,后续会详细介绍,此处在不做过多说明。
先理解什么是死元组
。
那如何解决这些问题?
》必然是清理掉这些死元组
,即当前章节说的 vacuum
vacuum
手动 vacuum
手动清理 死元组
即人工执行 vacuum
语句,其语法如下:
vacuum
语法:
postgres=# \h vacuum
Command: VACUUM
Description: garbage-collect and optionally analyze a database
Syntax:
VACUUM [ ( { FULL | FREEZE | VERBOSE | ANALYZE | DISABLE_PAGE_SKIPPING } [, ...] ) ] [ table_name [ (column_name [, ...] ) ] ]
VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ table_name ]
VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ table_name [ (column_name [, ...] ) ] ]postgres=#
演示:
--- (1) 清理之前,查询一下 死元组数
postgres=# select * from pg_stat_user_tables where relname = 'tb_client';
-[ RECORD 1 ]-------+----------
relid | 24587
schemaname | public
relname | tb_client
seq_scan | 5
seq_tup_read | 25
idx_scan |
idx_tup_fetch |
n_tup_ins | 5
n_tup_upd | 4
n_tup_del | 0
n_tup_hot_upd | 4
n_live_tup | 5
n_dead_tup | 4 ----- 注意这里:死元组数
n_mod_since_analyze | 9
last_vacuum |
last_autovacuum |
last_analyze |
last_autoanalyze |
vacuum_count | 0
autovacuum_count | 0
analyze_count | 0
autoanalyze_count | 0postgres=# --- (2) vacuum 表名称; 进行清理
postgres=# vacuum tb_client;
VACUUM
postgres=#--- (3) 再次查看死元组数
postgres=# select * from pg_stat_user_tables where relname = 'tb_client';
-[ RECORD 1 ]-------+------------------------------
relid | 24587
schemaname | public
relname | tb_client
seq_scan | 5
seq_tup_read | 25
idx_scan |
idx_tup_fetch |
n_tup_ins | 5
n_tup_upd | 4
n_tup_del | 0 ----- 注意这里,已清理为 零
n_tup_hot_upd | 4
n_live_tup | 5
n_dead_tup | 0
n_mod_since_analyze | 9
last_vacuum | 2025-07-11 15:22:44.696469+08
last_autovacuum |
last_analyze |
last_autoanalyze |
vacuum_count | 1
autovacuum_count | 0
analyze_count | 0
autoanalyze_count | 0postgres=#
另外,vacuum
清理表死元组时,还可以添加一些额外参数(例如:full
)
vacuum
和 vacuum full
区别:
》 扩展说明
标准
VACUUM
空间处理:只标记被删除/更新行占用的空间为"可重用",不会缩小物理文件大小
并发性:执行时不阻塞正常的读写操作
用途:
- 维护可见性映射(visibility map)
- 冻结旧的事务ID
- 更新统计信息(配合ANALYZE)
优势:可以在生产环境中频繁运行,影响小
VACUUM FULL
空间处理:完全重组表,将空间返还给操作系统,显著减少表占用的磁盘空间
并发性:需要排它锁,会阻塞所有对该表的访问
内部操作:
- 创建表的全新副本
- 只拷贝有效数据到新文件
- 删除原文件,用新文件替代
风险:
- 长时间锁表
- 执行期间需要额外空间(原始表大小的额外空间)
- 可能造成WAL日志大量增长
自动 vacuum (autovacuum)
自动 vacuum 这些参数,可在 PostgreSQL 数据库中的 postgresql.conf
文件中配置。
与 autovacuum
相关参数如下,
postgres=# select name, setting from pg_settings where name like 'autovacuum%';name | setting
-------------------------------------+-----------autovacuum | onautovacuum_analyze_scale_factor | 0.1autovacuum_analyze_threshold | 50autovacuum_freeze_max_age | 200000000autovacuum_max_workers | 3autovacuum_multixact_freeze_max_age | 400000000autovacuum_naptime | 60autovacuum_vacuum_cost_delay | 20autovacuum_vacuum_cost_limit | -1autovacuum_vacuum_scale_factor | 0.2autovacuum_vacuum_threshold | 50autovacuum_work_mem | -1
(12 rows)postgres=#
参数说明:
( 1 )autovacuum
(on/off):是否启用自动 vacuum 进程,默认为 on
( 2 )autovacuum_analyze_scale_factor
(0.1):触发 ANALYZE 操作的表数据变化比例因子(相对于表大小),例如:100万行的表,当10万行(10%)发生变化时会触发ANALYZE。
( 3 )autovacuum_analyze_threshold
(50):触发 ANALYZE 操作的最小变更行数阈值,与scale_factor一起使用,取两者计算结果的较大值。
( 4 )autovacuum_freeze_max_age
(200000000):事务ID上限,超过此值强制进行vacuum以防止事务ID回卷 以事务数为单位
( 5 )autovacuum_max_workers
(3):可同时运行的最大autovacuum工作进程数
( 6 )autovacuum_multixact_freeze_max_age
(400000000):多事务ID上限,超过此值强制进行vacuum以防止多事务ID回卷
( 7 )autovacuum_naptime
(60):autovacuum进程两次运行之间的休眠时间(秒)
( 8 )autovacuum_vacuum_cost_delay
(20):当autovacuum达到cost限制时的延迟时间(毫秒)用于控制autovacuum对系统性能的影响。
( 9 )autovacuum_vacuum_cost_limit
(-1):autovacuum的cost限制,-1表示使用vacuum_cost_limit的值控制autovacuum的工作强度
( 10 )autovacuum_vacuum_scale_factor
(0.2):触发VACUUM操作的表数据变化比例因子(相对于表大小)例如:100万行的表,当20万行(20%)发生变化时会触发VACUUM
( 11 )autovacuum_vacuum_threshold
(50):触发VACUUM操作的最小变更行数阈值,与scale_factor一起使用,取两者计算结果的较大值。
( 12 )autovacuum_work_mem
(-1):每个autovacuum工作进程可用的内存量,-1表示使用maintenance_work_mem的值
上述的参数中,可看以下参数:
--- vacuum 清理死元组autovacuum_vacuum_scale_factor | 0.2autovacuum_vacuum_threshold | 50---- analyze 收集统计信息autovacuum_analyze_scale_factor | 0.1autovacuum_analyze_threshold | 50
autovacuum_vacuum_scale_factor
参数设定为 0.2(20%),表示当表中 20% 的数据被修改时触发 VACUUM 操作。但这一机制存在一个明显问题:对于小表来说,20% 的修改比例可能仅对应几行数据。
例如:
百万行
大表 = 20万行修改 → 此时,占比 20%,值得清理
单行
小表: = 1行修改 → 此时,占比 100%,但是,不值得清理
为解决这个问题,PostgreSQL 引入了 autovacuum_vacuum_threshold
参数(默认50行),形成双重触发条件:
(1)必须达到 20% 的修改比例 且
(2)修改行数至少达到 50 行
这种设计有效避免了小表频繁触发不必要的 VACUUM 操作。