高效备份不能靠存储过程独立完成,需手动设计调度、分批、跨库及错误恢复;瓶颈在于事务大小、索引缺失、冷备库连接与状态追溯。

不能靠存储过程自己完成“高效备份”——它只是逻辑容器,调度、分批、跨库、错误恢复都得手动设计。 真正的效率瓶颈不在SQL写法,而在事务大小、索引缺失、冷备库连接方式和失败后状态是否可追溯。
为什么直接 INSERT INTO ... SELECT 会卡死或锁表
大表一次性搬移,MySQL会把整个 SELECT 结果集缓存在内存或临时表里,再一次性 INSERT。如果源表没索引、WHERE条件扫全表,或者目标库网络延迟高,就会触发 max_allowed_packet 溢出或 innodb_lock_wait_timeout 超时。
- 必须确保
WHERE字段(如create_time)上有有效索引,否则LIMIT也无效——MySQL仍要扫描全表找前N行 - 跨库
INSERT INTO cold_db.t_archive SELECT * FROM prod_db.t实际走的是单线程复制路径,无法并行,且冷备库若慢于源库,会拖慢主库事务提交 - 别用
SELECT * INTO(SQL Server语法),MySQL不支持;也别在存储过程中拼接CREATE TABLE ... AS SELECT后直接删原表——结构差异(如默认值、字符集)会导致数据截断或隐式转换
分批搬移必须用 REPEAT + ROW_COUNT() 控制循环
用 COUNT(*) 预估总行数再循环是典型误区:MVCC快照下结果不准,且多一次全表扫描。正确做法是依赖每次 INSERT 和 DELETE 后的 ROW_COUNT() 判断是否还有数据可处理。
- 每次只搬
LIMIT 1000行,插入后立刻检查:GET DIAGNOSTICS @inserted = ROW_COUNT;,若为 0 直接LEAVE - 删除必须用完全相同的
WHERE条件 +LIMIT 1000,否则可能漏删或多删;删完再查ROW_COUNT()确认是否清空本轮 - 循环体外加
START TRANSACTION,但不要把整个循环包在一个事务里——长事务会持锁、占 undo log,建议每批独立事务
跨库归档必须显式处理连接与权限
MySQL 存储过程默认只能访问当前数据库,INSERT INTO cold_db.t 要求执行账号对 cold_db 有 INSERT 权限,且两个库字符集、时区、TIMESTAMP 行为必须一致,否则字段值会被静默修正或报错。
- 冷备库表结构必须用
CREATE TABLE t_archive LIKE prod_db.t创建,再手工校验:SHOW CREATE TABLE对比默认值、约束、COLLATE - 避免用
SELECT * FROM prod_db.t插入,显式列出字段名,防止源表加字段导致目标表列数不匹配 - 如果冷备库在另一台机器上,纯 SQL 无法直连——此时存储过程失效,必须改用
pt-archiver或外部脚本中转
存储过程里必须带错误捕获和行数校验
MySQL 默认出错不停止、不回滚,@@ERROR 只反映上一条语句,多语句下完全不可靠。没有错误处理的归档过程,失败后状态残缺,根本不知道哪一批卡住了。
- 开头必须声明:
DECLARE EXIT HANDLER FOR SQLEXCEPTION,内部做ROLLBACK并记录日志表 - 每次
INSERT后立刻GET DIAGNOSTICS @n = ROW_COUNT;,若@n = 0就跳出循环,避免空跑消耗资源 - 插入前后分别查
SELECT COUNT(*) FROM prod_db.t WHERE ...和SELECT COUNT(*) FROM cold_db.t_archive WHERE ...做最终一致性校验(仅限小批量验证)
最易被忽略的一点:归档不是“搬完就完”,而是“搬得稳、删得准、查得到”。冷备库表名带日期后缀(如 t_archive_202608)比共用一张表更安全,但必须在存储过程中动态生成并检查是否存在,否则重复执行会报 Table exists 错误——这个判断逻辑,90% 的存储过程都漏了。











