从全局锁到行级锁,MySQL 的锁机制是数据库并发控制里最绕不开的一块硬骨头。面试必问的“MySQL 锁有哪些类型”、生产环境里时不时蹦出来的 Lock wait timeout exceeded、甚至一条 UPDATE 把整个业务拖慢,背后都是锁在起作用。这篇文章我会从全局锁开始,一直讲到 InnoDB 的行级锁、间隙锁、插入意向锁这些细节,把每个锁的适用场景、加锁时机、观察方法和我在实际运维中踩过的坑都串起来。不管你是刚接触 MySQL 的开发者,还是正在准备面试,或者在做数据库优化和故障排查,这篇都能给你一个相对完整的锁机制地图。
1. MySQL 锁的类型全貌:从库到表再到记录
1.1 为什么需要锁:并发下的资源竞争
先聊一个最基础的问题:为什么 MySQL 需要锁?数据库本身是一个多用户并发访问的系统,同一时刻可能有几十上百个会话在读写同一张表、同一行记录。如果没有一个协调机制,两个事务同时修改一条数据,后写入的覆盖先写入的,前一个事务刚读到的数据就被另一个事务改了,数据一致性和事务隔离都无从谈起。
锁就是数据库用来协调并发访问的机制。它本质上是在说:这块资源我现在要用,你在旁边等着,等我用完再给你。这个思想不难理解,就像一间办公室只有一个打印机,两个人同时要用,要么排队,要么约定谁先谁后。数据库锁做的事情就是这种排队和约定,只不过它的资源粒度更细、并发的策略也更复杂。
MySQL 的锁从粒度上来分,一般有全局锁、表级锁、行级锁三类。全局锁锁的是整个实例,表级锁锁的是某张表,行级锁锁的是某条或某个范围内的记录。粒度越细,并发能力越强,但管理锁的代价也越高。这也是为什么 InnoDB 默认使用行级锁,而 MyISAM 只能支持到表级锁的一个重要原因。
1.2 从锁的读写模式看共享锁与排他锁
除了按粒度分,锁还可以按读写模式分为共享锁(S 锁)和排他锁(X 锁)。共享锁可以理解为“我也读一下,不影响别人读”,多个事务可以同时持有同一资源的共享锁。排他锁则是“这块资源我来改,谁都别碰”,一旦有事务拿到排他锁,其他事务无论是读还是写都要等待。
这两个概念是理解后续所有表级锁、行级锁的基础。你会发现表锁有 READ 和 WRITE,行锁有共享锁和排他锁,全局锁的 FTWRL 本质上也是一种排他性质的全局锁。在锁的兼容矩阵里,核心规则就两条:共享锁和共享锁互相兼容,共享锁和排他锁冲突,排他锁和任何锁都冲突。搞明白这个矩阵,后面看锁等待的排查结果会轻松很多。
1.3 不同存储引擎的锁策略差异
MySQL 的锁机制由存储引擎来实现,所以不同的存储引擎锁策略差异极大。MyISAM 只支持表级锁,读写操作都会把整张表锁住,写入时其他会话连读都不能读,所以它的并发写入能力很差,现在除了某些只读场景基本已经被 InnoDB 取代。InnoDB 支持行级锁,同时也在某些场景下需要用到表级锁,比如 DDL 期间的元数据锁,以及给表加字段时的 online DDL 流程,这很多人不太清楚。Memory 引擎同样只支持表级锁。你在看锁等待的时候,务必先搞清楚表的存储引擎,不然可能分析方向就错了。
1.4 MySQL 5.7 / 8.0 对锁观测能力的增强
早期 MySQL 版本里,锁等待的问题比较难观测,DBA 往往只能通过 show processlist 看到一堆 Waiting for table metadata lock 之类的状态干着急。MySQL 5.7 开始增加了 sys 库,里面有很多现成的视图可以直接查锁等待关系,比如 sys.innodb_lock_waits、sys.schema_table_lock_waits,分析问题效率高了不少。MySQL 8.0 里又多了一些 API 和性能字典表,可以直接查询数据字典。如果你还在用 5.6 或更早的版本,遇到锁问题只能靠 informmation_schema 下的 INNODB_TRX、INNODB_LOCKS 这类表来手工关联分析,相对麻烦。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 全局锁:备份、整库只读与危险时刻
2.1 加全局锁的命令与执行时机
全局锁的概念很直接,就是给整个 MySQL 实例加一把锁,锁上之后整个库变成只读状态。官方提供的命令是 FLUSH TABLES WITH READ LOCK,一般简写成 FTWRL。这条命令执行后会把所有表关闭,然后给整个实例加上全局读锁,在解锁前,任何会话的写操作都会被阻塞,包括 insert、update、delete、DDL 等。
我记得第一次在测试环境执行 FTWRL 时,直观感受就是所有写入都卡住了,查询还能正常返回,整个实例就像进入了一个“只读模式”。FTWRL 最常见的使用场景是做物理备份或者逻辑备份前,用来获取一份一致性的数据快照。比如用 mysqldump 备份时主动执行 FTWRL,可以让备份期间所有表的数据保持在一个时间点,避免备份到一半数据变了导致备份文件不可用。
2.2 FTWRL 在备份场景的必要性与锁的影响范围
提到备份为什么要加全局锁,这里有一个细节。mysqldump 如果不加 --single-transaction,在备份 InnoDB 表时并不是一致性快照,可能需要各种表级锁才能保证数据一致;而加全局锁则简单粗暴,直接让整个实例不能写,备份自然就是一致的。很多人在生产环境跑 mysqldump 默认会加 --single-transaction --set-gtid-purged=OFF,此时 InnoDB 表可以通过 MVCC 机制拿到一致性快照,并不会使用 FTWRL。但注意一点:--single-transaction 对 MyISAM 表不生效,如果有 MyISAM 表仍然需要 FTWRL 来保证一致性,这必须提前想清楚。
FTWRL 的影响范围是全库的写阻塞,所以它的风险非常大。生产环境如果在业务高峰期误执行 FTWRL,哪怕只锁几秒钟,积压的写请求也会让应用端出现大量超时。更可怕的是,如果执行了 FTWRL 后客户端会话一直不释放,可能全局锁持续几分钟甚至更久,数据库写入全面瘫痪。我自己遇到过的情况是开发同学连了一个长连接,在一个事务里执行 FTWRL 后忘记提交,导致其他业务完全不可写入,最后 kill 掉对应会话才恢复。
2.3 全局锁的替代方案与参数控制
真正的生产环境里,使用 FTWRL 的机会其实很少。数据备份一般用物理备份工具,比如 Percona XtraBackup;逻辑备份则用带事务一致性选项的 mysqldump,InnoDB 表都可以用 MVCC 来做快照。对于需要整库只读的场景,MySQL 8.0 之后还支持用 SET GLOBAL super_read_only = 1 或 read_only = 1 来让实例进入只读状态,这种方式不会强制关闭所有表,对运行中的连接影响更小。不过它跟全局锁不是同一个层面的东西——前者是在权限和写入控制层面拒绝写操作,锁还是可以加的,只是加了也执行不了写入;FTWRL 则是从锁层面直接阻塞写。搞运维的人需要分清这两者,别一提整库只读就只想到 FTWRL。
2.4 执行 FTWRL 前后的检查清单
如果你发现确实需要用 FTWRL,我建议你执行前检查以下几点:确
