文章目录
- 每日一句正能量
- 1. 背景与问题
- 2. 环境与数据
- 3. 复现过程
- 4. 方案实施
- 在线重整工具(pg_repack)与 VACUUM FULL 对比
- 5. 结果对比
- 6. 风险与复盘
- 故障排查实战案例:VACUUM FULL 锁等待超时
- 1. 故障模拟
- 2. 诊断过程
- 3. 处理步骤
- 4. 最终效果
- 5. 排查思路总结
本文围绕 PostgreSQL 高更新表膨胀问题,从诊断、复现到治理与复盘,给出完整实践路径。
每日一句正能量
日拱一卒,功不唐捐。
目标遥不可及?那就专注于今天能推进的这一小步。不追求石破天惊,只相信细水长流。
坚持每天推进一小步,积累终会带来改变。
1. 背景与问题
高频 UPDATE/DELETE 业务容易产生大量失效元组(Dead Tuple),导致表膨胀、索引膨胀、扫描变慢和磁盘空间持续增长。某订单系统核心业务表每天更新超过千万次,磁盘容量持续告警,查询响应逐渐变慢,因此开展表膨胀诊断和空间治理演练。
2. 环境与数据
- PostgreSQL 16
- Linux 9
- 数据规模:1.6TB
- 高更新表:orders
诊断SQL:
SELECTrelname,n_live_tup,n_dead_tupFROMpg_stat_user_tablesORDERBYn_dead_tupDESC;SELECTpg_size_pretty(pg_total_relation_size('orders'));目标:
- RTO≤30分钟
- RPO≤5分钟
3. 复现过程
故障注入:
- 持续批量UPDATE和DELETE。
- 暂停自动VACUUM(仅测试环境)。
- 观察Dead Tuple增长。
- 记录查询性能和空间变化。
监控:
- n_dead_tup
- 表大小
- 查询耗时
- Autovacuum日志
4. 方案实施
部署/演练流程:
- 评估膨胀比例。
- 执行VACUUM (ANALYZE)。
- 必要时使用VACUUM FULL或在线重整工具。
- 重建膨胀索引。
- 恢复业务并验证。
下面是完整的治理流程图,从评估膨胀比例到恢复业务验证,每个步骤都标注了关键输出或检查点:
检查项:
- Dead Tuple下降
- 表空间回收
- 查询恢复
- 主备同步正常
- 业务健康检查
在线重整工具(pg_repack)与 VACUUM FULL 对比
当表膨胀严重且业务无法接受长时间阻塞时,可选用在线重整工具(如 pg_repack)替代 VACUUM FULL。两者对比如下:
| 对比维度 | VACUUM FULL | pg_repack |
|---|---|---|
| 锁行为 | 全程持有 AccessExclusiveLock,阻塞读写 | 仅在创建/交换临时表瞬间短暂加锁,业务基本无感 |
| 空间需求 | 需要与表等量的额外磁盘空间 | 同样需要额外空间,但可分批处理 |
| 执行速度 | 单线程重写,大表耗时较长 | 可并行,整体更快 |
| 索引重建 | 需单独 REINDEX | 自动重建索引 |
| 适用场景 | 可接受停机窗口、表较小或低峰期 | 7x24 在线业务、大表、无法接受长时间阻塞 |
| 风险 | 锁等待超时、主备延迟 | 依赖触发器/日志表,需预留空间并关注主备延迟 |
pg_repack 基本使用示例:
# 安装(以 CentOS/RHEL 为例)yuminstallpg_repack# 对 orders 表在线重整(需超级用户或表属主)pg_repack-h127.0.0.1-p5432-Upostgres-dpostgres-torders# 若表有主键,可加 --no-order 跳过按主键排序,减少 IOpg_repack-h127.0.0.1-p5432-Upostgres-dpostgres-torders --no-order提示:pg_repack 执行期间会短暂持有排他锁,建议仍避开业务最高峰;同时确保磁盘有足够空间存放临时表,并关注主备延迟。
针对 orders 表每天千万级更新的高膨胀场景,建议按以下参数调优 Autovacuum,让回收更及时、避免膨胀累积:
| 参数名 | 当前默认值 | 建议值 | 适用场景说明 |
|---|---|---|---|
| autovacuum_vacuum_scale_factor | 0.2 | 0.05 | 默认按表大小 20% 触发,高更新表会积压大量 Dead Tuple;调低后更早触发 VACUUM |
| autovacuum_vacuum_threshold | 50 | 1000 | 与 scale_factor 配合,避免小表频繁触发;orders 表较大,可适当提高阈值 |
| autovacuum_naptime | 60s | 30s | 缩短两次自动 VACUUM 的间隔,让高更新表更快进入回收队列 |
| autovacuum_max_workers | 3 | 6 | 增加并行 worker,避免多个大表同时膨胀时排队等待 |
| autovacuum_analyze_scale_factor | 0.1 | 0.02 | 更频繁更新统计信息,保证查询计划不因数据变化而劣化 |
orders 表配置示例(按表级参数覆盖全局默认值):
ALTERTABLEordersSET(autovacuum_vacuum_scale_factor=0.05,autovacuum_vacuum_threshold=1000,autovacuum_analyze_scale_factor=0.02);上图展示了调优 Autovacuum 参数后,Dead Tuple 回收效率与表空间释放的对比效果。
5. 结果对比
| 指标 | 治理前 | 治理后 |
|---|---|---|
| 表大小 | 860GB | 610GB |
| Dead Tuple | 1.2亿 | 430万 |
| 平均查询耗时 | 420ms | 170ms |
| RTO | 29分钟 | 24分钟 |
| RPO | 5分钟 | 2分钟 |
治理后空间释放明显,索引扫描效率恢复,业务高峰响应稳定。
6. 风险与复盘
风险:
- VACUUM FULL需要额外空间并可能阻塞。
- REINDEX会消耗IO,应避开业务高峰。
- 未提前备份和演练可能增加维护风险。
复盘建议:
- 建立表膨胀定期巡检。
- 结合Autovacuum参数持续优化。
- 每次治理记录RTO、RPO、耗时和空间回收率。
- 建立检查清单:备份、主备、空间、业务验证、回滚方案。
故障回滚方案:
若 VACUUM FULL 或 REINDEX 过程中出现异常(如锁等待超时、磁盘空间不足、主备延迟过大),按以下步骤回滚:
- 停止操作:立即终止当前 VACUUM FULL / REINDEX 会话,避免持续占用 IO 和锁资源。
- 恢复业务:确认表锁已释放,恢复业务读写;若主库受影响,优先切换或恢复主备连接。
- 检查主备同步:通过 pg_stat_replication 确认主备延迟恢复,WAL 发送与接收正常。
- 检查空间释放:确认异常中断后表空间未异常增长,必要时清理临时文件或 WAL 堆积。
- 记录并复盘:记录异常原因、耗时与影响范围,更新检查清单和回滚预案。
故障排查速查表:
| 故障现象 | 可能原因 | 快速诊断命令 | 处理措施 |
|---|---|---|---|
| VACUUM FULL 锁等待超时 | 业务长事务或高并发读写持有表锁 | SELECT pid, state, wait_event_type, wait_event FROM pg_stat_activity WHERE wait_event_type = 'Lock'; | 终止阻塞会话或等待长事务结束,错峰执行 VACUUM FULL |
| 磁盘空间不足 | 表膨胀占用大量空间,VACUUM FULL 需要额外空间 | SELECT pg_size_pretty(pg_total_relation_size('orders')); | 先清理 WAL/临时文件释放空间,或改用在线重整工具分批处理 |
| 主备延迟过大 | 大事务或 VACUUM FULL 产生大量 WAL,备库回放跟不上 | SELECT * FROM pg_stat_replication; | 暂停写入高峰,等待备库追平延迟,必要时调整 max_wal_senders 或同步策略 |
| Dead Tuple 不下降 | Autovacuum 未及时触发或参数配置过保守 | SELECT relname, n_live_tup, n_dead_tup FROM pg_stat_user_tables WHERE relname = 'orders'; | 调低 autovacuum_vacuum_scale_factor、缩短 naptime,必要时手动 VACUUM (ANALYZE) |
故障排查实战案例:VACUUM FULL 锁等待超时
下面通过一次完整的故障演练,演示从发现锁等待超时到恢复业务的全过程。
下面是本次故障排查的完整流程图,从故障模拟到恢复验证,每个环节都标注了关键诊断命令或检查点:
1. 故障模拟
在业务高峰时段对 orders 表执行 VACUUM FULL,同时模拟一个长事务持续持有表锁:
-- 会话 A:模拟业务长事务,持有 orders 表锁BEGIN;UPDATEordersSETstatus='processing'WHEREorder_id<100000;-- 故意不提交,模拟长事务-- 会话 B:执行 VACUUM FULL,等待表锁VACUUMFULLorders;会话 B 长时间无法获取锁,最终触发锁等待超时,日志片段如下:
2026-08-31 14:32:18.123 CST [28471] ERROR: canceling statement due to lock timeout 2026-08-31 14:32:18.123 CST [28471] DETAIL: process 28471 still waiting for AccessExclusiveLock on relation "orders" of database "postgres" after 30000.123 ms 2026-08-31 14:32:18.123 CST [28471] STATEMENT: VACUUM FULL orders;2. 诊断过程
通过 pg_stat_activity 定位阻塞源:
SELECTpid,state,wait_event_type,wait_event,queryFROMpg_stat_activityWHEREwait_event_type='Lock';-- 结果:-- pid | state | wait_event_type | wait_event | query-- 28471 | active | Lock | relation | VACUUM FULL orders;-- 28102 | idle in transaction | Lock | relation | UPDATE orders SET status = 'processing' WHERE order_id < 100000;进一步确认阻塞链:
SELECTblocked.pidASblocked_pid,blocking.pidASblocking_pid,blocking.queryASblocking_queryFROMpg_stat_activity blockedJOINpg_stat_activity blockingONblocking.pid=ANY(pg_blocking_pids(blocked.pid))WHEREblocked.queryILIKE'%VACUUM FULL%';3. 处理步骤
- 与业务方确认长事务是否可以结束,若为测试事务直接终止:
SELECTpg_terminate_backend(28102); - 确认锁已释放后,错峰重新执行 VACUUM FULL:
VACUUMFULLorders; - 重建膨胀索引并验证:
REINDEXTABLEorders;SELECTpg_size_pretty(pg_total_relation_size('orders'));
4. 最终效果
| 指标 | 故障前 | 处理后 |
|---|---|---|
| 表大小 | 860GB | 610GB |
| Dead Tuple | 1.2亿 | 430万 |
| 平均查询耗时 | 420ms | 170ms |
| 锁等待 | 超时 | 无阻塞 |
5. 排查思路总结
- 先定位阻塞源:通过 pg_stat_activity 和 pg_blocking_pids 找到持有锁的会话,判断是长事务还是高并发读写。
- 区分处理策略:测试事务可直接终止;生产长事务需与业务方确认,避免误杀关键任务。
- 错峰执行:VACUUM FULL 和 REINDEX 尽量安排在业务低峰,降低锁冲突概率。
- 事后复盘:记录锁等待时长、阻塞来源和处理耗时,更新检查清单,必要时调优 Autovacuum 参数减少膨胀累积。
本文结合部署流程、故障注入、诊断SQL、治理前后对比以及RTO/RPO验证,总结了高更新表空间治理的实践方法。
转载自:https://blog.csdn.net/u014727709/article/details/164125404
欢迎 👍点赞✍评论⭐收藏,欢迎指正