SELECT ... FOR UPDATE 用于先查后改场景(如扣库存),加排他锁;SELECT ... LOCK IN SHARE MODE 用于协同只读场景(如报表),加共享锁;二者均需事务、索引支持,否则退化为表锁或间隙锁。
MySQL 中
和
的适用场景
这两个语句是 InnoDB 实现行级读写锁的核心手段,但它们不是“加锁就能用”,而是必须在事务中生效,且只对索引列有效。如果
条件未命中索引,InnoDB 会退化为表锁(或间隙锁组合),导致并发骤降。
:适用于需要“先查后改”的典型场景,比如扣减库存、转账前校验余额——它会加排他锁(X 锁),阻塞其他事务的读写
:适合多事务需协同读取同一数据但不修改的场景,比如生成报表时防止基础数据被删——它加共享锁(S 锁),允许其他事务加 S 锁,但阻塞 X 锁
两者都要求事务隔离级别为
或
;在
下会被忽略
为什么
有时锁住整张表而不是某几行
根本原因在于锁的粒度由执行计划决定,而非 SQL 表面写法。即使写了
,若
列没有索引,优化器无法定位具体行,只能走全表扫描,进而对所有聚簇索引记录加 X 锁——表现就是“锁表”。
用
检查执行计划:
字段必须是
、
或
,不能是
或
注意
隐式类型转换
:比如
(字段是
),会导致索引失效,锁范围扩大
唯一索引和普通索引行为不同:非唯一索引可能触发间隙锁(Gap Lock),锁住不存在的值区间,造成意外阻塞
高并发下锁等待超时与死锁的实际应对
和
是线上最常遇到的两类锁问题,但它们成因和处理方式完全不同。
MySQL(Linux)
MySQL 9.6.0是面向Linux平台的2026年创新版本,核心架构迎来重大革新。其将外键约束与级联操作从InnoDB引擎层上移至SQL层,确保所有数据变更均被完整记录至Binlog,彻底解决了CDC(变更数据捕获)与主从复制中的数据不一致难题。此外,该版本引入container_aware启动选项以原生适配容器环境,并对审计日志进行了组件化重构,为追求极致数据一致性与云原生体验的开发者提供了全新选择。
下载
锁等待超时(
):通常是事务持有锁时间过长(比如事务里混入 HTTP 调用、日志写入或 sleep),应缩短事务生命周期,把非数据库操作移出事务块
死锁(
):本质是循环等待,MySQL 会自动回滚代价小的事务。关键不是避免死锁(不可能完全避免),而是让应用能捕获
并重试——尤其对幂等性操作(如更新状态)必须支持重试逻辑
通过
查看最近死锁详情,重点关注
块中的
等描述,定位哪几行/哪个索引被争抢
替代方案:用
或
规避显式锁
当业务逻辑本质是“存在则更新,不存在则插入”,硬上
+
不仅代码冗长,还放大锁竞争。这类场景优先考虑原子性更强的替代语法。
:依赖唯一索引(主键或
约束),整个操作由 InnoDB 内部加锁完成,无需手动控制事务顺序,性能更稳
:底层是
,会触发两次索引查找和锁操作,且可能引发自增 ID 跳变,在高并发下不如前者可控
注意:这两者仍会加锁,但锁的持有时间极短(仅在索引查找和行修改瞬间),且无事务上下文依赖,天然规避了长事务锁表风险
锁的复杂性不在语法本身,而在于它和索引、事务、隔离级别、执行路径深度耦合。一个没走索引的
,比十个没加锁的
更容易拖垮系统。
SELECT ... FOR UPDATESELECT ... LOCK IN SHARE MODEWHERESELECT ... FOR UPDATESELECT ... LOCK IN SHARE MODEREAD COMMITTEDREPEATABLE READREAD UNCOMMITTEDFOR UPDATEWHERE id = 123idEXPLAINtypeconstrefrangeALLindexWHERE user_id = '123'INTLock wait timeout exceededDeadlock found when trying to get lockLock wait timeout exceededDeadlock found when trying to get lockerrno 1213SHOW ENGINE INNODB STATUS\GTRANSACTIONlock_mode X locks rec but not gapINSERT ... ON DUPLICATE KEY UPDATEREPLACE INTOSELECT ... FOR UPDATEINSERT/UPDATEINSERT ... ON DUPLICATE KEY UPDATEUNIQUEREPLACE INTODELETE + INSERTINSERT INTO order_lock (order_id, status, updated_at)
VALUES (1001, 'processing', NOW())
ON DUPLICATE KEY UPDATE status = 'processing', updated_at = NOW();FOR UPDATEUPDATE