MySQL 8.0+ RANGE分区必须基于纯DATE/DATETIME列且不可用函数包装,正确做法是使用TO_DAYS(dt)对STORED虚拟列或原生日期列分区,以支持高效归档。

MySQL 8.0+ 的 RANGE 分区必须用 DATE/DATETIME 列且不能有函数包装
直接对 DATE(created_at) 或 YEAR(created_at) 做 RANGE 分区会报错 ERROR 1486 (HY000): Constant, random or timezone-dependent expressions in (sub)partitioning function are not allowed。MySQL 要求分区表达式必须是确定性列引用,不能是函数调用。
正确做法是:确保表里有一列是纯 DATE 或 DATETIME 类型(比如 dt),且建表时直接用它分区:
CREATE TABLE orders (
id BIGINT,
order_no VARCHAR(32),
dt DATE NOT NULL
) PARTITION BY RANGE (TO_DAYS(dt)) (
PARTITION p202301 VALUES LESS THAN (TO_DAYS('2023-02-01')),
PARTITION p202302 VALUES LESS THAN (TO_DAYS('2023-03-01')),
PARTITION p202303 VALUES LESS THAN (TO_DAYS('2023-04-01')),
PARTITION p_future VALUES LESS THAN MAXVALUE
);
-
TO_DAYS()是安全的,因为输入是常量日期字面量,不是列函数 - 不要用
YEAR(dt)—— 它无法区分跨年但同月的数据,且 MySQL 8.0 对 YEAR 分区支持弱、易出边界问题 - 如果原表只有
created_at DATETIME,建议新增dt AS (DATE(created_at)) STORED虚拟列并基于它分区(需 MySQL 5.7+)
自动归档老数据:用 DROP PARTITION + REORGANIZE 替代 DELETE
RANGE 分区的价值不在查询加速,而在低成本归档。删掉一年前数据,用 DROP PARTITION 比 DELETE FROM ... WHERE dt 快几个数量级,且不锁全表、不产生大事务日志。
- 先确认要删的分区名:
SELECT PARTITION_NAME FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME = 'orders' AND TABLE_SCHEMA = 'your_db'; - 执行归档:
ALTER TABLE orders DROP PARTITION p202201, p202202; - 删完后,记得用
REORGANIZE PARTITION p_future INTO (...)补上新分区,否则后续插入会失败 - 注意:
DROP PARTITION是 DDL,不可回滚;生产环境务必先在从库验证分区结构和数据分布
INSERT 时写入错误分区?检查严格模式和时区设置
常见现象:插入 dt = '2023-01-31' 却进了 p202302 分区,或报错 ERROR 1526 (HY000): Table has no partition for value X。
- 根本原因是
TO_DAYS()计算依赖 session 时区。若应用连接用了SET time_zone = '+08:00',但 MySQL server 默认是 UTC,'2023-01-31'在 UTC 下可能是'2023-01-30 16:00:00',导致TO_DAYS()结果偏小 - 统一方案:所有连接强制设为 UTC:
SET time_zone = '+00:00';,并在应用层传入 ISO 格式日期(如'2023-01-31') - 检查是否开启严格模式:
SELECT @@sql_mode;—— 若含STRICT_TRANS_TABLES,则非法日期(如'2023-02-30')会直接报错,避免静默写入错误分区
PostgreSQL 不支持原生 RANGE 分区表归档?用 PARTITION BY RANGE + ATTACH/DETACH
PostgreSQL 10+ 的声明式分区确实支持 PARTITION BY RANGE (dt),但它没有 DROP PARTITION 的快捷归档能力,必须手动 DETACH PARTITION 后再 DROP TABLE。
- 建分区表示例:
CREATE TABLE logs (id SERIAL, dt DATE) PARTITION BY RANGE (dt); - 创建子分区:
CREATE TABLE logs_202301 PARTITION OF logs FOR VALUES FROM ('2023-01-01') TO ('2023-02-01'); - 归档时:
ALTER TABLE logs DETACH PARTITION logs_202201;→ 再DROP TABLE logs_202201; - 关键点:DETACH 是元数据操作,极快;但必须确保该分区无活跃事务、无外键引用,否则会卡住
真正容易被忽略的是时间字段精度——MySQL 的 TO_DAYS() 只认日期,而 PostgreSQL 的 RANGE 支持 TIMESTAMP,但若业务按天归档,却用 TIMESTAMP 分区,会导致同一日期的数据散落在多个分区,归档时得 detach 多个分区,反而增加运维负担。










