MySQL 在 UPDATE / DELETE 时防止其它操作
MySQL 在执行 UPDATE / DELETE 时,如何防止别的事务对同一批数据进行 INSERT / UPDATE / DELETE 的干扰。
要防止其它操作影响你正在 UPDATE/DELETE 的数据,核心是:用正确的 InnoDB 锁(记录锁、间隙锁、Next-Key Lock),并确保走索引。
UPDATE / DELETE 在执行时:
- 自动对目标行加 排他锁(X 锁) -> 防止其他事务 UPDATE/DELETE
- 自动加 Next-Key Lock(记录锁 + 间隙锁) -> 防止其他事务在范围内 INSERT
- 如果语句不能走索引 -> 会锁全表(非常危险!)
例如:
UPDATE users SET age=30 WHERE id=10;
锁住记录 id=10:
- 🔒 阻止其它事务 UPDATE 这行
- 🔒 阻止其它事务 DELETE 这行
- ❌ 不阻止 SELECT(快照读)
例如:
DELETE FROM orders WHERE amount > 100 AND amount < 500;
InnoDB 会自动加 Next-Key Lock(记录锁 + 间隙锁):
- 🔒 阻止其他事务插入(INSERT)
- 🔒 阻止其他事务更新命中该范围的数据
- 🔒 防止幻读
也就是说:
你的 DELETE 正在扫描范围
- 其他人不能在这个范围插入
- 也不能更新范围内记录
- 也不能删除范围内记录
例如:
UPDATE users SET status=1 WHERE name='abc';
但 name 没有索引 -> 产生:
全表扫描
全表加 Next-Key Lock!
其他事务对整张表的 INSERT/UPDATE/DELETE 都会被阻塞。
事务 A:
UPDATE users SET score = score + 10 WHERE id BETWEEN 100 AND 200;
这会锁住:
[100, 200] 之间的所有记录 + 所有间隙
事务 B:
INSERT INTO users(id, score) VALUES(150, 10);
会被阻塞,因为 150 在事务 A 的锁区间内。
UPDATE、DELETE 自动保护机制:
| 操作 | 是否阻止别人 INSERT | 是否阻止别人 UPDATE | 是否阻止别人 DELETE | 原因 |
|---|---|---|---|---|
| 单点 UPDATE | ❌(单点无间隙) | ✅ | ✅ | X Lock |
| 范围 UPDATE | ✅ | ✅ | ✅ | Next-Key Lock |
| 单点 DELETE | ❌ | ✅ | ✅ | X Lock |
| 范围 DELETE | ✅ | ✅ | ✅ | Next-Key Lock |
如果希望“当前事务操作的数据完全避免外界改动”,可以使用:
先锁行,再更新
SELECT * FROM users WHERE id IN (1,2,3) FOR UPDATE;
UPDATE users SET ... WHERE id IN (1,2,3);
优点:
- 强制锁定目标记录
- 阻止其它 UPDATE/DELETE
- 确保不会发生并发覆盖
把所有操作变成一个原子过程:
START TRANSACTION;
UPDATE/DELETE ...;
-- 其它检查、业务逻辑
-- 计算新值
COMMIT;
如果没有索引:
- UPDATE/DELETE 会锁表
- 会阻塞别人所有写入
- 性能极差
如果有索引:
- 精确锁定记录
- 减少冲突
- 避免锁扩大化
例如:
❌ 这样会锁表:
DELETE FROM msg WHERE created_at < '2020-01-01';
✅ 应加索引:
ALTER TABLE msg ADD INDEX idx_created_at(created_at);
一般用这两个方案:
START TRANSACTION;
SELECT * FROM orders
WHERE id = 123
FOR UPDATE;
UPDATE orders SET status = 'done'
WHERE id = 123;
COMMIT;
这时:
- 其它事务不能 UPDATE 同一行
- 也不能 DELETE 同一行
- 不能 INSERT 冲突范围(若是范围查询)
比如你希望范围内数据完全由你独占:
START TRANSACTION;
SELECT * FROM users
WHERE score BETWEEN 100 AND 200
FOR UPDATE;
UPDATE users
SET level = level + 1
WHERE score BETWEEN 100 AND 200;
COMMIT;
这会锁定:
- 所有匹配行的记录锁
- 所有间隙锁
-> 其它事务无法插入符合条件的记录
完全隔离,绝对安全。
要防止别人对你正在 UPDATE/DELETE 的数据进行读写:
| 需求 | 使用 |
|---|---|
| 防止别人 UPDATE/DELETE 同一行 | 自动加的 X 锁 |
| 防止别人 INSERT 冲突数据 | 使用范围查询 -> Next-Key Lock |
| 防止别人并发修改同一批数据 | SELECT … FOR UPDATE |
| 防止写入冲突且确保强一致 | 使用事务包裹 |