订单表按时间戳分区后JOIN性能变差,根本原因是JOIN条件未显式包含分区键导致分区裁剪失效;PostgreSQL插入报错因未手动创建对应子分区;MySQL ORDER BY LIMIT慢因跨分区无法下推;函数操作分区键会使分区和索引同时失效。

订单表按时间戳分区后,JOIN 性能反而变差?
根本原因不是分区没用,而是 JOIN 时没走分区裁剪——MySQL/PostgreSQL 的分区表在 JOIN 条件中若没显式带上分区键(比如 created_at),优化器大概率放弃分区扫描,退化成全分区遍历。
- 常见错误现象:
EXPLAIN显示type=ALL或partitions=NULL,哪怕WHERE里写了时间范围,JOIN后就失效 - 必须确保 JOIN 条件中至少一侧的分区键参与等值或范围匹配,例如:
o.created_at = p.created_at或o.created_at >= '2024-01-01' - 如果关联的是非时间维度表(如用户表),且无法补时间字段,宁可不用分区,改用普通 B-tree 索引 + 覆盖索引优化
- MySQL 8.0+ 支持
INFORMATION_SCHEMA.PARTITIONS查看实际扫描了哪些分区,别只信EXPLAIN的估算
PostgreSQL 中用 CREATE TABLE ... PARTITION BY RANGE (created_at) 后,为什么 INSERT 报错?
PostgreSQL 分区表不自动创建子分区,必须手动 CREATE TABLE ... PARTITION OF,否则插入时间超出所有已有子分区范围就会报 no partition of relation "orders" found for row。
- 典型场景:按月分区,但只建了 2023 年的分区,2024 年 1 月数据一进来就失败
- 解决方法不是建一堆静态分区,而是配合触发器或应用层路由,或用
pg_partman自动管理;手动建建议至少预留未来 3 个月 - 注意时区:
created_at是TIMESTAMP WITHOUT TIME ZONE还是WITH TIME ZONE?分区边界计算依赖实际存储值,混用会导致边界错位 - 子分区上必须单独建索引,父表上的索引不继承,漏建会直接让
JOIN变慢
MySQL 5.7/8.0 的 RANGE COLUMNS(created_at) 分区,为什么 ORDER BY created_at LIMIT 10 很慢?
分区本身不改变排序逻辑,MySQL 仍需合并各分区结果再排序,除非能确定只查单一分区,否则 LIMIT 无法下推,性能和未分区表无异。
- 容易踩的坑:以为“有分区就有局部有序”,其实每个分区内部有序,但跨分区无序
- 真正提速的做法是把时间范围条件写死,例如:
WHERE created_at BETWEEN '2024-03-01' AND '2024-03-31' ORDER BY created_at LIMIT 10,这样才可能只扫一个分区 - 如果业务常查“最新 N 条”,不如在主键或
created_at上建普通联合索引:INDEX (created_at, order_id),比分区更轻量、更可靠 - MySQL 8.0 支持降序索引,
INDEX (created_at DESC)对倒序分页更友好,别依赖分区“自然倒序”
JOIN 时用 DATE(created_at) 或 YEAR(created_at) 做条件,分区失效
对分区键做函数操作,等于主动屏蔽索引和分区裁剪,MySQL 和 PostgreSQL 都无法识别原始列值范围,只能全扫。
- 错误写法:
WHERE DATE(o.created_at) = '2024-03-15'→ 分区失效,索引失效 - 正确写法:
WHERE o.created_at >= '2024-03-15' AND o.created_at ,既走分区又走索引 - 如果业务代码已大量使用
DATE(),别硬改 SQL,可在应用层提前算好范围,或加生成列 + 索引(MySQL 5.7+ 支持STORED列) - PostgreSQL 中
created_at::date同样失效,但可用表达式索引:CREATE INDEX ON orders ((created_at::date)),不过这和分区键重复,通常没必要
分区不是银弹,它只在“查询能精准命中少数几个分区”时才有意义。电商订单最常查的是“最近 7 天”“某用户+某时段”,这类场景优先保证 WHERE 条件干净、分区键裸露、索引覆盖充分——比堆砌分区重要得多。










