MySQL的UPDATE默认在可重复读(REPEATABLE READ)隔离级别下执行,但不依赖MVCC快照读,而是先加行锁或间隙锁,再用最新版本数据判断。

UPDATE 语句默认走什么隔离级别?
MySQL 的 UPDATE 默认在可重复读(REPEATABLE READ)下执行,但**不是靠 MVCC 快照读来更新**——它会先加行锁(或间隙锁),再用最新版本做判断。很多人误以为“RR 下 UPDATE 看的是快照”,结果发现并发更新时还是冲突或死锁,根源就在这儿。
常见错误现象:Deadlock found when trying to get lock;或两个事务交替更新同一行,后提交的覆盖前提交的修改(幻读式覆盖,尤其涉及范围条件时)。
- MyISAM 不支持事务,
UPDATE无隔离级别概念,直接表锁 - InnoDB 下,即使显式开启事务,
UPDATE也会触发当前会话的隔离级别行为 -
READ COMMITTED下,每次UPDATE都重新读取最新已提交数据,避免部分幻读,但可能多读一次
WHERE 条件没走索引时,锁住的是全表还是全聚簇索引?
没索引 → 无法定位具体行 → InnoDB 退化为**全表扫描 + 每行加记录锁**,等效于锁住整个聚簇索引。这不是“锁表”,但效果接近:其他事务对任意行的 UPDATE、DELETE 或带相同 WHERE 的操作都会被阻塞。
使用场景:线上误删条件写成 WHERE status = 'pending',而 status 没索引,整张订单表卡住 3 秒——这 3 秒里所有下单、改状态请求都在等锁。
- 用
EXPLAIN看type是否为ALL,key是否为NULL - 加索引不等于解决问题:如果
WHERE a=1 AND b LIKE '%x',只有a有索引,仍可能锁大量无关行 -
SELECT ... FOR UPDATE同样受此影响,别以为只UPDATE才要小心
想避免间隙锁(Gap Lock)?试试 READ COMMITTED + 唯一索引条件
间隙锁是 RR 级别下防幻读的主力,但它也是死锁高发源。唯一索引上的等值查询(如 WHERE id = 100)能**完全规避间隙锁**,只加记录锁;但范围查询(WHERE id > 100)或非唯一索引(WHERE name = 'Alice')仍会锁间隙。
性能影响:关闭间隙锁可降低死锁概率,但代价是可能读到新插入的满足条件的行(即允许幻读),这对资金类业务通常是不可接受的。
- 必须同时满足两个条件才真正避开间隙锁:
READ COMMITTED+WHERE匹配唯一索引且为等值 -
INSERT ... ON DUPLICATE KEY UPDATE在唯一键冲突时只锁冲突行,不锁间隙,比先SELECT再UPDATE更安全 - MySQL 8.0+ 可用
innodb_locks_unsafe_for_binlog=ON关闭间隙锁,但主从复制可能出错,不推荐
UPDATE 多列时,SET 顺序会影响锁升级吗?
不会。InnoDB 加锁顺序由**WHERE 条件匹配的行物理顺序**决定(聚簇索引顺序),和 SET 里字段排列无关。但 SET 中包含子查询或函数调用时,可能引发额外锁或临时表,间接拖慢锁持有时间。
容易踩的坑:写 UPDATE t SET a = (SELECT max(x) FROM log), b = now(),子查询可能扫全表并加共享锁,导致和另一事务的 INSERT INTO log 死锁。
- 拆开写:先查出
max(x)放应用层,再拼进 UPDATE,减少锁持有时间 - 避免在 SET 中调用
UUID()、NOW()以外的函数,尤其是涉及 I/O 或大表扫描的 - 批量更新超 1000 行时,分批加
LIMIT,否则长事务拖垮整个锁队列
事务隔离不是开关,是锁策略、MVCC 和引擎行为的混合体。最常被忽略的一点:你改的那条 SQL 走不走索引,比你设什么隔离级别更能决定系统会不会卡住。










