update必须带where,否则全表覆盖不可逆;where字段须有索引以防全表扫描和锁表;set不能直接跨列赋值;大表更新需分批并避开高峰。

UPDATE 语句必须带 WHERE,否则会全表覆盖
mysql 的 UPDATE 默认不加防护机制,漏写 WHERE 条件会导致整张表所有行被更新,且不可逆。这不是警告,是实际发生过的高频事故。
- 执行前务必确认
WHERE子句能精准定位目标行,建议先用select验证:SELECT id, name FROM users WHERE status = 'inactive'; - 开发环境可临时开启安全模式:
SET SQL_SAFE_UPDATES = 1;,此时没有WHERE或没用到索引的WHERE会被拒绝 - 生产环境严禁直接执行无条件
UPDATE;如真需全表更新(例如统一设置默认值),应显式加上WHERE 1并走发布流程审批
WHERE 条件中使用非索引字段会导致全表扫描和锁表风险
当 WHERE 匹配字段未建索引时,MySQL 必须逐行读取判断,不仅慢,还会对所有扫描过的行加 next-key lock(在可重复读隔离级别下),极大增加死锁和阻塞概率。
- 检查执行计划:
EXPLAIN UPDATE users SET status = 'archived' WHERE email LIKE '%@example.com';若
type是ALL,说明没走索引 -
LIKE开头带通配符(如'%abc')无法使用 B-tree 索引;改用覆盖索引或全文索引(FULLTEXT)替代 - 时间范围更新慎用
datetime字段直接比较,确保该列有索引,且避免函数包裹:WHERE DATE(create_time) = '2024-01-01'会让索引失效
UPDATE 中的 SET 子句不能引用正在被更新的列做计算(除非用子查询绕过)
MySQL 不允许在同一个 UPDATE 语句中,把某列既当源又当目标来计算,比如 SET score = score + 10 是合法的,但 SET a = b, b = a 这类交换操作会出错或行为异常。
- 列值自增/自减(如
score = score + 5)是支持的,MySQL 内部会先读旧值再计算 - 跨列依赖更新(如交换两列)必须用子查询或临时变量:
UPDATE t SET a = (SELECT b FROM t AS tmp WHERE tmp.id = t.id), b = (SELECT a FROM t AS tmp WHERE tmp.id = t.id); - 避免在
SET中调用不确定函数(如NOW()、RAND())影响多行结果一致性;若需每行不同值,确认业务是否真需要
大表 UPDATE 容易触发锁等待、超时或主从延迟
更新百万级以上数据时,单条 UPDATE 语句可能持有行锁/间隙锁数秒甚至更久,阻塞其他读写,还可能因 binlog 写入放大导致从库延迟飙升。
- 拆成小批量更新,用主键范围控制每次数量:
UPDATE orders SET status = 'shipped' WHERE id BETWEEN 10000 AND 10999 AND status = 'pending'; - 配合
LIMIT(注意:仅限 MySQL 5.6+,且必须有ORDER BY才可控):UPDATE logs SET processed = 1 WHERE processed = 0 ORDER BY id LIMIT 1000; - 避开业务高峰;监控
innodb_row_lock_waits和Seconds_Behind_Master,发现异常立即暂停
真正难的不是语法,是判断哪一行该更新、锁多久、主库扛不扛得住、从库跟不跟得上——这些都藏在 WHERE 和数据分布里。