数据量一大分区表就是数据库的保命技巧之一。我在 CentOS 7 服务器上管理 PostgreSQL 已经好几年了今天要讲的 pg_partman就是把我从“每天手动建新分区、手动删老分区”里彻底解放出来的那个扩展。简单说pg_partman 是一款 PostgreSQL 分区自动管理扩展你只需要告诉它父表是谁、分区键是什么、分区间隔多大、保留周期多久剩下的自动创建新分区、清理过期分区、维护索引和约束它全部帮你干完。这篇文章我就把 CentOS 7 上从零安装配置 pg_partman 的完整过程记录下来涵盖环境准备、编译安装、建表托管、自动化调度和生产避坑适合正在折腾分区表维护的 DBA 和运维同学。1. 分区维护痛点pg_partman 到底帮你解决了什么先说一个很多 PostgreSQL DBA 都遇到过的经典场景。某张业务表持续写入每天几百万行三个月后表体积已经几百 GB查询慢、索引膨胀、DELETE 历史数据要把主库 VACUUM 搞得半死。这时候绝大多数人会想到分区表按天或者按月拆成若干个子表数据只往当天或当月的子表写旧数据直接 DROP 掉对应的子表干净利落。PostgreSQL 从 10 开始引入了声明式分区Declarative Partitioning语法比老版本基于继承INHERITS的方式简洁很多写起来就像这样CREATE TABLE measurement ( id bigserial NOT NULL, logdate date NOT NULL, data text ) PARTITION BY RANGE (logdate);但声明式分区只帮你解决了“怎么分”的问题没有解决“谁来建子表、谁来删子表”的问题。你要么自己写脚本定时 CREATE TABLE要么每次都手动敲 SQL稍微一忙就把某天的分区忘了建结果当天插入数据直接报错“no partition of relation found for row”。这种错误在凌晨跑批、半夜值班的时候出现是真的会让人头皮发麻。pg_partman 解决的就是这个痛点。它是 PostgreSQL 社区非常成熟的分区管理扩展核心能力在于自动创建子分区支持 range范围、list列表、hash哈希三种分区方式支持按时间date、timestamp、按自增 ID、按整数列作为分区控制字段自动清理超过保留周期的老分区可以彻底 DROP也可以只解绑保留为普通表提供 run_maintenance() 函数可以被 cron、pg_cron 甚至自己的后台进程周期性调用对于老式基于继承的分区也能做数据迁移和约束管理。所以我的整体判断是如果你的数据模型是“滚动时间窗口”类型的流水数据比如行为日志、订单流水、监控指标、消息记录并且保留周期比较固定比如保留 30 天、90 天、6 个月那么 pg_partman 几乎就是这个场景的标准答案。不需要自己造轮子写一堆 shell 脚本去拼接建表 SQL 了。2. 基础环境准备YUM 源、时间同步和 PostgreSQL 安装2.1 先确认机器基础信息别一上来就装包在 CentOS 7 上装任何数据库组件我习惯先花两分钟确认三件事系统版本、架构、磁盘空间。cat /etc/redhat-release getconf LONG_BIT df -h不同机器返回的内容会不一样但重点是确认你是 x86_64 架构、CentOS 7.x、以及 /var 或者数据目录所在分区有足够空间。PostgreSQL 的数据目录默认在 /var/lib/pgsql如果这个目录所在分区只剩几个 GB后面初始化数据、灌测试数据都会很难受建议提前把数据目录规划到一个独立磁盘或独立分区。这一步能避免后面装到一半发现磁盘满了的尴尬。另外如果你的 CentOS 7 是刚装好的最小化系统先顺手把基础工具装上后面编译 pg_partman 要用yum install -y epel-release yum install -y gcc make readline-devel zlib-devel git net-tools这里多说一句很多人在最小化 CentOS 上执行 ifconfig 会提示 command not found那是因为默认没有安装 net-tools执行上面的安装命令后就有了。装完可以先ifconfig看一眼网卡 IP确认网络通不通。如果 ping 外网不通多半是网关或者 DNS 配置问题这会影响后面下载 yum 包得先处理掉。2.2 国内服务器建议换 YUM 源下载速度快一个量级CentOS 7 官方的 mirror 在国内访问速度经常非常感人几十兆的包能下半天。我一般直接把基础源换成国内镜像源操作很简单一条命令搞定curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo替换之后执行yum clean all yum makecache等你看到显示生成缓存的数量和耗时就能明显感觉到速度差异。这一步虽然不是 pg_partman 安装的必要步骤但强烈建议做尤其是后续要装 PostgreSQL 服务端和 devel 包有几个包的体积并不小。2.3 时间同步分区自动管理依赖精准时钟pg_partman 按时间分区时判断“该建哪个新分区”“哪个分区过期了”都依赖系统时间。如果服务器时间漂移可能把分区提前建了或者漏建而且保留策略也会受影响。所以安装 pg_partman 之前我先确认 chrony 时间同步是正常的yum install -y chrony systemctl enable chronyd --now chronyc sources -v看到^*开头的输出说明已经同步上时间源了。这个步骤很不起眼但千万别跳过我在生产环境见过因为服务器日期慢了几天导致 pg_partman 一直不创建第二个分区排查半天才发现是时钟问题。2.4 防火墙和 SELinux测试环境从简生产环境按规范实验环境里为了少折腾我通常直接关掉 firewalldsystemctl stop firewalld systemctl disable firewalld但生产环境不建议这么干正确做法是只放行 PostgreSQL 的 5432 端口firewall-cmd --permanent --add-port5432/tcp firewall-cmd --reloadSELinux 如果遇到奇怪的文件权限问题可以先查/var/log/audit/audit.log确认是不是 SELinux 拦截再决定要不要临时放行不建议一上来就永久 disable。2.5 用 PGDG 仓库安装 PostgreSQL 14CentOS 7 自带的 PostgreSQL 是 9.2实在太老pg_partman 新版早就要求 PG 11 以上所以直接上 PostgreSQL 官方的 PGDG 源。yum install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm然后安装 PostgreSQL 14 以及后续编译需要的开发包yum install -y postgresql14-server postgresql14-devel初始化数据库并启动/usr/pgsql-14/bin/postgresql-14-setup initdb systemctl enable --now postgresql-14服务起来后切换到 postgres 用户设置一个自己的密码su - postgres psql -c ALTER USER postgres PASSWORD 你的密码;这一步需要自己在终端输入密码。注意 PGDG 的 PostgreSQL 14 可执行文件在/usr/pgsql-14/bin后面编译 pg_partman 时这个路径很关键因为系统默认的pg_config可能指向旧版本 9.2如果不做处理编译时容易找错头文件。3. 编译安装 pg_partman 扩展依赖、源码和常见报错3.1 拉取源码前先确认 PG 版本配套pg_partman 不同版本对 PostgreSQL 的版本要求不一样我这次以 5.x/6.x 版本为例它们要求 PG 11 及以上PG 14 上完全没问题。源码直接去 GitHub 拉cd /usr/local/src git clone https://github.com/pgpartman/pg_partman.git cd pg_partman git tag建议 checkout 一个稳定版本不要直接拉最新 mastergit checkout v6.0.0这里的git tag是列出所有版本标签git checkout v6.0.0是切换到指定版本实际使用时你可以选择当时的最新稳定版本。PGDG 仓库会持续更新版本号以你实际看到的为准。3.2 make install 前记得指定 PG_CONFIG 环境变量在有多套 PostgreSQL 版本或者系统里存在旧版 pg_config 的情况下直接执行 make 很可能会用错编译器参数。我习惯显式指定一下PG_CONFIG/usr/pgsql-14/bin/pg_config make PG_CONFIG/usr/pgsql-14/bin/pg_config make install执行完后如果没报错可以验证一下扩展文件是否已经复制到正确目录ls /usr/pgsql-14/share/extension/ | grep partman正常情况下会看到pg_partman.control、pg_partman--*.sql之类的文件。这个验证步骤很重要因为我遇到过一次 make install 因为权限问题静默失败扩展文件根本没复制到对应目录后面执行 CREATE EXTENSION 才发现找不到文件。3.3 在数据库里创建 partman schema 和扩展pg_partman 会创建一批管理表和函数我不喜欢把它放进 public schema所以先建一个独立 schemaCREATE SCHEMA partman; CREATE EXTENSION pg_partman WITH SCHEMA partman;这里要注意创建扩展需要超级用户权限。如果当前用户不是超级用户会报permission denied to create extension那就先用 postgres 用户创建后面再把对应权限授权给业务账号。创建成功后检查一下版本SELECT extversion FROM pg_extension WHERE extname pg_partman;再确认管理表已经建好\d partman.part_config如果能看到一张名为 part_config 的表说明扩展安装成功。这张表是 pg_partman 的核心配置文件后面所有分区策略都会记录在这里。4. 创建分区父表并把维护工作交给 pg_partman4.1 先建父表用声明式分区语法pg_partman 可以配合 PostgreSQL 10 的声明式分区使用。我先建一张用于演示的父表CREATE TABLE public.measurement ( id bigserial NOT NULL, logdate date NOT NULL, data text ) PARTITION BY RANGE (logdate);这张表本身不存储任何真实业务数据它只是“分区模板”或者叫“路由母体”。按照分区键 logdate 写入时PostgreSQL 会自动把行路由到匹配的子分区。如果找不到匹配子分区就会报错。这也是为什么要用 pg_partman 提前把子分区建好。4.2 create_parent 函数一行 SQL 完成分区托管接下来调用 pg_partman 的入口函数 create_parent把这张父表托管给它SELECT partman.create_parent( p_parent_table public.measurement, p_control logdate, p_interval 1 day, p_type range, p_premake 14, p_start current_date );参数含义我逐个说清楚p_parent_table父表名必须带 schemap_control分区控制字段这里是 logdate类型需要是 date 或者 timestampp_interval分区间隔1 day 表示一天一个分区也可以写成 1 week、1 month、1 yearp_type分区类型range 对应范围分区p_premake提前创建多少个分区这里 14 表示把未来 14 天的分区都建好p_start起始分区日期用 current_date 表示从今天开始建。执行完成后可以用下面这条 SQL 查看生成的子分区SELECT child_table, partition_tablename FROM partman.show_partition_info(public.measurement);或者直接去查 pg_class 里的列表。你会看到类似 measurement_p2025_01_18、measurement_p2025_01_19 这样的子表出现。子表命名规则是“父表名_p日期”具体格式受 pg_partman 版本影响但都带日期标识肉眼就能看懂属于哪一天。4.3 配置保留周期让老分区自动消失建完分区后核心配置集中在 partman.part_config 表里。我先看看生成的默认配置SELECT parent_table, control, partition_interval, premake, retention FROM partman.part_config;默认情况下 retention 是空值表示不清理老分区。我需要把它改成保留 30 天UPDATE partman.part_config SET retention 30 days, retention_keep_table false, retention_keep_index true WHERE parent_table public.measurement;这里三个字段的含义很重要retention保留周期30 days 表示 30 天之前的分区会被处理retention_keep_tablefalse 表示直接 DROP 掉过期分区true 表示把过期分区先从父表上解绑DETACH再保留成独立普通表retention_keep_index在保留为独立表的情况下是否保留该表上的索引。如果你的业务需要审计留档但又不想数据堆在分区父表里影响查询可以把 retention_keep_table 设为 true。否则就设 false让它干脆利落地 drop空间立刻释放。生产环境第一次设置时建议先设为 true 跑一个周期看看效果确认没问题再考虑是否改为 false。配置保存后手动触发一次维护试试SELECT partman.run_maintenance(public.measurement);这条 SQL 会立即创建未来 premake 个分区同时清理超过 retention 的旧分区。5. 自动化调度三种方式让维护任务自己跑起来5.1 方案一Linux crontab 调度最简单也最可控run_maintenance 本质上就是个普通 SQL 函数最朴素的调度方式就是使用 crontab。我一般会让维护任务每天凌晨执行一次30 1 * * * psql -U postgres -d yourdb -c SELECT partman.run_maintenance(public.measurement); /var/log/pg_partman.log 21这里有几个细节需要处理psql 的路径如果没加到 PATH 里建议写绝对路径比如/usr/pgsql-14/bin/psql认证问题可以用 ~/.pgpass 文件避免每次输入密码文件里写入host:port:db:user:password格式的凭据并把权限设为 600日志建议单独落文件内容里最好带上时间戳排查问题时非常有用。如果你想更通用一点让所有已托管的父表都跑一次维护可以直接执行SELECT partman.run_maintenance();不带表名参数时pg_partman 会遍历 part_config 里所有父表统一维护。对于表数量多的场景我更喜欢这种写法。5.2 方案二pg_cron 数据库内调度适合不想碰操作系统的环境有些团队不希望数据库服务器开通外部 cron 权限那就可以考虑在 PostgreSQL 里装 pg_cron 扩展。PGDG 源里直接提供yum install -y postgresql14-cron然后在 postgresql 主配置里启用shared_preload_libraries pg_cron cron.database_name yourdb重启 PostgreSQL 后在数据库里启用扩展并创建定时任务CREATE EXTENSION pg_cron; SELECT cron.schedule( maintain-partman, 30 1 * * *, SELECT partman.run_maintenance() );pg_cron 的表单调度方式很直观而且任务状态全部存在数据库里查询和修改都方便。缺点是它需要额外安装扩展、改配置并重启数据库不是所有环境都能接受重启窗口。5.3 方案三pg_partman 自带的 background worker最省心但配置细节多pg_partman 从 5.0 开始后台工作的加载模块名改成了 pg_partman早期版本叫 pg_partman_bgw。这个方案零外部依赖完全由 PostgreSQL 进程自己定时跑维护需要修改 postgresql.confshared_preload_libraries pg_partman pg_partman_bgw.interval 3600 pg_partman_bgw.role postgres pg_partman_bgw.dbname yourdb配置完成后重启 PostgreSQL然后查看数据库日志应该能看到类似 “pg_partman background worker started” 的记录。这里我必须提醒一个坑如果你在同一个库里还加载了别的扩展比如 pg_cronshared_preload_libraries 要用逗号分隔写全不能只写其中一个。另外不同版本的 pg_partman 对 bgw 的配置名有差异使用前建议先查一下当前版本对应的官方文档不要照抄别人的老配置。三种方案选哪个我的经验是初期或者测试环境用 crontab 就好直观、可控、不依赖数据库配置如果服务器管理规范不允许用户自己加 crontab就上 pg_cron如果追求最小外部依赖只想让 PostgreSQL 自己搞定就研究 bgw。没有绝对的优劣稳定压倒一切。6. 从 0 到 1 的完整配置清单与避坑心得6.1 最小化配置清单直接抄作业把上面所有关键步骤整理成一张速查表方便你按顺序操作步骤操作内容核心命令/代码1换国内 YUM 源curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo2安装基础工具yum install -y epel-release gcc make git postgresql14-server postgresql14-devel3初始化并启动 PG/usr/pgsql-14/bin/postgresql-14-setup initdb systemctl enable --now postgresql-144编译安装 pg_partmangit clone https://github.com/pgpartman/pg_partman.git cd pg_partman PG_CONFIG/usr/pgsql-14/bin/pg_config make install5创建扩展CREATE SCHEMA partman; CREATE EXTENSION pg_partman WITH SCHEMA partman;6建父表CREATE TABLE ... PARTITION BY RANGE (logdate);7托管给 pg_partmanSELECT partman.create_parent(...);8配置保留周期UPDATE partman.part_config SET retention 30 days;9配置定时任务在 crontab / pg_cron / bgw 三选一这张表可以作为你的部署文档底稿实际使用的时候把数据库名、表名、分区间隔替换成自己的业务值就行。6.2 常见问题与排查方法我把实际操作中遇到频率最高的问题整理一下按症状和解决思路列出问题现象可能原因解决思路CREATE EXTENSION 报找不到文件pg_partman 未安装成功或安装到错误版本的扩展目录检查 ls /usr/pgsql-14/share/extension/ 是否有 partman 文件重新 make installmake 时提示找不到 pg_config缺 postgresql14-devel 或 PATH 不对安装 devel 包并显式指定 PG_CONFIG 环境变量执行 build 时报“权限不足”/usr/pgsql-14 目录宿主权限限制用 root 用户执行 make install或者利用 sudo建了父表但子分区一直不增加premake 设置太小或者当前日期超出 p_start 范围调大 premake确认 p_start 早于当前日期手动执行 run_maintenance 后再次查看插入数据报 no partition of relation found某一天的分区还没被创建检查日志和 part_config确认定时维护任务已生效并检查系统时钟是否准确老分区没有按预期删除retention 为空或值不对或者定时任务没跑核对 part_config 的 retention 字段手动执行 run_maintenance 验证升级 pg_partman 之后 bgw 没起来新版本模块名改变旧配置失效查官方文档确认新模块名修改 shared_preload_libraries 后重启这里要特别强调如果发现某些天分区数量对不上第一件事不是重跑建表脚本而是先看系统时间和 part_config。我曾经排查过一个“每天少建一个分区”的问题最后发现是 crontab 里的 psql 命令执行时连错了数据库修改了 DBNAME 后一切正常。定时任务里的数据库名、schema 名一定要写准确这是最容易被忽略的坑。6.3 已有历史大表如何迁移到 pg_partman 管理如果你不是从零开始建表而是已经有一张几千万行的大表想切到 pg_partman 管理建议不要直接把父表改成分区表那一步在 PostgreSQL 里代价太大。实际可行的路径是新建一张分区父表使用目标表名比如 new_measurement在旧表上把数据按时间分批导出/插入到新父表利用 pg_partman 的 partition_data_id、partition_data_time 等函数对旧表进行分批次迁移切换业务连接或重命名表。其中第 3 步是 pg_partman 提供的迁移利器比如按月分区迁移一批SELECT partman.partition_data_time(public.measurement_old, p_batch_count : 10000, p_interval : 1 day);它会自动识别旧表数据把落在某个分区的行移动到对应子表并且是分批执行的避免一次性锁表时间过长。注意执行这类迁移要看清楚函数签名不同版本参数略有差别务必先在测试环境验证。6.4 生产环境的几条实操心得我管理的系统里有一张核心流水表靠 pg_partman 按天分区保留 90 天已经稳定跑了一年多。分享几个在实操中积累的经验第一分区间隔不要照搬别人的参数。写入量日均低于 10 万行的表按月分区就够了日均百万级以上的必须按天甚至按小时分区。分区过多也会带来管理开销比如子表元数据膨胀、SQL 解析时需要评估的分区数量变多。我一般是先估算单分区数据量控制在 200 万到 2000 万行的区间。第二对更新频繁的分区表可以考虑在子表模板上设置 fillfactor。pg_partman 支持通过模板表来配置新建子表的参数比如把 fillfactor 设为 90让每个数据页留一点空间给后续更新使用减少页分裂。用 create_parent 创建时可以先用一个 p_template_table 指定模板表后面新子表都会继承模板表的存储参数。第三生产环境切换 retention_keep_table 前必须做一次演练。这个参数一旦设错比如本来想保留归档表结果配成 false过期分区会被直接 DROP数据找不回来。我习惯先用生产数据的副本库做一次完整周期测试确认 DROP 的边界日期符合预期再改动正式库。第四日志监控很重要。pg_partman 本身不会像应用那样打印大量的运行日志但它维护动作是否成功可以通过查询 part_config 里的 last_run 字段来确认SELECT parent_table, last_run, last_run_epoch FROM partman.part_config;如果 last_run 很久没更新说明维护任务断了要赶紧排查。我自己做数据库运维时一直有个习惯任何自动化工具第一次使用时都要先手动跑一遍观察它对系统造成的锁和 IO 影响。pg_partman 也是这样先手动创建分区、手动清理一次心里有底后再交给定时任务。这套从 CentOS 7 基础环境准备到 pg_partman 编译安装、再到分区托管和自动调度的流程我基本固化成了自己的部署清单每次新环境都照这个顺序走一遍。希望这篇记录能帮你少踩几个坑。