一、为什么需要 vacuum?
postgresql 使用 mvcc(多版本并发控制)实现事务隔离:
- update 操作:本质是 delete + insert,旧版本数据并不会立即删除
- delete 操作:只是将数据标记为"已删除",物理空间不释放
这导致大量死元组(dead tuples) 残留在表中:
┌──────────────────────────────────────────────────────┐
│ 表空间 │
│ [活跃数据] [死元组] [活跃数据] [死元组] [死元组] │
│ │
│ 死元组累积 → 空间浪费 → 查询变慢 → 需要 vacuum │
└──────────────────────────────────────────────────────┘
vacuum 就是负责回收这些死元组、释放空间、更新统计信息的维护命令。
二、哪些操作会产生空间碎片?
2.1 高频 update
每次 update 都会保留旧版本,旧版本变成死元组:
-- 订单状态每次变更,都产生一个旧版本死元组 update orders set status = 'paid' where order_id = 12345; update orders set status = 'shipped' where order_id = 12345; update orders set status = 'delivered' where order_id = 12345;
初始: page 1: [row-v1] [空闲] [空闲] [空闲]
3次update后:
page 1: [row-v1(死)] [row-v2(死)] [row-v3(死)] [row-v4]
← 60% 空间被死元组占用
2.2 高频 delete
大量删除后,空间被死元组占据无法重用:
-- 每天删除过期日志 delete from interface_execution_log where start_time < now() - interval '90 days';
⚠️ 即使删除了 500 万行,表文件大小也不会缩小,空间不会归还给操作系统。
2.3 批量数据导入 + 清理
-- step 1:导入 1000 万行临时数据 insert into odh_sell_in_inbound select * from external_source; -- step 2:数据处理完毕,删除临时数据 delete from odh_sell_in_inbound where batch_id = 'xxx'; -- 结果:表大小维持在 1000 万行的体量,内部全是死元组空洞
2.4 长时间未提交的事务
-- 事务 a 开启但长时间未提交 begin; select * from lorder_master_info where id = 1; -- ⏰ 业务处理了很久,事务未提交... -- 此期间,其他事务产生的所有死元组都无法被 vacuum 清理 -- 因为事务 a 可能还需要读到旧版本数据
⚠️ 这是线上最常见的表膨胀根因之一,尤其是跑批任务或报表查询时。
2.5 高并发小事务
-- 每秒上万次库存扣减 update inventory set stock = stock - 1 where product_id = 'hot001';
短时间内大量死元组堆积,查询性能会急剧下降。
三、如何诊断表膨胀?
-- 查看死元组比例,找出需要清理的表
select
schemaname,
tablename,
pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) as total_size,
n_live_tup as live_tuples,
n_dead_tup as dead_tuples,
round(n_dead_tup * 100.0 / nullif(n_live_tup + n_dead_tup, 0), 2) as dead_ratio,
last_autovacuum
from pg_stat_user_tables
where n_dead_tup > 10000
order by n_dead_tup desc
limit 20;
判断标准:
| 死元组比例 | 状态 | 处理建议 |
|---|---|---|
| < 5% | 🟢 健康 | 无需操作 |
| 5% ~ 20% | 🟡 关注 | 执行 vacuum analyze |
| > 20% | 🔴 膨胀 | 立即执行 vacuum,严重时用 vacuum full |
四、vacuum 的类型与使用
4.1 vacuum —— 日常清理(推荐)
-- 清理单表 vacuum orders; -- 清理 + 更新统计信息(最常用) vacuum analyze orders; -- 查看清理详情 vacuum verbose analyze orders;
特点:
- ✅ 不锁表,允许并发读写,可在线执行
- ✅ 将死元组空间标记为可重用(供后续 insert/update 使用)
- ❌ 不归还空间给操作系统,表文件大小不变
4.2 vacuum full —— 深度清理(维护窗口)
vacuum full verbose analyze orders;
工作原理:
1. 创建新的表文件
2. 将所有活跃数据紧凑复制到新文件
3. 删除旧文件,重建所有索引
4. ✅ 空间归还给操作系统,表文件大幅缩小
特点:
- ✅ 彻底回收空间,消除所有碎片
- ❌ 需要 access exclusive 锁,执行期间表不可读写
- ❌ 需要约 2 倍表大小的临时磁盘空间
- ❌ 大表耗时很长(gb 级别可能需要数十分钟)
⚠️ 仅在业务低峰期(如凌晨维护窗口)执行,生产高峰期禁止使用。
4.3 vacuum analyze —— 清理 + 更新统计信息
统计信息过时会导致查询优化器选错执行计划:
-- 数据大量变更后,一定要执行 analyze vacuum analyze lorder_master_info; -- 或只更新统计信息(不清理) analyze lorder_master_info;
典型场景:
- 大批量数据导入后
- 创建新索引后
- 某张表数据量变化超过 20% 后
效果对比:
统计信息过时 → 优化器估算:10 行 → 选 nested loop → 执行 30 秒
执行 analyze → 优化器估算:100 万行 → 选 hash join → 执行 2 秒
4.4 vacuum freeze —— 防止事务 id 回绕
postgresql 使用 32 位事务 id(xid),用完(约 42 亿)后会回绕,导致数据混乱:
-- 查看各表的事务年龄(接近 2 亿时需警惕)
select
relname,
age(relfrozenxid) as xid_age,
pg_size_pretty(pg_total_relation_size(oid)) as size
from pg_class
where relkind = 'r'
order by age(relfrozenxid) desc
limit 10;
-- 出现以下告警时,立即执行:
-- warning: database must be vacuumed within 1000000 transactions
vacuum freeze;
五、vacuum 的核心好处
5.1 回收空间,降低 i/o
清理前:表 100gb,有效数据 60gb,死元组 40gb → 全表扫描读 100gb
vacuum 后:死元组空间标记为可重用,表不再无限膨胀
vacuum full 后:表缩减为 60gb → 全表扫描只需读 60gb,i/o 节省 40%
5.2 提升查询性能
清理前:
page 1: [data][dead][dead][data] ← 扫描效率 50%
page 2: [dead][dead][data][dead]
page 3: [data][data][dead][dead]
→ 扫描 3 页,只有 50% 有效数据清理后(vacuum full):
page 1: [data][data][data][data] ← 扫描效率 100%
page 2: [data][data][空闲][空闲]
→ 扫描 2 页,100% 有效数据,性能提升 ~60%
5.3 优化查询执行计划
统计信息过时是慢查询的常见根因:
-- 统计信息过时 → 优化器估算行数偏差巨大 → 选错 join 方式 → 慢 30 倍 -- vacuum analyze 后 → 统计准确 → 选 hash join → 正常速度 vacuum analyze orders;
5.4 改善 shared buffer 缓存命中率
清理前:buffer 中缓存大量死元组页,热数据被挤出
清理后:buffer 中全是有效数据,缓存命中率显著提升
5.5 防止事务 id 回绕(数据库崩溃风险)
定期 vacuum 会自动冻结旧事务 id,防止 xid 回绕导致数据库不可用。
六、实战清理操作指南
6.1 标准清理(不锁表)
-- step 1:找出需要清理的表
select tablename, n_dead_tup,
round(n_dead_tup * 100.0 / nullif(n_live_tup + n_dead_tup, 0), 2) as dead_ratio
from pg_stat_user_tables
where n_dead_tup > 100000
or (n_dead_tup * 100.0 / nullif(n_live_tup + n_dead_tup, 0)) > 20
order by n_dead_tup desc;
-- step 2:执行清理(不锁表)
vacuum verbose analyze lorder_master_info;
-- step 3:验证效果
select pg_size_pretty(pg_total_relation_size('lorder_master_info')) as size,
n_dead_tup, last_vacuum
from pg_stat_user_tables
where tablename = 'lorder_master_info';
6.2 深度清理(维护窗口执行)
-- step 1:确认磁盘空间(需要 2 倍表大小)
select pg_size_pretty(pg_total_relation_size('orders')) as current_size,
pg_size_pretty(pg_total_relation_size('orders') * 2) as required_space;
-- step 2:设置超时保护
set statement_timeout = '2h';
-- step 3:执行深度清理(⚠️ 会锁表)
vacuum full verbose analyze orders;
6.3 分区表清理策略
针对项目中的大分区表,逐个分区清理,避免一次性影响范围过大:
-- 逐个分区清理(推荐)
vacuum verbose analyze lorder_master_info_ap_st_fy2526_q1;
vacuum verbose analyze lorder_master_info_ap_st_fy2526_q2;
vacuum verbose analyze lorder_master_info_ap_st_fy2526_q3;
vacuum verbose analyze lorder_master_info_ap_st_fy2526_q4;
-- 批量清理所有子分区
do $$
declare r record;
begin
for r in
select tablename from pg_tables
where schemaname = 'public'
and tablename like 'lorder_master_info_%'
loop
raise notice '正在清理: %', r.tablename;
execute 'vacuum verbose analyze ' || quote_ident(r.tablename);
end loop;
end $$;
6.4 批量操作前后的最佳实践
-- ✅ 大批量导入后,立即更新统计信息 insert into odh_sell_in_inbound select * from staging_table; vacuum analyze odh_sell_in_inbound; -- ✅ 大批量删除后,回收死元组空间 delete from interface_execution_log where start_time < now() - interval '90 days'; vacuum interface_execution_log; -- ✅ 创建索引后,更新统计信息 create index concurrently idx_xxx on lorder_master_info (geo_type, fiscal_year); analyze lorder_master_info;
6.5 在线清理方案(pg_repack)
vacuum full 会锁表,生产环境推荐用 pg_repack 替代:
# 安装(ubuntu) sudo apt-get install postgresql-16-repack # 在线整理表,不锁表,允许读写 pg_repack -d mydb -t orders pg_repack -d mydb -t lorder_master_info_ap_st_fy2526_q4
| 对比项 | vacuum full | pg_repack |
|---|---|---|
| 锁表 | ✅ 锁表(不可读写) | ❌ 不锁表 |
| 空间回收 | ✅ 完全回收 | ✅ 完全回收 |
| 磁盘需求 | 2 倍表大小 | 2 倍表大小 |
| 生产适用 | ❌ 仅维护窗口 | ✅ 随时可用 |
适用版本:postgresql 12+
到此这篇关于postgresql vacuum 清理机制详解的文章就介绍到这了,更多相关postgresql vacuum 清理机制内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论