55人参与 • 2026-03-26 • MsSqlserver
在现代数据驱动的应用系统中,postgresql 作为一款功能强大、开源且高度可扩展的关系型数据库,被广泛应用于各类业务场景。然而,随着业务的持续运行和数据的不断增长,数据库不可避免地会面临性能下降的问题。其中,数据碎片化(data fragmentation)和表空间管理不当是两个常见但容易被忽视的性能瓶颈。
本文将深入探讨 postgresql 中的数据碎片整理机制与表空间优化策略,结合实际场景、原理剖析以及 java 应用示例,帮助开发者和 dba 构建更高效、更稳定的数据库系统。无论你是刚接触 postgresql 的新手,还是已有多年经验的资深工程师,相信都能从中获得实用的见解。
在 postgresql 中,数据以“元组”(tuple)的形式存储在数据页(page)中。每个数据页默认大小为 8kb。当执行 update 或 delete 操作时,postgresql 并不会立即物理删除旧数据,而是采用 mvcc(多版本并发控制)机制,将旧版本标记为“死亡元组”(dead tuple),同时写入新版本。这种设计保证了高并发下的读写一致性,但也带来了副作用:表和索引中会积累大量无用的“死数据”。
这些死亡元组占据着磁盘空间,却不再被查询使用,导致:
💡 举个例子:假设你有一个用户表,每天有 10 万条记录被更新。一个月后,表的实际有效数据可能只有 50 万行,但物理存储可能已膨胀到 300 万行的规模——其中 250 万是“幽灵数据”。
这种现象就是典型的数据碎片化。
postgresql 提供了 vacuum 命令来回收死亡元组占用的空间。它分为两种形式:
vacuum table_name;
insert 重用;full);vacuum full table_name;
postgresql 默认启用 autovacuum 后台进程,根据配置自动触发 vacuum 和 analyze。关键参数包括:
autovacuum_vacuum_threshold = 50 # 触发 vacuum 的最小死亡元组数 autovacuum_vacuum_scale_factor = 0.2 # 表大小的 20% + 50 行 autovacuum_analyze_threshold = 50 autovacuum_analyze_scale_factor = 0.1
⚠️ 注意:对于高频更新的小表,scale_factor 可能导致清理延迟。建议对关键表单独设置更激进的策略:
alter table orders set (autovacuum_vacuum_scale_factor = 0.05);
在决定是否进行碎片整理前,需先评估当前系统的碎片状况。以下是几个实用的 sql 查询:
select
schemaname,
tablename,
pg_size_pretty(pg_table_size(schemaname || '.' || tablename)) as real_size,
pg_size_pretty(pg_total_relation_size(schemaname || '.' || tablename)) as total_size,
n_tup_ins - n_tup_del as net_rows,
n_dead_tup,
round(100.0 * n_dead_tup / greatest(n_live_tup + n_dead_tup, 1), 1) as dead_pct
from pg_stat_user_tables
where n_dead_tup > 1000
order by dead_pct desc;select
schemaname,
tablename,
indexname,
pg_size_pretty(pg_relation_size(indexrelid)) as index_size,
idx_tup_read,
idx_tup_fetch,
case
when idx_tup_read = 0 then 'never used'
when idx_tup_fetch = 0 then 'only for scans'
else 'active'
end as usage
from pg_stat_user_indexes
join pg_index on pg_stat_user_indexes.indexrelid = pg_index.indexrelid
where pg_relation_size(indexrelid) > 10 * 1024 * 1024 -- >10mb
order by pg_relation_size(indexrelid) desc;create extension if not exists pgstattuple;
select * from pgstattuple('orders');输出包含:
table_len:表总字节tuple_count:有效元组数dead_tuple_count:死亡元组数free_space:空闲空间📌 官方文档参考:postgresql statistics functions
✅ 推荐操作:vacuum(非 full)
理由:普通 vacuum 能快速回收空间供重用,且不影响业务。
✅ 推荐操作:vacuum full(在维护窗口执行)
或使用 pg_repack 工具(见下文)。
✅ 推荐操作:reindex index index_name;
注意:reindex 会锁表,建议在低峰期执行。
pg_repack 是一个第三方工具,可在不阻塞 dml 操作的情况下重建表和索引。
安装(以 ubuntu 为例):
sudo apt-get install postgresql-14-repack
使用:
pg_repack -d your_db -t orders
🔗 官网:https://reorg.github.io/pg_repack/
✅ 优势:零停机、支持并行、可中断恢复。
在 postgresql 中,表空间(tablespace)是用于定义数据库对象(表、索引等)物理存储位置的逻辑容器。默认情况下,所有对象都存储在 pg_default 表空间(位于 $pgdata/base)。
但通过自定义表空间,我们可以实现:
-- 假设 /ssd/data 是一个高速 ssd 挂载点
create tablespace fast_ssd location '/ssd/data';
-- 将表创建在指定表空间
create table hot_orders (
id serial primary key,
user_id int,
amount numeric
) tablespace fast_ssd;
-- 将现有表移动到新表空间
alter table cold_data set tablespace slow_hdd;select
spcname as tablespace_name,
pg_size_pretty(pg_tablespace_size(oid)) as size
from pg_tablespace;⚠️ 注意:表空间路径必须由 postgresql 用户(通常是
postgres)拥有写权限。
以下是一个基于 spring boot 的定时任务,自动将“冷”订单迁移到慢速表空间:
@component
public class tablespacemigrator {
@autowired
private jdbctemplate jdbctemplate;
// 每天凌晨 2 点执行
@scheduled(cron = "0 0 2 * * ?")
public void migratecoldorders() {
string sql = """
alter table orders
set tablespace slow_hdd
where created_at < now() - interval '90 days'
""";
// 注意:postgresql 不支持 where 子句的 alter table
// 实际应通过分区表或物化视图实现
// 正确做法:使用分区表(见下文)
}
}❗ 上述代码仅为示意。postgresql 的
alter table ... set tablespace不支持where条件。要实现按条件迁移,应使用分区表(partitioning)。
从 postgresql 10 开始,原生支持声明式分区(declarative partitioning)。通过分区,我们可以:
vacuum 或 reindex;-- 主表(分区父表)
create table orders (
id serial,
order_date date not null,
amount numeric
) partition by range (order_date);
-- 2023 年分区(放在 ssd)
create table orders_2023 partition of orders
for values from ('2023-01-01') to ('2024-01-01')
tablespace fast_ssd;
-- 2022 年分区(放在 hdd)
create table orders_2022 partition of orders
for values from ('2022-01-01') to ('2023-01-01')
tablespace slow_hdd;spring data jpa 或 mybatis 可透明操作分区表,无需特殊处理:
@entity
@table(name = "orders")
public class order {
@id
private long id;
private localdate orderdate;
private bigdecimal amount;
}
// repository
public interface orderrepository extends jparepository<order, long> {
// 自动路由到对应分区
list<order> findbyorderdatebetween(localdate start, localdate end);
}@scheduled(cron = "0 0 1 1 * ?") // 每月 1 日
public void createnextmonthpartition() {
localdate nextmonth = localdate.now().plusmonths(1);
string partitionname = "orders_" + nextmonth.getyear();
string tablespace = nextmonth.isafter(localdate.now().plusmonths(6)) ? "slow_hdd" : "fast_ssd";
string sql = string.format(
"create table if not exists %s partition of orders " +
"for values from ('%s-01-01') to ('%s-01-01') " +
"tablespace %s",
partitionname,
nextmonth.getyear(), nextmonth.plusyears(1).getyear(),
tablespace
);
jdbctemplate.execute(sql);
}✅ 优势:新数据自动进入高性能存储,旧数据静默迁移至低成本存储,碎片整理只需针对单个分区。
仅靠手动检查远远不够。建议在监控系统中集成以下指标:
| 指标 | 说明 | 告警阈值 |
|---|---|---|
n_dead_tup / (n_live_tup + n_dead_tup) | 死亡元组占比 | > 30% |
pg_table_size / estimated_row_count * avg_row_width | 表膨胀率 | > 2.0 |
autovacuum 运行频率 | 是否及时清理 | 延迟 > 1 小时 |
| 表空间使用率 | 磁盘空间预警 | > 85% |
通过 postgres_exporter 采集指标,配置 grafana 面板:
# prometheus.yml
scrape_configs:
- job_name: 'postgres'
static_configs:
- targets: ['localhost:9187']🔗 postgres_exporter 项目地址:https://github.com/prometheus-community/postgres_exporter(注:此处仅为说明用途,不提供 github 地址)
下面的流程图展示了从数据写入到碎片清理的完整过程:


该图清晰地说明了:及时的自动清理是防止碎片恶化的关键。
cluster orders using idx_orders_date;
vacuum full,会锁表;当查询条件包含分区键时,postgresql 会自动跳过无关分区:
explain analyze select * from orders where order_date between '2023-06-01' and '2023-06-30';
输出中应看到:
-> seq scan on orders_2023 (...)
而非扫描所有分区。
✅ 优化建议:确保查询条件使用分区键,否则无法裁剪。
避免逐条 update,改用批量:
@transactional
public void updateorderstatusbatch(list<long> ids, string status) {
string sql = "update orders set status = ? where id = any(?)";
jdbctemplate.update(sql, status, ids.toarray());
}对于“存在则更新,否则插入”场景,使用 on conflict:
string sql = """
insert into user_stats (user_id, login_count, last_login)
values (?, 1, now())
on conflict (user_id)
do update set
login_count = user_stats.login_count + 1,
last_login = now()
""";
jdbctemplate.update(sql, userid);通过 jdbc 获取统计信息:
public map<string, object> getvacuumstats(string tablename) {
string sql = """
select n_dead_tup, n_live_tup, last_autovacuum
from pg_stat_user_tables
where relname = ?
""";
return jdbctemplate.queryformap(sql, tablename);
}避免长事务阻塞 autovacuum:
// 默认 read committed 即可
@transactional(isolation = isolation.read_committed)
public void processorder(long orderid) {
// 业务逻辑
}⚠️ 长时间运行的
repeatable read或serializable事务会阻止 vacuum 清理旧版本。
在 aws rds、azure database for postgresql 等托管服务中,虽然无法直接创建表空间(因文件系统受限),但仍可通过以下方式优化:
freestoragespace 指标。pg_dump 按模式导出;🔗 aws rds for postgresql 最佳实践:https://aws.amazon.com/blogs/database/
某电商平台订单表(1 亿行)优化前后对比:
| 指标 | 优化前 | 优化后 |
|---|---|---|
| 表大小 | 120 gb | 45 gb |
| 全表扫描时间 | 85 秒 | 32 秒 |
| vacuum 频率 | 每 6 小时一次 | 每 30 分钟一次(自动) |
| 索引大小 | 30 gb | 12 gb |
| 磁盘 i/o 利用率 | 95% | 40% |
优化措施:
autovacuum_vacuum_scale_factor = 0.05;pg_repack 一次性清理历史碎片。reindex 或使用 pg_repack 同时处理。chown postgres:postgres /your/path。数据碎片和表空间管理不是“一次性任务”,而是持续的运维艺术。通过以下组合策略,你可以显著提升 postgresql 的性能与稳定性:
pg_repack、pgstattuple;记住:最好的优化,是让问题根本不发生。通过良好的表结构设计、合理的写入模式和前瞻性的存储规划,你的 postgresql 数据库将如瑞士手表般精准、高效、持久。
🌟 最后提醒:任何重大操作前,请务必在测试环境验证,并做好完整备份!
到此这篇关于postgresql 数据碎片整理与表空间优化的全过程的文章就介绍到这了,更多相关postgresql 数据库碎片整理内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
您想发表意见!!点此发布评论
版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。
发表评论