应避免在存储过程中用 LIMIT i,1 分页取数,因其在并发或数据变动时会导致跳行或死循环;正确做法是用带 ORDER BY 的游标配合 FETCH、分批 COMMIT(如每1000–5000行)、临时表替代子查询,并确保 WHERE 条件命中联合索引。

存储过程里别用 LIMIT i,1 做分页取数
这是最常踩的坑:用 LIMIT i,1 在循环中逐条取 busi_id,会导致跳过数据甚至死循环。MySQL 的 LIMIT offset,row_count 在并发写入或非唯一排序下不保证稳定性,尤其当源表有删改时,i 递增后可能查不到新行或重复取同一行。
正确做法是用游标(CURSOR)配合 FETCH,确保每条记录只处理一次:
- 声明游标时带明确
ORDER BY(如ORDER BY busi_id),避免无序扫描 - 用
DECLARE CONTINUE HANDLER FOR NOT FOUND捕获游标结束,而非靠count硬算上限 - 每次
FETCH后立即处理并更新,不依赖中间变量缓存
批量提交必须控制事务大小,不能等最后才 COMMIT
把几万条更新塞进一个事务里,会撑爆 innodb_log_file_size、锁住整张表、拖慢 binlog 写入,还可能触发超时中断。
实操建议按行数分批,而不是按时间或随机切片:
- 每 1000–5000 行执行一次
COMMIT(视单行数据大小和服务器配置调整) - 在循环内用
MOD(i, 1000) = 0判断是否提交,但注意i要从 1 开始计数,否则第 0 次就提交空事务 - 开头加
SET autocommit = 0,结尾显式COMMIT,避免隐式提交干扰批次边界
UPDATE 语句本身要避免子查询嵌套
像 UPDATE A SET col = (SELECT x FROM B WHERE ...) 这种写法,在存储过程中每执行一次都会触发一次子查询,N 行就是 N 次全表/索引扫描,性能雪崩。
替代方案是提前把关联数据载入临时表,再用 JOIN 更新:
- 在存储过程开头建
CREATE TEMPORARY TABLE temp_join (...) - 用单条
INSERT INTO temp_join SELECT ... FROM B WHERE ...一次性加载所需字段 - 主
UPDATE改成UPDATE A JOIN temp_join ON A.id = temp_join.a_id SET A.col = temp_join.val - 临时表自动随会话销毁,无需手动
DROP
真正影响速度的往往不是存储过程,而是 WHERE 条件有没有走索引
即使你把逻辑全写进存储过程,如果 WHERE pid = ? AND status = ? 这类条件没命中索引,照样全表扫描。而存储过程会让这种问题更隐蔽——你以为是“逻辑慢”,其实是“扫描慢”。
检查方法很简单:
- 在存储过程外单独跑一遍核心
SELECT语句,加EXPLAIN - 确认
type是ref或range,不是ALL - 复合条件优先建联合索引,顺序按
WHERE中等值匹配列在前、范围列在后(如(pid, status, busi_id))
没有索引支撑的存储过程,只是把慢操作包得更严实而已。


















