MySQL 5.7 原生不支持 BATCH_SIZE 自动分批,必须由应用层或存储过程显式控制事务边界;误用 LIMIT 而无确定性 WHERE 条件会导致重复、遗漏或全表锁;推荐基于主键区间(如 id BETWEEN)分片,并配合适当索引与 ROW 格式 binlog。

为什么不能直接用 BATCH_SIZE 参数拆分大事务
MySQL 5.7 原生不支持类似 PostgreSQL 的 FOR UPDATE SKIP LOCKED 或自动分批提交机制,也没有内置的 BATCH_SIZE 事务拆分参数。你看到的很多“分批更新”脚本,本质是靠应用层或存储过程手动控制事务边界——不是 MySQL 自动做的,而是你得显式 COMMIT 和重开事务。
常见错误是误以为加个 LIMIT 就能安全分批,比如写 UPDATE ... LIMIT 1000 却没加 WHERE 条件过滤范围,结果每次执行都作用于最新匹配的 1000 行(可能重复或遗漏),且锁住全表或大量索引页。
用 WHERE id BETWEEN ? AND ? 实现可重复、低锁表的分片
这是最稳定、最容易压测和中断续跑的方式。核心是依赖主键(或唯一递增索引)做闭区间切分,避免 OFFSET 跳跃和 ORDER BY 排序开销。
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
- 先查出主键范围:
SELECT MIN(id), MAX(id) FROM t_user; - 按步长(如 5000)生成区间:
[1, 5000], [5001, 10000], ... - 每批单独事务执行:
BEGIN; UPDATE t_user SET status = 2 WHERE id BETWEEN 1 AND 5000 AND status = 1; COMMIT;
- 务必在
status = 1上建联合索引:INDEX idx_status_id (status, id),否则BETWEEN会触发全表扫描
用 SELECT ... FOR UPDATE + 游标模拟微事务(仅限存储过程)
适合无法修改应用逻辑、只能在数据库内完成的场景。但要注意:MySQL 5.7 的游标不可滚动,且 FOR UPDATE 在事务中持续持有锁,必须严格控制单次 fetch 数量。
- 声明游标时必须用
READ ONLY+FOR UPDATE显式锁定,例如:DECLARE cur CURSOR FOR SELECT id FROM t_order WHERE state = 'pending' ORDER BY id LIMIT 100 FOR UPDATE;
- 每次
FETCH后立即处理并COMMIT,不能等游标遍历完再提交——否则锁住所有 fetched 行,退化为大事务 - 不推荐在高并发 OLTP 表上使用;更适合后台批处理且表有明确低频访问窗口
容易被忽略的三个硬约束
拆事务不是加个循环就完事,这三个点卡住会导致数据不一致或性能雪崩:
-
innodb_lock_wait_timeout默认 50 秒,如果单批执行超时,整个事务回滚,但上层可能没捕获异常,造成“看似成功实则漏更” - binlog 格式必须为
ROW,否则基于语句的复制(STATEMENT)在分批时可能因非确定性函数(如NOW()、RAND())导致从库数据偏差 - 如果表有外键或触发器,每次
COMMIT都会触发完整校验/执行——批量关掉触发器(SET @disable_triggers = 1不生效!必须用DROP TRIGGER临时移除)或预估触发开销










