必须用PREPARE+EXECUTE动态执行:先CONCAT拼接含反引号的INSERT语句,字段值通过USING传参,确保子表结构一致,并DEALLOCATE释放预处理语句。

存储过程里怎么拼出正确的 INSERT 语句并执行
MySQL 存储过程中不能直接用变量当表名写 INSERT INTO @table_name,必须走 PREPARE + EXECUTE 这条路径。否则会报错 ERROR 1064 或静默失败。
- 先用
CONCAT()拼完整 SQL 字符串,表名建议加反引号,比如CONCAT('INSERT INTO `user_', LPAD(NEW.user_id % 16, 2, '0'), '` VALUES (...)') - 字段值不能直接写
NEW.xxx,得用USING传参,否则变量作用域失效;例如EXECUTE stmt USING NEW.user_id, NEW.name, NEW.created_at - 所有子表字段顺序、类型、是否允许 NULL 必须和源结构完全一致,否则
USING会错位或报类型不匹配 - 别漏掉
DEALLOCATE PREPARE stmt,否则连接内存可能缓慢增长(尤其高并发场景)
分表逻辑放在哪里更可靠:触发器 vs 存储过程
触发器看似“自动”,但实际限制极多:不能改 NEW、不能动态表名、不能跨库、无法捕获建表失败。而存储过程由应用显式调用,可控性高得多。
- 触发器里写
SET @sql = CONCAT(...); PREPARE ... EXECUTE是合法的,但一旦目标表不存在,就会直接报错Table 'db.user_09' doesn't exist,且无法在触发器内建表 - 存储过程可以先查
information_schema.TABLES确认表存在,不存在就CREATE TABLE再插入,形成闭环 - 应用层调用存储过程时,可统一处理错误(如建表失败、插入冲突),而触发器失败会导致整个事务回滚,排查链路更长
- ORM(如 MyBatis)通常不支持触发器路由,但能安全调用带参数的存储过程
日期类分表如何避免跨天/时区问题
按天或按月分表时,NOW() 返回的是当前会话时区时间,若服务器和业务时区不一致,可能导致同一条记录被写入错误日期表。
- 统一用
UTC_DATE()或CONVERT_TZ(NOW(), '+08:00', '+00:00')转为 UTC 后再格式化,例如DATE_FORMAT(UTC_DATE(), '%Y%m%d') - 表名中避免用横线(
-),优先选下划线或纯数字,如log_20260702,防止反引号包裹出错或关键字冲突 - 不要用
SHOW TABLES LIKE @table_name判断表是否存在——MySQL 不支持变量用于LIKE右侧,必须查information_schema.TABLES - 高并发写入场景下,“查表→建表→插入”有竞态风险,建议加
GET_LOCK('create_table_lock', 0)控制临界区,或依赖上层定时任务预创建
为什么分表后 UNION ALL 查询变慢
MySQL 5.7 及更早版本对跨分表查询优化能力弱,UNION ALL 不会下推 WHERE 条件到各子表,容易全表扫描。
- 所有分表必须严格保持索引一致,尤其是分片键(如
user_id)和时间字段(如created_at)上的联合索引 - 避免在
UNION ALL外层套复杂子查询,尽量让每个子查询自带有效过滤条件 - MySQL 8.0+ 支持
INFORMATION_SCHEMA动态生成查询语句,可封装成视图或辅助存储过程,但要注意权限和元数据缓存延迟 - 真正需要跨表聚合时,考虑提前物化汇总表,而非每次实时
UNION ALL
AUTO_INCREMENT 起始值错开和 information_schema 表存在性校验这两步,线上故障往往就卡在这儿。

















