MySQL存储过程无法直接用变量作表名,必须通过CONCAT拼接SQL字符串并用PREPARE/EXECUTE执行;需用反引号包裹标识符、检查分区是否存在、确保分区列在主键中且类型匹配。

ALTER TABLE 不支持直接传入变量作为表名,这是 MySQL 存储过程中最常踩的坑。想用存储过程批量给多个表加分区,必须靠字符串拼接 + PREPARE/EXECUTE 绕过语法限制。
为什么不能直接用变量写表名
MySQL 解析器在编译阶段就要求 ALTER TABLE 后面跟的是字面量标识符(如 `orders`),不接受用户变量(如 @table_name)或存储过程参数(如 IN in_table_name VARCHAR(64))。一旦写成 ALTER TABLE in_table_name ...,会报错:ERROR 1146 (42S02): Table 'database.in_table_name' doesn't exist —— 它真把你当表名了。
必须用 PREPARE + EXECUTE 拼接 SQL
核心思路是:把整个 DDL 语句构造成字符串,再用预处理语句执行。注意三点:
- 表名、库名、分区名都得用反引号包裹,防止关键字冲突(比如表名是
order) - 日期值要用
QUOTE()或单引号手动包裹,避免注入或格式错误 -
@sql_stmt是会话级用户变量,必须在PREPARE前赋值,且不能跨连接复用
示例(为指定表添加按日分区):
DELIMITER $$
CREATE PROCEDURE add_daily_partition(
IN in_db_name VARCHAR(64),
IN in_table_name VARCHAR(64),
IN in_date DATE
)
BEGIN
DECLARE part_name VARCHAR(16);
DECLARE end_date DATE;
SET part_name = CONCAT('p', DATE_FORMAT(in_date, '%Y%m%d'));
SET end_date = in_date + INTERVAL 1 DAY;
SET @sql_stmt = CONCAT(
'ALTER TABLE `', in_db_name, '`.`', in_table_name, '` ',
'ADD PARTITION (PARTITION `', part_name, '` ',
'VALUES LESS THAN (TO_DAYS(\'', end_date, '\')))'
);
PREPARE stmt FROM @sql_stmt;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END$$
DELIMITER ;
批量处理多张表时要检查分区是否存在
重复执行 ADD PARTITION 会报错:ERROR 1517 (HY000): Duplicate partition name。不能靠捕获异常硬扛,应在执行前查 information_schema.PARTITIONS:
- 查
PARTITION_NAME是否已存在,用SELECT COUNT(*) INTO exists_flag ... - 只对不存在的分区才拼接并执行
ALTER TABLE - 注意
information_schema.PARTITIONS中的TABLE_SCHEMA和TABLE_NAME区分大小写(取决于系统变量lower_case_table_names)
漏掉这步,事件调度器定时跑就会某天突然失败停摆。
分区字段类型和主键约束容易被忽略
动态加分区不是“只要语句能跑通就行”。两个硬性前提必须在建表时就满足,否则后续所有 ADD PARTITION 都会失败:
- 分区列(如
order_date)必须包含在主键或唯一索引中 —— 否则报ERROR 1503 (HY000): A PRIMARY KEY must include all columns in the table's partitioning function -
TO_DAYS()只接受DATE或DATETIME;若字段是VARCHAR存日期,先STR_TO_DATE()转换,否则报ERROR 1659 (HY000): Failed to read auto-increment value from storage engine(实际是类型不匹配的伪装错误)
这些约束不会在存储过程里提示你,而是在 EXECUTE 那一刻炸开。建表脚本和分区逻辑必须同步验证。


















