最稳妥的批量更新方式是INSERT ON DUPLICATE KEY UPDATE,前提是表有主键或唯一索引;否则优先选UPDATE JOIN配合临时表,因其避免逐条UPDATE的高网络开销、频繁日志写入与行锁竞争,实测1000条数据耗时从800ms降至45ms。

MySQL批量操作中部分失败,不是“要不要继续”的问题,而是“怎么让失败不拖垮整体”的问题。核心在于:默认事务是原子性的,一条错,全盘回滚;必须主动打破这种默认行为,用语法或配置绕过它。
INSERT IGNORE 能跳过冲突但不报错
当批量插入遇到主键或唯一索引重复时,INSERT IGNORE 会把冲突行当作警告(warning)处理,其余行照常插入。它不抛异常,也不中断执行。
- 适用场景:数据允许丢弃重复项,比如日志补录、缓存同步、去重导入
- 注意点:
INSERT IGNORE也会忽略其他错误,比如字段类型不匹配、NOT NULL 字段为 NULL —— 这些本该报错的也被静默吞掉,容易掩盖真实问题 - 示例:
INSERT IGNORE INTO users (id, name) VALUES (1,'a'), (1,'b'), (2,'c');→ 只插入 id=1 和 id=2 的记录,第二条 id=1 被跳过
ON DUPLICATE KEY UPDATE 实现“有则更新、无则插入”
这是最可控的方案,尤其适合需要合并数据的场景。它只对触发主键/唯一索引冲突的行执行 UPDATE,其余正常 INSERT。
- 关键限制:UPDATE 子句里不能引用原表别名,要用
VALUES(column)获取当前 INSERT 行的值 - 性能影响:每行都要先做一次唯一键查找,再决定是 insert 还是 update,比纯 insert 略慢,但远好于事务回滚重试
- 示例:
INSERT INTO users (id, name, score) VALUES (1,'alice',90), (2,'bob',85) ON DUPLICATE KEY UPDATE name=VALUES(name), score=score+VALUES(score);
MyBatis 批量更新需显式开启 multi-query 支持
MyBatis 默认把 生成的多条 SQL 当作单条语句发给 MySQL,而 MySQL 默认禁止一条请求里含多个语句(分号分隔)。不加配置就会报错 ERROR 1064 (42000) 或直接被 JDBC 拒绝。
- 必须在 JDBC URL 中加上:
allowMultiQueries=true,例如:jdbc:mysql://localhost:3306/db?allowMultiQueries=true&useSSL=false - MyBatis XML 中的
separator=";"才能生效,否则生成的 SQL 会缺少分号或格式错乱 - 注意:开启后需防范 SQL 注入,确保所有参数都走预编译(
#{}),不用${}
大批次操作要防锁死和超时
哪怕语法正确,一次性插/更新几万行仍可能触发锁等待超时或 Lock wait timeout exceeded。这不是失败逻辑问题,而是资源调度问题。
- 推荐分批:每批 500~2000 行,用循环提交,每次
COMMIT后释放锁 - 避免全表扫描式更新:用主键范围(
WHERE id BETWEEN ? AND ?)或带LIMIT的循环,而不是WHERE status=0这种无索引条件 - 检查并调大两个参数:
max_allowed_packet(防止 packet too large)、wait_timeout(防止空闲连接被断)
真正容易被忽略的,是 INSERT IGNORE 对非唯一冲突错误的静默吞没,以及 MyBatis 开启 allowMultiQueries=true 后未校验 SQL 注入风险。这两处不写日志、不抛异常,出问题时最难定位。











