真正有效的批量更新核心是让数据库一次性干完该干的活:必须确保WHERE条件走索引,用EXPLAIN确认type非ALL;按主键分批(如id > ? ORDER BY id LIMIT 1000–5000),每批COMMIT并休眠;优先用CASE WHEN或临时表JOIN替代单条循环,避免全表扫描与锁堆积。
单条 UPDATE 循环百万次,不是慢,是自毁式写法。真正有效的批量更新,核心就一条:让数据库一次性干完该干的活,而不是反复叫它起床干活。
没索引的 UPDATE 本质就是全表扫描+全表加锁,再怎么分批也救不回来。先跑 EXPLAIN FORMAT=TREE(MySQL 8.0+)或 EXPLAIN ANALYZE(PostgreSQL),确认 type 不是 ALL。
WHERE status = ? AND updated_at > ?,索引应为 (status, updated_at)
WHERE DATE(created_at) = '2024-01-01' 这种写法——函数导致索引失效;改用 created_at >= '2024-01-01' AND created_at
status)单独建索引意义不大,必须搭配时间、ID等高区分度字段用 LIMIT offset, size 分页?越往后越慢,且容易漏数据。正确做法是靠索引字段“游标式”推进,每次只查下一批起点。
WHERE id > 100000 ORDER BY id LIMIT 5000,记录本次最大 id 作为下次起点COMMIT,释放行锁和 undo log,避免事务堆积SLEEP(0.1) 缓冲 IO 压力,尤其在主从架构下防复制延迟突增你要更新 1 万行不同 ID 的不同值?别写 1 万个 UPDATE,也别拼超长 SQL。两种更稳的合并方式:
CASE WHEN 单语句:适用于离散 ID 更新,SQL 长度可控时最轻量UPDATE users SET score = CASE id WHEN 1 THEN 95 WHEN 2 THEN 87 ELSE score END WHERE id IN (1,2);
JOIN:适合来源复杂(比如来自 CSV、API 或另一张表)CREATE TEMPORARY TABLE tmp_updates (id INT PRIMARY KEY, new_score INT);UPDATE users u JOIN tmp_updates t ON u.id = t.id SET u.score = t.new_score;
临时调参能提速,但多数人调错地方。以下三个是真正影响批量 UPDATE 的关键点:
innodb_flush_log_at_trx_commit = 2:仅限非金融类业务,降低 redo log 刷盘频率,写入速度可提升 3–5 倍SET FOREIGN_KEY_CHECKS = 0 和 SET UNIQUE_CHECKS = 0:关掉约束校验,更新完立刻恢复,注意外键级联行为会失效innodb_log_file_size:如果频繁报 “log is full”,说明这个值太小,但调整需重启,属于运维动作,不属于 SQL 优化范畴最容易被跳过的一步:更新前先 SELECT COUNT(*) 统计影响行数。没这一步,你根本不知道这批要跑多久、占多少 undo log、会不会触发锁升级——所有优化都成了盲打。