事务隔离级别决定锁行为:READ COMMITTED下FOR UPDATE仅锁命中行,REPEATABLE READ默认加间隙锁防幻读但易死锁,SERIALIZABLE使所有SELECT加共享锁;INSERT…ON DUPLICATE KEY UPDATE依赖唯一索引记录锁,不加间隙锁;显式加锁需索引命中、避免耗时操作与顺序不一致;自增锁在高并发插入时成瓶颈,建议调优模式或改用无锁ID。

事务隔离级别直接影响锁行为
MySQL 的 SELECT ... FOR UPDATE 或 INSERT ... ON DUPLICATE KEY UPDATE 是否加锁、加什么锁,首先取决于当前事务的隔离级别。在 READ COMMITTED 下,普通 SELECT 不加锁,但 FOR UPDATE 只锁定命中行;而在 REPEATABLE READ(InnoDB 默认)下,它还会隐式加间隙锁(Gap Lock),防止幻读——这既是保护,也是死锁温床。
- 生产环境若大量使用范围条件更新(如
WHERE status = 0 LIMIT 10),REPEATABLE READ下可能锁住整个索引区间,阻塞并发插入 - 若业务能接受“读已提交”,可显式设为
SET TRANSACTION ISOLATION LEVEL READ COMMITTED,减少间隙锁范围 -
SERIALIZABLE会把所有普通SELECT都转成SELECT ... LOCK IN SHARE MODE,几乎等于串行化,极少用
INSERT … ON DUPLICATE KEY UPDATE 是最轻量的写冲突处理
当存在唯一键(UNIQUE 或 PRIMARY KEY)时,INSERT ... ON DUPLICATE KEY UPDATE 在内部由 MySQL 自动处理“先查后插/更”的竞争,无需手动加锁,且只在发生冲突时才触发更新逻辑。它底层依赖的是唯一索引上的记录锁(Record Lock),不会扩展到间隙。
INSERT INTO order_log (order_id, status, updated_at) VALUES (123, 'paid', NOW()) ON DUPLICATE KEY UPDATE status = VALUES(status), updated_at = NOW();
- 必须确保
order_id有唯一索引,否则语句不生效或报错 - 如果
UPDATE子句中引用了未在VALUES()中提供的列(如updated_at = updated_at + 1),要注意是否真的需要该语义——多数场景应直接赋值NOW() - 该语句在 binlog 中以
ROW格式记录,主从一致,但若用MIXED模式,某些函数(如NOW())可能被转成STATEMENT,引发主从时间不一致
显式加锁要慎用 SELECT … FOR UPDATE
当你必须“读出再计算再更新”(比如扣库存:查余额 → 判断是否足够 → 扣减),就得用 SELECT ... FOR UPDATE。但它不是万能解药,容易成为性能瓶颈和死锁源头。
本书是全面讲述PHP与MySQL的经典之作,书中不但全面介绍了两种技术的核心特性,还讲解了如何高效地结合这两种技术构建健壮的数据驱动的应用程序。本书涵盖了两种技术新版本中出现的最新特性,书中大量实际的示例和深入的分析均来自于作者在这方面多年的专业经验,可用于解决开发者在实际中所面临的各种挑战。
- 务必在
WHERE条件上命中索引,否则会升级为表锁(尤其在REPEATABLE READ下) - 避免在事务里做耗时操作(如调用外部 HTTP、复杂计算),否则锁持有时间过长
- 多个
FOR UPDATE语句访问行的顺序不一致(A 先锁 id=1 再锁 id=2,B 反过来),是典型死锁诱因;建议统一按主键升序加锁 - 可以用
SELECT ... FOR UPDATE SKIP LOCKED(MySQL 8.0+)跳过已被锁的行,适合队列类消费场景
自增主键与 insert 并发的锁表现
InnoDB 的自增锁(auto-inc lock)在高并发 INSERT 时可能成为隐形瓶颈。默认 innodb_autoinc_lock_mode = 1(连续模式)下,简单插入(不含 INSERT ... SELECT)不锁表,但批量插入仍需获取表级自增锁。
- 若应用频繁执行
INSERT INTO t SELECT ... FROM other_t,考虑调大innodb_autoinc_lock_mode = 2(交错模式),但要求 binlog 格式为ROW - 不要依赖
AUTO_INCREMENT值做业务逻辑(如“订单号=时间戳+自增ID”),因为 ID 分配不连续、不可预测 - 对于超高并发写入,可考虑分库分表、或改用雪花 ID 等无锁生成策略,把写压力从单表主键分配上卸下来
真正难的不是写对一条 FOR UPDATE,而是理清哪条语句在什么索引路径下会锁住哪些索引项、是否包含间隙、会不会被其他事务的相同语句反向覆盖——这些只有看 INFORMATION_SCHEMA.INNODB_TRX 和 INNODB_LOCKS(MySQL 5.7)或 performance_schema.data_locks(8.0+)才能确认。









