不能。MySQL 8.0+ 中 VIRTUAL 虚拟列不可用作 RANGE 分区键,必须使用 STORED 列(如 YEAR(created_at) STORED),且分区表达式需确定性、无外部依赖,否则报错 ERROR 1564。
MySQL 8.0+ 虚拟列能直接用作 RANGE 分区键吗?
不能。即使定义为 virtual,只要底层表达式不可持久化(比如含 now()、uuid()、子查询或用户变量),mysql 就会拒绝创建分区表,并报错:error 1564 (hy000): this partition function is not allowed。
关键限制在于:MySQL 要求分区表达式必须是「确定性 + 无外部依赖」的标量值,且虚拟列本身必须是 STORED 类型才能参与分区——但注意,STORED 列不是“虚拟列”,它占物理存储,只是由表达式自动生成。
-
VIRTUAL列不落地,无法被分区函数读取;必须改用STORED - 表达式里禁用:
NOW()、CURRENT_DATE、RAND()、USER()、任何函数返回非确定性结果 - 支持的典型表达式:
YEAR(created_at)、TO_DAYS(date_col)、LEFT(code, 2)、id DIV 1000
如何正确声明可分区的表达式列(以日期范围为例)
目标是按年分区,但原始字段是 DATETIME;不能直接对 DATETIME 做 RANGE COLUMNS(只支持单列且类型需匹配),所以得提取年份作为整数列。
必须分两步:先加 STORED 列,再用它分区。不能一步到位用虚拟列表达式写在 PARTITION BY RANGE 里。
ALTER TABLE orders ADD COLUMN year_part INT AS (YEAR(created_at)) STORED;
然后重建表启用分区:
ALTER TABLE orders
PARTITION BY RANGE (year_part) (
PARTITION p2022 VALUES LESS THAN (2023),
PARTITION p2023 VALUES LESS THAN (2024),
PARTITION p2024 VALUES LESS THAN (2025),
PARTITION p_future VALUES LESS THAN MAXVALUE
);
-
AS (YEAR(created_at)) STORED是必须的,VIRTUAL会失败 - 分区键必须是列名(如
year_part),不能是表达式(如YEAR(created_at)) - 如果原表数据量大,
ALTER TABLE ... PARTITION BY会锁表并重写全表,生产环境务必评估停机窗口
为什么不用 RANGE COLUMNS 直接分区 DATE 字段?
可以,但有隐含代价:RANGE COLUMNS (date_col) 要求每个 VALUES LESS THAN 的边界必须是完整 DATE 值(如 '2023-01-01'),没法按“年”抽象,导致分区语句冗长、维护困难。
更严重的是:如果业务查的是 WHERE created_at >= '2023-06-01',优化器可能无法精准裁剪到单个分区,尤其当 date_col 上没索引或统计信息不准时。
- 用整数年份分区(如
year_part)+ 普通 B-tree 索引,查询WHERE year_part = 2023能 100% 落到对应分区 -
RANGE COLUMNS (date_col)对WHERE date_col BETWEEN '2023-06-01' AND '2023-08-31'可能跨多个分区,扫描更多数据 - 分区裁剪是否生效,可通过
EXPLAIN PARTITIONS验证,别只看EXPLAIN
常见翻车点:ALTER TABLE 添加 STORED 列后分区不生效
加完列、执行 PARTITION BY 后发现查询仍走全表扫描,或者 SHOW CREATE TABLE 显示没分区结构——大概率是 MySQL 版本或存储引擎不支持。
- 必须是 MySQL 5.7.6+ 且使用
InnoDB;MyISAM不支持分区 - MySQL 5.7 默认关闭
innodb_file_per_table时,某些旧版本会静默忽略分区定义(检查SHOW VARIABLES LIKE 'innodb_file_per_table') - 执行
ALTER TABLE ... PARTITION BY若中途失败,表可能处于不一致状态:有新列但无分区,需手动回滚或重建 - 分区键列(如
year_part)必须允许为NULL,否则插入NULL值会报错;建议在表达式中加默认兜底,例如COALESCE(YEAR(created_at), 1970)
最麻烦的其实是数据迁移成本:一旦表已上线且数据量超千万,加 STORED 列 + 重分区 = 长时间锁表。这时候不如考虑应用层分表,或者用时间范围做归档表切换。










