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 快几个数量级,且不锁全表、不产生大事务日志。