PostgreSQL 分区表的原理、实践与 MySQL 对比

PostgreSQL 分区表的原理、实践与 MySQL 对比

Elvis Lv2

一、背景

需要对AI调用进行记录,并对失败过高的情况进行告警。但是目前每天的AI调用量在百万级,对应的数据量也在百万级,单表存储显然是不可靠的。单表过大带来的问题包括:

  • 查询、插入、更新性能下降;
  • 索引膨胀,维护困难;
  • 清理历史数据成本高;
  • 统计与聚合操作耗时。
    在这种情况下,表分区(Partitioning) 是一种非常有效的优化手段。

二、什么是分区表

分区表(Partitioned Table)是一个 逻辑表,数据按某种规则(范围、列表、哈希)分布在多个物理子表中(称为分区 / Partition)。
应用程序仍然对“主表”执行查询或写入,数据库内核自动将操作路由到对应的分区。
🔹 举个例子
假设我们有一个 AI 模型调用日志表 ai_call_record,每天产生数百万条记录。
我们可以根据时间字段 started_at 按天进行分区:

1
2
3
4
5
6
7
8
9
CREATE TABLE ai_call_record (
id BIGSERIAL,
call_id UUID NOT NULL,
channel TEXT NOT NULL,
model_name TEXT NOT NULL,
status TEXT NOT NULL,
started_at TIMESTAMPTZ NOT NULL,
PRIMARY KEY (id, started_at)
) PARTITION BY RANGE (started_at);

PostgreSQL 会根据 started_at 的值自动把数据路由到对应的子表:

1
2
3
CREATE TABLE ai_call_record_20251113
PARTITION OF ai_call_record
VALUES FROM ('2025-11-13') TO ('2025-11-14');

当插入 2025-11-13 10:00:00 的数据时,会自动进入该分区表。


三、PostgreSQL 分区类型

PostgreSQL 从 v10 开始原生支持表分区(之前依靠继承 + 触发器实现)。
常见的分区类型如下:

类型 划分方式 适用场景 示例
范围分区(RANGE) 按连续区间划分 日志、订单等时间序列数据 按天或按月分区
列表分区(LIST) 按离散值划分 地区、租户、业务类型 按国家或渠道分区
哈希分区(HASH) 按哈希余数均匀划分 没有自然范围、需要分散写入的数据 按用户 ID 分为 8 个分区

范围分区最适合本文的调用日志场景,因为查询和清理通常都以时间为边界。列表分区要求提前明确值集合;哈希分区能让数据分布更均匀,但不适合按时间快速删除历史数据。


四、PostgreSQL 分区表的优势

✅ 1. 自动路由与查询优化
当你执行:
SELECT * FROM ai_call_record WHERE started_at >= now() - interval '1 day';
PostgreSQL 的 分区裁剪(Partition Pruning) 能自动只扫描符合条件的分区,大幅减少 I/O。


✅ 2. 高效的归档与清理
删除历史数据非常简单:
DROP TABLE ai_call_record_20251001;
比传统 DELETE 快几个数量级。


✅ 3. 独立索引与并行查询
每个分区都有自己的索引,查询时只访问相关索引;
分区之间还能并行扫描,提高吞吐。


✅ 4. 与原表操作几乎一致
INSERT/UPDATE/SELECT/DELETE 都不需要改应用逻辑,完全透明。
只是 DDL 管理上稍微复杂(需要自动建分区任务,如 pg_cron)。


五、MySQL 的分区表机制

MySQL 也支持分区(从 5.1 起),主要由 InnoDB 实现。
两者在设计思想相似,但实现方式有明显差异:

场景 PostgreSQL 分区表 MySQL 分区表
单分区写入 快,近似普通表 类似
分区裁剪查询 精准裁剪,性能稳定 条件匹配不严格会全表扫描
删除历史分区 DROP TABLE 秒级完成 ALTER TABLE DROP PARTITION 也快,但锁表风险高
索引管理 独立索引,灵活 全局索引支持有限
自动化管理 可借助 pg_cron 或 pg_partman 无官方自动分区管理工具
并行查询 ✅ 支持多分区并行 ❌ 主要单线程

六、实战案例:AI 调用日志按天分区

1️⃣ 分区表定义

1
2
3
4
5
6
7
8
9
10
11
CREATE TABLE ai_call_record (
id BIGSERIAL,
call_id UUID NOT NULL,
channel TEXT NOT NULL,
model_name TEXT NOT NULL,
status TEXT NOT NULL,
duration_ms BIGINT,
started_at TIMESTAMPTZ NOT NULL,
finished_at TIMESTAMPTZ,
PRIMARY KEY (id, started_at)
) PARTITION BY RANGE (started_at);

2️⃣ 自动分区创建函数(每日)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
CREATE OR REPLACE FUNCTION create_ai_call_partitions()
RETURNS void LANGUAGE plpgsql AS $$
DECLARE
base_date date := (now() AT TIME ZONE 'UTC')::date;
i int;
BEGIN
FOR i IN 0..1 LOOP
EXECUTE format(
'CREATE TABLE IF NOT EXISTS ai_call_record_%s
PARTITION OF ai_call_record
FOR VALUES FROM (%L) TO (%L);',
to_char(base_date + i, 'YYYYMMDD'),
to_char(base_date + i, 'YYYY-MM-DD 00:00:00+00'),
to_char(base_date + i + 1, 'YYYY-MM-DD 00:00:00+00')
);
END LOOP;
END;
$$;

3️⃣ 删除过期分区
DROP TABLE ai_call_record_20251001;

🚀 清理历史数据只需秒级,性能极高。


七、性能对比:PostgreSQL vs MySQL

场景 PostgreSQL 分区表 MySQL 分区表
单分区写入 快,近似普通表 类似
分区裁剪查询 精准裁剪,性能稳定 条件匹配不严格会全表扫描
删除历史分区 DROP TABLE 秒级完成 ALTER TABLE DROP PARTITION 也快,但锁表风险高
索引管理 独立索引,灵活 全局索引支持有限
自动化管理 可借助 pg_cron 或 pg_partman 无官方自动分区管理工具
并行查询 ✅ 支持多分区并行 ❌ 主要单线程

结论:

  • PostgreSQL 的分区表更接近“多表聚合”的设计,灵活、易扩展。
  • MySQL 的分区是“逻辑分片”,不如 PostgreSQL 强大,但轻量。
  • 如果你有时间序列、日志型、归档型数据,PostgreSQL 分区是更优方案。

八、最佳实践建议

  1. 选择合适的分区键
    常见为时间戳字段(如 created_at、started_at)。
  2. 提前创建分区
    使用 pg_cronpg_partman 定期自动创建未来分区。
  3. 清理过期分区
    定期删除旧分区,避免表膨胀。
  4. 索引策略
    仅对查询必要的字段建索引,避免每个分区维护大量冗余索引。
  5. 监控与维护
    使用 pg_stat_user_tablespg_inherits 监控分区数量和大小。

九、总结

对比项 PostgreSQL MySQL
分区灵活性 ✅ 高 ⚠️ 一般
自动化支持 ✅ pg_cron / pg_partman ❌ 需手工
大数据量表现 ✅ 稳定 ⚠️ 易退化
清理历史数据 ✅ 秒级 DROP ✅ 快,但锁表
查询优化 ✅ 分区裁剪智能 ⚠️ 条件匹配要求高

✅ 一句话总结:
PostgreSQL 的分区表是生产级别的“时间序列 + 大数据归档”解决方案,
而 MySQL 的分区功能更像是一种轻量级的逻辑分割。


📘 推荐扩展阅读

  • 标题: PostgreSQL 分区表的原理、实践与 MySQL 对比
  • 作者: Elvis
  • 创建于 : 2025-11-14 10:00:00
  • 更新于 : 2026-07-25 09:07:17
  • 链接: https://qianwj.github.io/2025/11/14/postgres_partition/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。