80人参与 • 2026-05-12 • MsSqlserver
postgresql 使用 mvcc(多版本并发控制)实现事务隔离:
这导致大量死元组(dead tuples) 残留在表中:
┌──────────────────────────────────────────────────────┐
│ 表空间 │
│ [活跃数据] [死元组] [活跃数据] [死元组] [死元组] │
│ │
│ 死元组累积 → 空间浪费 → 查询变慢 → 需要 vacuum │
└──────────────────────────────────────────────────────┘
vacuum 就是负责回收这些死元组、释放空间、更新统计信息的维护命令。
每次 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% 空间被死元组占用
大量删除后,空间被死元组占据无法重用:
-- 每天删除过期日志 delete from interface_execution_log where start_time < now() - interval '90 days';
⚠️ 即使删除了 500 万行,表文件大小也不会缩小,空间不会归还给操作系统。
-- 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 万行的体量,内部全是死元组空洞
-- 事务 a 开启但长时间未提交 begin; select * from lorder_master_info where id = 1; -- ⏰ 业务处理了很久,事务未提交... -- 此期间,其他事务产生的所有死元组都无法被 vacuum 清理 -- 因为事务 a 可能还需要读到旧版本数据
⚠️ 这是线上最常见的表膨胀根因之一,尤其是跑批任务或报表查询时。
-- 每秒上万次库存扣减 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 orders; -- 清理 + 更新统计信息(最常用) vacuum analyze orders; -- 查看清理详情 vacuum verbose analyze orders;
特点:
vacuum full verbose analyze orders;
工作原理:
1. 创建新的表文件
2. 将所有活跃数据紧凑复制到新文件
3. 删除旧文件,重建所有索引
4. ✅ 空间归还给操作系统,表文件大幅缩小
特点:
⚠️ 仅在业务低峰期(如凌晨维护窗口)执行,生产高峰期禁止使用。
统计信息过时会导致查询优化器选错执行计划:
-- 数据大量变更后,一定要执行 analyze vacuum analyze lorder_master_info; -- 或只更新统计信息(不清理) analyze lorder_master_info;
典型场景:
效果对比:
统计信息过时 → 优化器估算:10 行 → 选 nested loop → 执行 30 秒
执行 analyze → 优化器估算:100 万行 → 选 hash join → 执行 2 秒
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;
清理前:表 100gb,有效数据 60gb,死元组 40gb → 全表扫描读 100gb
vacuum 后:死元组空间标记为可重用,表不再无限膨胀
vacuum full 后:表缩减为 60gb → 全表扫描只需读 60gb,i/o 节省 40%
清理前:
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%
统计信息过时是慢查询的常见根因:
-- 统计信息过时 → 优化器估算行数偏差巨大 → 选错 join 方式 → 慢 30 倍 -- vacuum analyze 后 → 统计准确 → 选 hash join → 正常速度 vacuum analyze orders;
清理前:buffer 中缓存大量死元组页,热数据被挤出
清理后:buffer 中全是有效数据,缓存命中率显著提升
定期 vacuum 会自动冻结旧事务 id,防止 xid 回绕导致数据库不可用。
-- 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';
-- 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;
针对项目中的大分区表,逐个分区清理,避免一次性影响范围过大:
-- 逐个分区清理(推荐)
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 $$;
-- ✅ 大批量导入后,立即更新统计信息 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;
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 清理机制内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
您想发表意见!!点此发布评论
版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。
发表评论