像素岛沙盒冒险
64.86MB · 2026-02-07
做后端开发的朋友不管是在开发过程中还是在线上环境,亦或者是面试中,多多少少肯定都遇过 MySQL 锁相关的问题。
其实MySQL锁问题还挺常见的。比如线上业务突然卡顿、线上突然出现事务阻塞、死锁报错,这些问题查到最后发现基本都是 MySQL 锁冲突问题。是不是有时听别人说起行锁、表锁,自己却一头雾水?虽然MySQL 里的锁看起来五花八门,但其实 MySQL 的锁说复杂也复杂,说简单吧,理清分类逻辑就能理解了。
下面一张图告诉你从不同的维度理解MySQL的锁。

MySQL 的锁说起来复杂,但其实按不同维度拆分一下就清晰多了。咱们可以从最常用的几个分类开始展开说:
这应该是最直观的分类了,毕竟锁定的范围直接影响并发性能。
这个维度是围绕读写操作的权限来的,核心就是解决 "能不能同时读"、"能不能同时写" 的问题:
select ... lock in share mode,其他事务也能加 S 锁读,但想加写锁就得等了。update、delete语句,InnoDB 会自动给涉及的行加 X 锁,select ... for update也能手动加。除了上面两个核心维度,还有几个分类也得知道:
alter table的时候,就会加 DDL 写锁,阻塞所有 DML 操作。update自动加 X 锁)和显示锁(比如手动执行lock tables或者select ... for update)。where version = 1,只有版本匹配才更新,适合并发冲突少的场景。光说分类太抽象,咱们可以看看实际开发中经常遇到的锁:
听名字就知道范围有多大了,执行flush tables with read lock(FTWRL)就能给整个库加全局读锁,加了之后整个库就只读了,增删改、DDL、事务提交全都会被阻塞。
这个锁主要用在全库逻辑备份的时候,但要注意,InnoDB 可以用mysqldump --single-transaction来做一致性备份,不用加全局锁,因为它会启动一个事务,利用 MVCC 拿到一致性视图。但是吧,如果是 MyISAM 这种不支持事务的引擎,那还是得用 FTWRL。
这里要提个坑,别用set global readonly = true来代替 FTWRL,因为如果客户端异常断开,FTWRL 会自动释放锁,但readonly不会,搞不好就把库一直锁在那儿了。
这个锁是 InnoDB 自动加的,你甚至感知不到它的存在,但它却很重要。比如你执行select、update这些 DML 操作,InnoDB 会自动加 MDL 读锁;执行alter table这种 DDL 操作,会加 MDL 写锁。
MDL 读锁之间不互斥,读锁和写锁、写锁和写锁是互斥的,这样就能防止一个事务在修改数据的时候,另一个事务突然改了表结构,导致数据不一致。
这里有个容易踩的坑:MDL 锁是在事务开始时申请,事务提交后才释放的。比如你开了一个事务,只是执行了一个select,这时候加了 MDL 读锁,然后你一直不提交事务,这时候别人要执行alter table就会被卡住,直到你提交事务。我之前就碰到过一次,排查了半天才发现是个没提交的事务占着 MDL 锁,真是血泪教训啊!

InnoDB 的行锁其实还有更细的划分,主要是为了解决幻读问题:
update user set name = '张三' where id = 1,如果 id 是主键,那就是给 id=1 的这行加记录锁。select * from user where id between 1 and 5 for update,会锁住 (1,3)、(3,5) 这两个间隙,防止其他事务在中间插入 id=2 或 4 的记录,解决幻读问题。select * from user where id > 3 for update,会锁住 (3,5]、(5, +∞) 这些区间,既锁住了存在的记录,也锁住了可能插入的间隙。
说了这么多,再给大家提几个注意事项:
update user set name = '张三' where name = '李四',如果 name 没有索引,那就是全表扫描,加表锁,并发瞬间就没了。innodb_lock_wait_timeout设置等待超时时间,默认是 50 秒。其实吧,MySQL 的锁虽然多,但只要理清分类,理解每种锁的适用场景和底层逻辑,遇到问题的时候就能快速定位了。比如线上出现锁等待,先看看是表锁还是行锁,再看看是哪个事务占着锁,一步步排查总能解决的。
最后想问下,你在开发中遇到过最头疼的锁问题是什么?欢迎在评论区交流。