有次值班,线上 MySQL 突然像抽搐了一样,所有写请求都卡住,拉到 show processlist 一看,满屏的 Waiting for lock。那段时间正好在看锁机制,从全局锁、表级锁一路梳理到行级锁,整个过程把很多模糊的概念彻底理清了。这篇就把我自己的理解和实际排查经验完整写出来,遇到锁表、锁等待、死锁问题时,能帮你少走弯路。
1. 并发控制与 MySQL 锁机制的三个层级
1.1 从“多人改同一行”说起
MySQL 作为一个数据库,每天要面对大量并发读写。多个事务同时读数据不会出问题,但只要有写操作,就可能互相干扰。比如两个事务同时把某一行金额从 100 改成 80,如果没有任何约束,最终结果可能是 80,也可能是 60,完全取决于谁后提交。
锁的作用就是给资源访问立规矩。事务在修改数据前必须拿到对应的“排他许可”,其他事务想改同一份数据时只能排队等候。这个机制和公共厕所差不多:有人占用时,后面的人只能等着,等里面的人出来才能进。
MySQL 的锁并不是一把大锁锁住全部数据,而是按影响范围分层设计。理解清楚每一层锁锁什么、什么时候生效、怎么释放,才能解释线上很多诡异现象。
1.2 锁粒度越大,并发能力越低
从影响范围看,MySQL 的锁可以分为三个层级:
- 全局锁:锁住整个数据库实例,所有表都变成只读。
- 表级锁:锁住某一张表,表内所有记录都受影响。
- 行级锁:只锁住某一行或某个索引区间,其他行依然可并发操作。
这里有一个取舍问题。全局锁最简单粗暴,但并发能力最低;行级锁最精细,并发能力最高,但对实现和排查的要求也最高。InnoDB 引擎同时实现了表级锁和行级锁,线上绝大多数锁问题其实都集中在行级锁与元数据锁上。
1.3 InnoDB 才是主角
默认情况下 MySQL 使用 InnoDB 存储引擎。MyISAM 只有表级锁,写并发一高基本卡死,现在很少用于核心业务。后面讲的行级锁、间隙锁、死锁检测,都是 InnoDB 的特性。这也是为什么线上排查锁问题时,第一步要看表引擎是否为 InnoDB,如果是 MyISAM,很多现象就要用另一套逻辑去解释。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 全局锁:影响整实例的命令不要乱用
2.1 全局锁到底锁住了什么
全局锁的官方命令是 FLUSH TABLES WITH READ LOCK,一般简写成 FTWRL。
执行这条命令后,整个实例的所有表都会被加上只读锁。业务上的 INSERT、UPDATE、DELETE、ALTER TABLE 全部阻塞,只有 SELECT 可以正常执行。
可以这样理解:数据库进入“全员阅读模式”,任何想改数据的人都得停在门外。
实际操作中很少会手动执行这条命令,但必须知道它的存在,因为一些备份工具在特定场景下会自动调用,导致线上突然写入卡住。
2.2 全局锁和一致性备份的取舍
全局锁经常出现在“保证备份一致性”的场景里。如果你用文件系统快照方式备份,或者备份的库里有 MyISAM 表,想要保证所有表数据处于同一个时间点,就需要先加全局锁,让写入停住,再拷贝数据文件。
但如果线上库全是 InnoDB,其实有更温和的方案。用 mysqldump 备份时加 --single-transaction 参数,会在事务隔离级别为可重复读的情况下开启一个一致性快照,备份过程中 InnoDB 表的数据依然可以被业务正常读写。
很多 DBA 的习惯是:
bash复制mysqldump -uroot -p --single-transaction --set-gtid-purged=OFF database_name > backup.sql
这样备份对业务几乎无感。关键点在于:--single-transaction 只对 InnoDB 表有效,如果库里混着 MyISAM 表,一致性就无法保证,这时才需要 FTWRL 配合。
2.3 全局锁引发线上事故的真实场景
我见过一次比较典型的事故:同事在业务高峰期执行了一个备份脚本,脚本为了“保险”,在备份前先执行了 FLUSH TABLES WITH READ LOCK,然后又把整个库的所有表数据导出。结果所有写请求在数据库连接池里堆成山,过了几十秒,连接池被打满,线上服务直接雪崩。
这里有一个容易忽略的连锁反应:全局锁虽然没有设置超时,但应用侧的连接池有最大连接数。写请求一旦被阻塞,连接就迟迟不释放,新请求也拿不到连接,最后把整个服务拖死。
所以使用全局锁,一定要避开业务高峰,并且把加锁的时间控制到最短。哪怕需
