select for update只锁实际扫描并返回的行,前提是走索引;无索引时退化为临键锁,可能锁全表;在repeatable read下加next-key lock(记录锁+间隙锁),read committed下仅加记录锁。

SELECT FOR UPDATE 会锁住哪些行
它只锁住 SELECT 实际扫描并返回的行,不是整个表,也不是 WHERE 条件“可能匹配”的行——前提是查询能走索引。如果 WHERE 字段没索引,InnoDB 可能升级为锁全表(或全索引),变成间隙锁 + 记录锁的组合,范围远超预期。
常见错误现象:SELECT ... FOR UPDATE 执行后,另一事务更新看似无关的行却被阻塞,就是掉进了间隙锁陷阱。
- 有主键或唯一索引时:只锁匹配的记录本身(记录锁)
- 有普通索引但非唯一:锁匹配行 + 该索引中相邻间隙(防止幻读)
- 无可用索引:退化为聚簇索引的临键锁(Next-Key Lock),等效于锁住整个表的索引范围
必须在事务中执行,且不能自动提交
SELECT ... FOR UPDATE 是事务级语义,脱离事务或在 AUTOCOMMIT=1 下无效——它会立刻提交,锁也随即释放,起不到排他控制作用。
使用场景:典型用于“读取-修改-写入”三步操作,比如扣库存、生成单号、抢优惠券。
- 显式开启事务:
BEGIN或START TRANSACTION - 确认
AUTOCOMMIT关闭:SET AUTOCOMMIT = 0 - 锁生效后,必须靠
COMMIT或ROLLBACK显式结束事务,否则锁一直持有
不同隔离级别下锁行为差异明显
在 READ COMMITTED 下,InnoDB 仅使用记录锁(Record Lock),不加间隙锁;而默认的 REPEATABLE READ 会用 Next-Key Lock(记录锁 + 间隙锁),这是导致“莫名阻塞”的最常见原因。
性能影响:Next-Key Lock 范围更大,冲突概率高,尤其在高并发 insert 场景下容易卡住。
- 若业务允许幻读(如只更新已存在记录),可临时切到
READ COMMITTED:SET TRANSACTION ISOLATION LEVEL READ COMMITTED - 若必须用
REPEATABLE READ,确保WHERE条件走唯一索引,避免间隙锁扩散 - MySQL 8.0+ 支持
SELECT ... FOR UPDATE SKIP LOCKED,适合队列消费类场景,跳过已被锁行
FOR UPDATE 和 LOCK IN SHARE MODE 的关键区别
FOR UPDATE 加的是排他锁(X 锁),阻止其他事务加任何锁(包括 S 锁和 X 锁);LOCK IN SHARE MODE 加共享锁(S 锁),允许其他事务再加 S 锁,但阻塞 X 锁。
容易踩的坑:误用 LOCK IN SHARE MODE 替代 FOR UPDATE,结果两个事务同时读到同一行、同时更新,引发覆盖写。
- 需要后续执行
UPDATE或DELETE:必须用FOR UPDATE - 仅需防止别人改、自己不改(如校验状态):可用
LOCK IN SHARE MODE - 两者都要求事务上下文,且同样受索引、隔离级别影响
SELECT ... FOR UPDATE 自身都会被自己阻塞(如果重复执行)。这点常被忽略——不是语法错,是逻辑卡死。











