MySQL锁由隔离级别与SQL类型共同决定:RC下仅锁命中行且无间隙锁,RR默认加间隙锁防幻读,SERIALIZABLE使所有SELECT隐式加锁;索引缺失会导致行锁退化为表锁;长事务会延长锁持有时间,需控制超时与加锁位置。

事务隔离级别直接决定默认锁行为
MySQL 的锁不是靠手动指定,而是由 SET TRANSACTION ISOLATION LEVEL 和具体 SQL 类型共同触发的。比如在 REPEATABLE READ 下,SELECT ... FROM t WHERE id = 1 不加锁,但 UPDATE t SET name='x' WHERE id = 1 会加行级记录锁(Record Lock);而同样语句在 READ COMMITTED 下,只锁命中的行,且不加间隙锁(Gap Lock)。
常见误判是以为“只要没写 FOR UPDATE 就不锁”,其实 UPDATE/DELETE 带 WHERE 条件时,InnoDB 必定先定位再加锁,是否锁住范围、是否锁住间隙,全看隔离级别和索引类型。
-
READ UNCOMMITTED:基本不加锁(仅意向锁),但脏读风险高,生产极少用 -
READ COMMITTED:每次快照读都新建一致性视图,锁只落在实际更新/删除的行上,无 Gap Lock -
REPEATABLE READ:默认级别,会加 Gap Lock 防止幻读,可能锁住索引区间(如WHERE age BETWEEN 20 AND 30) -
SERIALIZABLE:所有普通 SELECT 都隐式转成SELECT ... LOCK IN SHARE MODE,开销大,一般不用
FOR UPDATE 和 LOCK IN SHARE MODE 的适用边界
显式加锁必须明确目的:FOR UPDATE 是为了后续 UPDATE/DELETE,LOCK IN SHARE MODE 是为了防止其他事务改同一行,但允许并发读——它不排斥其他 S 锁,只排斥 X 锁。
典型陷阱是用 SELECT ... LOCK IN SHARE MODE 做“预占位”,结果发现两个事务同时拿到 S 锁后,都尝试升级为 X 锁(比如执行 UPDATE),造成死锁。真正需要串行化修改逻辑时,应统一用 FOR UPDATE,并确保加锁顺序一致。
- 读多写少且需防覆盖的场景(如库存扣减前校验),优先
SELECT ... FOR UPDATE - 只读聚合统计 + 同时允许其他只读事务访问,可考虑
LOCK IN SHARE MODE,但注意它不能防止幻读 - 不要在
LOCK IN SHARE MODE后跟UPDATE,这属于隐式锁升级,InnoDB 不保证原子性,容易出错
索引缺失会让行锁退化为表锁
InnoDB 行锁的前提是能通过索引精确定位记录。如果 WHERE 条件列没有索引,或用了函数/隐式类型转换导致索引失效(如 WHERE CAST(id AS CHAR) = '1'),优化器会走全表扫描,此时每条记录都会被加上记录锁——效果等同于锁整张表,且更容易引发锁等待和超时。
可通过 EXPLAIN 确认是否走了索引,再结合 SELECT * FROM performance_schema.data_locks 查看实际加锁范围(MySQL 8.0+)。5.7 可查 INFORMATION_SCHEMA.INNODB_TRX 和 INNODB_LOCKS(已废弃,但部分环境仍可用)。
- 执行
UPDATE users SET status=1 WHERE phone='138...'前,确认phone有唯一索引,否则可能锁住成千上万行 - 联合索引要注意最左匹配,
INDEX(a,b)支持WHERE a=1加行锁,但WHERE b=1会失效 -
LIKE '%abc'无法使用索引,慎用于加锁语句
长事务和锁等待时间要主动控制
锁持有时间 = 事务开启到 COMMIT/ROLLBACK 的时长。一个没提交的事务,哪怕只执行了一条 SELECT ... FOR UPDATE,也会持续持有锁,阻塞其他事务。MySQL 默认 innodb_lock_wait_timeout=50 秒,超时抛出 Lock wait timeout exceeded 错误,但前端未必捕获,容易演变成请求堆积。
更隐蔽的问题是:事务中混入了慢查询、远程调用、日志写入等非数据库操作,导致锁暴露窗口拉长。这时加锁本身没问题,问题出在“锁不该拿这么久”。
- 把加锁 SQL 尽量靠近事务末尾,避免前置查询后长时间空转
- 应用层设置合理的事务超时(如 Spring 的
@Transactional(timeout = 3)),比依赖 MySQL 默认值更可控 - 监控
INNODB_TRX.TRX_STATE = 'LOCK WAIT'和TRX_WAITING_TRX_ID,快速定位阻塞源头
锁策略选错往往不是语法问题,而是对“数据访问模式”和“业务一致性要求”的误判。比如把强一致性场景当成最终一致性来设计,或者反过来在高并发扣减里用 READ COMMITTED 加乐观锁,却没处理好重试逻辑。真实系统里,锁的代价常藏在平均延迟毛刺和偶发超时里,而不是报错本身。










