MySQL按月分区不强制用RANGE COLUMNS,但强烈建议:它支持DATE/DATETIME列原生比较,避免函数分区导致的全表扫描;分区键须为主键/唯一键组成部分,created_at不可为NULL,边界值语义需全局统一。

MySQL按月分区必须用 RANGE COLUMNS?
不是必须,但强烈建议。MySQL 5.7+ 中,RANGE COLUMNS 支持直接对 DATETIME 或 DATE 列分区,避免函数表达式(如 TO_DAYS())带来的隐式类型转换和索引失效问题。若用 RANGE (TO_DAYS(created_at)),MySQL 无法在分区裁剪时准确识别范围,查询可能扫全表;而 RANGE COLUMNS(created_at) 可原生比较日期字面量,执行计划更可靠。
常见错误现象:EXPLAIN PARTITIONS 显示所有分区都被访问,即使 WHERE created_at BETWEEN '2024-06-01' AND '2024-06-30' —— 很可能就是用了函数分区。
- 分区键必须是主键/唯一键的组成部分(否则建表报错
ERROR 1503) -
created_at列不能为NULL(RANGE COLUMNS不支持 NULL 分区边界) - 分区名需符合标识符规则,建议用
p202406格式,便于脚本解析
自动创建下月分区的存储过程怎么写?
核心逻辑是:计算下个月第一天(DATE_ADD(LAST_DAY(NOW()), INTERVAL 1 DAY)),生成对应分区定义,并用 ALTER TABLE ... ADD PARTITION 动态执行。不能用变量直接拼进 ADD PARTITION 语句,必须用 PREPARE/EXECUTE。
DELIMITER $$
CREATE PROCEDURE add_monthly_partition(IN tbl_name VARCHAR(64), IN db_name VARCHAR(64))
BEGIN
DECLARE next_month DATE;
DECLARE part_name VARCHAR(16);
DECLARE part_value DATE;
DECLARE stmt TEXT;
SET next_month = DATE_ADD(LAST_DAY(NOW()), INTERVAL 1 DAY);
SET part_name = CONCAT('p', DATE_FORMAT(next_month, '%Y%m'));
SET part_value = LAST_DAY(next_month);
SET stmt = CONCAT(
'ALTER TABLE `', db_name, '`.`', tbl_name, '` ',
'ADD PARTITION (PARTITION `', part_name, '` VALUES LESS THAN (\'', part_value, '\'))'
);
SET @sql = stmt;
PREPARE s FROM @sql;
EXECUTE s;
DEALLOCATE PREPARE s;
END$$
DELIMITER ;
注意:LAST_DAY(next_month) 是该月最后一天(如 2024-07-31),作为 LESS THAN 边界,确保 2024-08-01 起的数据落入下一分区。别误用 next_month 本身作边界——会导致数据错位。
如何避免重复添加分区或漏加?
存储过程不能盲目执行,得先查 INFORMATION_SCHEMA.PARTITIONS 确认目标分区名是否已存在。否则每天定时任务跑一次,第二天就会报错 ERROR 1517: Duplicate partition name。
- 在调用
add_monthly_partition前,先查SELECT PARTITION_NAME FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_SCHEMA = 'your_db' AND TABLE_NAME = 'your_table' AND PARTITION_NAME = 'p202407' - 生产环境建议把分区检查 + 添加封装成一个带判断的存储过程,而不是靠外部调度器“试错”
- 首次建表时,必须手动创建至少两个分区(含当前月及下月),否则自动脚本第一次运行会因“无现有分区”而失败(
ADD PARTITION要求表已有分区)
定时任务用 EVENT 还是外部 Cron?
用 MySQL EVENT 最简,但隐患大:EVENT 默认被禁用(event_scheduler=OFF),且一旦 mysqld 重启未自动启用,分区就停更;另外 EVENT 执行失败不告警,容易静默遗漏。更稳妥的是外部 cron 调用 mysql -e "CALL add_monthly_partition('t_log', 'prod')",配合日志和失败重试。
若坚持用 EVENT,务必确认:
-
SET GLOBAL event_scheduler = ON;并写入my.cnf的[mysqld]段落 - EVENT 定义里加上
ON COMPLETION PRESERVE,防止执行完被自动删掉 - 用
SHOW EVENTS定期核对Last_executed时间是否滞后
真正麻烦的不是写脚本,而是分区边界值的语义一致性——比如你用 LESS THAN ('2024-07-01') 还是 LESS THAN ('2024-07-31'),决定了 7 月 1 日零点的数据去哪,这个逻辑一旦定下,所有上下游脚本、归档策略、BI 查询都得对齐,改起来牵一发而动全身。











