UPDATE 是什么
UPDATE 修改已有行,语法是 UPDATE 表 SET 列=值 [WHERE 条件] [LIMIT n]。它的风险明显高于查询:条件写错不是查不到数据,而是改多了;条件列没有索引时,行锁会沿着扫描路径扩大到大量记录,并发写入被阻塞,主从之间也会因为大事务产生延迟。稳妥的做法是先用相同条件 SELECT 确认影响范围,必要时包在事务里,大表更新分批推进。
基本信息
| 用途 | 修改已有行 |
|---|---|
| 语法 | UPDATE 表 SET 列=值[, ...] [WHERE 条件] [ORDER BY ...] [LIMIT n] |
| 安全前置 | 先用相同条件做一次 SELECT 计数 |
| 锁行为 | 条件走索引只锁命中行,扫描整表会锁住大量行 |
| 事务建议 | 批量更新放事务并分批提交 |
| 复制影响 | 单条语句改动行数很大时主从延迟明显 |
典型使用场景
- 状态流转:把超时未支付的订单批量置为关闭
- 计数列自增:浏览量、下载量加一
- 数据修复:修正错误字段值并补上时间戳
- 批量打标:给一批用户补标签或调整分组
- 归档前预处理:给待迁移数据打标记位
- 定时任务分批推进:每批限制行数,避免长事务
安全更新流程
安全更新流程
SELECT COUNT(*) FROM orders WHERE status='pending' AND created_at < '2026-09-01'; # 预演,确认影响范围
BEGIN;
UPDATE orders SET status='closed' # 分批更新,用 LIMIT 控制单次规模
WHERE status='pending' AND created_at < '2026-09-01' LIMIT 1000;
COMMIT; # 每批独立提交,控制事务大小
SELECT COUNT(*) FROM orders WHERE status='pending' AND created_at < '2026-09-01'; # 复核剩余量多列与表达式更新
多列与表达式更新
UPDATE counters SET cnt = cnt + 1, updated_at = NOW() WHERE id = 7; # 基于原值自增,行内原子完成
UPDATE users SET level = level + 1
WHERE vip_expire_at > NOW(); # 条件列有索引,锁范围才不会扩散更新风险对照
| 情形 | 后果 | 处置 |
|---|---|---|
| 没有 WHERE | 全表被修改 | 执行前必须确认条件,必要时开事务 |
| 条件列无索引 | 扫描到的行都被加锁,并发写入阻塞 | 先给条件列建索引,或分批更新 |
| 单条影响行数极大 | 长事务、回滚段膨胀、主从延迟上升 | 拆批提交并监控复制延迟 |
| 更新同时用子查询读同表 | 直接报错无法指定目标表 | 改成连接或先落临时表 |
| 更新唯一键列 | 与既有值冲突时整条语句失败 | 先确认目标值未被占用 |
| 带排序与 LIMIT | 只改排序后的前 N 行 | 排序键不确定时命中的行也会变 |
没有 WHERE 的 UPDATE 会修改全表;条件列没有索引时锁范围会从几行扩大到整张表,生产环境等同事故,动手前先用相同条件 SELECT 计数。
大表更新建议按主键或时间范围分批,每批数千行并提交一次,既控制锁持有时间与回滚段增长,也把主从延迟压在可接受范围内。
更新计数类列时写成基于原值的表达式(列 = 列 + 1),由服务端在行内完成计算,能避免应用先读后写造成覆盖;但同一行的并发更新频率仍需业务侧控制。
更新检查清单
- 条件列是否有索引,能否只锁住命中的行
- 影响行数是否可控,超过阈值就改成分批执行
- 是否需要开启事务,执行失败时能否整体回滚
- 是否有并发写入同一批行,锁等待会不会引发超时
- 是否会影响主从延迟,是否需要限速或避开高峰
- 更新后是否要复核计数与关键业务指标
把更新语句先在测试环境跑一遍并核对影响行数,是最省成本的验证方式;生产上首次执行时用小批量试探条件是否写对,再放开全量推进。