作为资深开发,理解 MySQL 的锁机制不能只停留在“行锁、表锁”的表面,必须深入到 InnoDB 存储引擎 的并发控制逻辑中。MySQL 的锁设计本质上是在 并发性能 与 数据一致性 之间做权衡。
我们可以按照从“粒度”到“策略”的维度来梳理:
1. 按锁的粒度分类 (Granularity)
这是最直观的分类,决定了操作数据时“受影响的范围”:
-
全局锁 (Global Lock):
-
命令:
Flush tables with read lock (FTWRL)。 -
场景:主要用于全库逻辑备份。它会让整个库处于只读状态,危险性极高。
-
-
表级锁 (Table-level Lock):
-
表锁:
lock tables ... read/write。 -
元数据锁 (MDL):自动加锁。当你对表做增删改查时,加 MDL 读锁;当你要修改表结构(Alter table)时,加 MDL 写锁。注意: 如果一个长查询没结束,此时改表结构会导致后续所有请求堵塞。
-
意向锁 (Intention Lock):由 InnoDB 自动维护。在对行加锁前,先在表级别加一个“意向锁”。它的存在是为了快速判断表里是否有行被锁住,从而避免全表扫描去检查冲突。
-
-
行级锁 (Row-level Lock):
-
InnoDB 特有。锁的是索引记录,而不是物理行。如果查询没命中索引,InnoDB 会退化为全表扫描,此时会升级为表锁。
-
2. 按锁的兼容性分类 (Compatibility)
这是实现 读写分离 和 并发控制 的基础:
-
共享锁 (S 锁 / 读锁):允许其他事务也加 S 锁读取,但禁止修改。
SELECT ... LOCK IN SHARE MODE。 -
排他锁 (X 锁 / 写锁):一旦某行加了 X 锁,其他事务既不能读也不能写。
UPDATE,DELETE,INSERT以及SELECT ... FOR UPDATE都会自动加 X 锁。
3. InnoDB 行锁的三大算法 (核心)
这是面试中最常考的部分,也是理解 间隙锁 的关键:
-
Record Lock (记录锁):封锁索引记录本身。
-
Gap Lock (间隙锁):锁住索引记录之间的间隙,不包括记录本身。目的是防止幻读(Phantom Read)。
-
Next-Key Lock (临键锁):Record Lock + Gap Lock 的组合。它是 InnoDB 在
Repeatable Read(可重复读)隔离级别下的默认行锁算法。它锁住一个范围,并且包含记录本身。
4. 乐观锁 vs. 悲观锁 (Strategy)
这更多是开发层面的逻辑选择:
-
悲观锁 (Pessimistic):假定冲突一定会发生。直接利用数据库的
FOR UPDATE来锁住数据。适合写多读少、并发竞争极其激烈的场景。 -
乐观锁 (Optimistic):假定冲突很少发生。不利用数据库锁,而是通过代码中的
version版本号或时间戳对比来实现。-
SQL 示例:
UPDATE table SET stock = stock - 1, version = version + 1 WHERE id = 1 AND version = 10; -
优点:响应快,不阻塞连接,适合读多写少的互联网场景。
-
5. 死锁 (Deadlock) 与监控
当两个事务互相等待对方释放资源时,就发生了死锁。InnoDB 有自动死锁检测机制(innodb_deadlock_detect),会主动回滚成本较低的那个事务。
资深开发必会的排查命令:
-- 查看最近一次死锁的详细日志
SHOW ENGINE INNODB STATUS;
-- 查看当前的事务与锁等待情况
SELECT * FROM information_schema.INNODB_TRX;
SELECT * FROM performance_schema.data_locks; -- MySQL 8.0 推荐
📉 总结与建议
在你的 Java 项目(如 pancakebot.vip 或算法交易系统)中:
-
尽量走索引:确保
UPDATE语句命中索引,否则在高并发下会产生全表扫描的表锁,导致系统瞬间瘫痪。 -
控制事务大小:事务越长,持锁时间越久,并发性能越差。
-
考虑乐观锁:对于库存扣减、余额更新等场景,优先考虑
version版本号方式,能显著提升 QPS。
既然聊到了锁,需要我带你结合具体的 Repeatable Read 隔离级别,演示一下它是如何通过 MVCC (多版本并发控制) 来减少锁竞争的吗?