MySQL 8.0 和 PostgreSQL 15 做订单表,5000万行到底要不要分区?我踩过的坑

🔑 关键词:MySQL 8.0,PostgreSQL 15,分区表,订单表,数据库选型

📖 摘要:一篇不太正经的数据库实战复盘:5000万行订单表在 MySQL 8.0.32 和 PostgreSQL 15.4 上单表、分区表的查询/插入/归档对比,以及我为什么最后把分区表当归档工具而不是查询加速器。

先说结论,省得你看到一半骂我:订单表 3000 万行以内,别碰分区表。 你遇到的慢查询,90% 不是表太大,是索引不对、状态字段乱用、查询不带时间范围。 我 2023 年 11 月接了个杭州 SaaS 团队的活,订单表 6800 万行,MySQL 5.7 单表。 他们听人说分区能提速,连夜升到 MySQL 8.0.32,按 created_at 月分区,64 个分区。 结果插入 P95 从 1.8ms 涨到 3.4ms,导出报表还因为跨分区扫描更慢了。

图片

MySQL 8.0 分区表的坑,不在性能在约束

MySQL 8.0.32 的 InnoDB 分区有个硬规矩:分区键必须包含在每一个唯一索引里。 订单表原来主键是 id,唯一索引是 order_no。你想按 created_at 分区? 那主键得改成 (id, created_at),唯一索引得改成 (order_no, created_at)。 听起来只是改 SQL,实际 ORM 里一堆 insert 不带 created_at 或者用数据库默认值,直接报错。 我们当时加了一个 uk_order_no_created (order_no, created_at),应用层还得补时间,后来还是出了重复支付——因为 order_no 唯一性只在同一分区内生效。 测试环境数据:5000 万行,表 18GB,索引 12GB,8C32G 机器,innodb_buffer_pool_size=20G,ESSD PL1 100GB。 单表走 idx_user_status_time (user_id, status, created_at) 查最近 20 单,P95 12ms。 64 个分区后 P95 9ms,但插入 P95 3.4ms,归档 DELETE 换成 DROP PARTITION 倒是从 47 分钟降到 0.8 秒。

图片

PostgreSQL 15 分区灵活,但规划器和 autovacuum 会教你做人

后来他们想换 PostgreSQL 15.4,觉得声明式分区没 MySQL 那么多破事。 确实,PG 15 可以按 created_at 范围分区,主键不一定非要带分区键,唯一索引也能单独建。 但别高兴太早:32 个分区,每个分区一套索引,查询 WHERE user_id=? AND status=? ORDER BY created_at DESC LIMIT 20,如果没带 created_at 范围,规划器要扫 32 个分区的索引,Planning Time 从 0.8ms 涨到 6.5ms。 PG 15.4 参数 shared_buffers=8GB, work_mem=64MB, random_page_cost=1.1, effective_cache_size=24GB。 单表 5000 万行 P95 15ms,32 分区 P95 11ms,但 autovacuum 默认配置下,P99 能飙到 120ms。 表 22GB,索引 15GB,膨胀比 MySQL 明显。

图片

我现在的做法:分区表只用来归档,查询靠单表+索引+冷热分离

如果重新来,我会按这个顺序做:1)先开慢查询日志,阈值 200ms,跑一周,把 top 20 SQL 拉出来;2)订单表建 (user_id, status, created_at) 或者 (user_id, created_at),status 区分度低就别放前面;3)超过 3000 万行,用事件调度每天凌晨把 6 个月前已完结订单搬到 order_history,原表只留热数据;4)真要分区,按 created_at 月分区,查询必须带时间范围,否则分区裁剪失效;5)归档目标用 ClickHouse 或者 TiDB,别在 MySQL/PG 里硬扛分析查询。 MySQL 侧用 pt-archiver,每次 1000 行,sleep 0.1 秒;PG 侧用 pg_partman + DETACH PARTITION。 监控看三个数:InnoDB Buffer Pool Hit Rate 低于 99.5%、PG blks_hit 低于 99%、autovacuum 延迟超过 60 秒,就该动手了。

图片

一个可能不讨喜的观点

分区表不是查询加速器,它是数据生命周期管理工具。 很多人把“表大”和“查询慢”画等号,其实 5000 万行订单表在 8C32G 上只要索引对,P95 10ms 出头很正常。 真正贵的是删除历史数据、备份恢复、DDL 变更。 MySQL 8.0 分区适合快速 DROP 归档,PostgreSQL 15 分区适合灵活管理,但两者都会带来规划开销、索引膨胀和应用改造。 如果你们的订单查询永远不带 created_at,只按 user_id 和 status 查,那分区裁剪根本用不上,分区就是给运维看的,不是给业务提速的。 先量清楚慢在哪,再决定换库还是换索引。

图片

🏷️ 标签: