MySQL架构揭秘:锁机制

📅 2026/7/19 5:15:56
MySQL架构揭秘:锁机制
在数据库系统中锁是协调多线程并发访问资源的核心机制。对于MySQL的InnoDB存储引擎而言锁的实现尤为复杂且精妙。它不仅需要保证数据的一致性还要在高并发场景下尽可能提升性能。今天我们就由浅入深通过大量案例拆解把InnoDB的锁机制彻底讲明白。提示本文基于MySQL 5.7及8.0版本默认隔离级别为可重复读REPEATABLE READ。一、为什么需要锁——从宏观视角理解锁的意义在计算机世界里锁是一种协调多个进程或线程并发访问同一资源的机制。在数据库中数据本身就是被所有用户共享的核心资源。如何保证数据在并发访问时的一致性和有效性是所有数据库必须解决的问题同时锁的粒度也直接影响着数据库的并发性能。不同存储引擎的锁策略对比引擎锁粒度开销加锁速度死锁并发度MyISAM表级锁小快不会出现低InnoDB行级锁默认大慢会出现高适用场景表级锁更适合以查询为主、并发写入较少的应用如报表系统。行级锁更适合有大量并发更新、且每次只影响少量数据的应用如电商交易系统。二、InnoDB 锁的基本类型共享锁与排他锁S锁和X锁InnoDB 实现了标准的行级锁主要分为两种基本类型1. 共享锁Shared Lock简称S锁别称读锁。特性允许持有锁的事务读取一行数据。兼容性多个事务可以同时持有同一行数据的S锁S锁与S锁兼容。加锁方式SELECT ... LOCK IN SHARE MODE。2. 排他锁Exclusive Lock简称X锁别称写锁。特性允许持有锁的事务更新或删除一行数据。兼容性X锁与任何锁都不兼容S锁和X锁均冲突。加锁方式自动DELETE、UPDATE、INSERT语句默认添加X锁。手动SELECT ... FOR UPDATE。核心规则如果事务T1持有行r的S锁另一个事务T2请求行r的X锁T2必须等待。如果T1持有X锁则T2无论请求S锁还是X锁都必须等待。三、表级意向锁Intention Locks——多粒度锁定的桥梁InnoDB 不仅支持行锁也支持表锁如LOCK TABLES ... WRITE。为了实现多粒度锁定InnoDB 引入了意向锁。意向锁是表级别的锁由存储引擎内部自动维护无需用户干预。意向锁的类型意向共享锁Intention Shared LockIS锁事务打算在表中的某些行上加S锁如SELECT ... FOR SHARE。意向排他锁Intention Exclusive LockIX锁事务打算在表中的某些行上加X锁如SELECT ... FOR UPDATE。表级锁兼容性矩阵兼容性XIXSISX冲突冲突冲突冲突IX冲突兼容冲突兼容S冲突冲突兼容兼容IS冲突兼容兼容兼容设计意图意向锁的主要目的是表明“有人正在锁定表中的某一行或者即将锁定某一行”。当要加表锁如X表锁时数据库只需检查是否存在IX或IS锁即可无需逐行遍历检查是否有行锁从而极大提升了效率。四、揭开行锁的真面目锁住的到底是什么这是很多开发者最困惑的问题。我们需要明白InnoDB 的行锁实际上是索引记录锁Index-Record Lock。锁不是直接加在行记录上的而是加在索引上的。1. 有主键索引的情况结论锁住的是主键索引聚簇索引上的值。验证如果你SELECT * FROM t2 WHERE id 1 FOR UPDATE;其他事务试图修改id1或基于主键id1的更新会被阻塞但修改id3不受影响。2. 无主键/无索引的情况现象即使你只更新一条记录也可能会锁住全表所有记录导致并发能力急剧下降。原因如果表中没有索引InnoDB 会创建一个隐藏的聚簇索引并将全表的所有记录都锁定以进行全表扫描。因此给查询和更新建立合适的索引是防止锁冲突的关键。3. 利用唯一索引非主键结论除了锁住唯一索引本身还会锁住对应的聚簇索引主键。示例如果有uk_name唯一索引执行SELECT * FROM t3 WHERE name 张三 FOR UPDATE;不仅uk_name上的“张三”记录会被加锁背后的主键id记录也会被加锁。这是为了防止在修改行时其他事务通过主键删除该行。五、锁的粒度与范围Record、Gap 与 Next-Key假设我们有一张表t4id为主键数据记录为1, 4, 7, 10。1. Record Lock记录锁定义锁定索引中的某一条具体记录。触发条件针对唯一性索引主键/唯一键进行等值查询且记录存在。示例SELECT * FROM t4 WHERE id 4 FOR UPDATE;效果锁住id4的这行记录其他事务不能修改或删除它但可以插入id5因为没有锁间隙。2. Gap Lock间隙锁定义锁定索引记录之间的间隙或是第一条记录前、最后一条记录后的间隙。核心作用防止幻读Phantom Read。触发条件查询记录不存在。关键特性间隙锁之间是兼容的即两个事务可以持有同一个间隙的锁互不阻塞。示例分析SELECT * FROM t4 WHERE id 6 FOR UPDATE;记录6不存在锁定的间隙(4, 7)。效果阻止其他事务在(4, 7)之间插入数据如id5或id6但不影响查询或修改id4和id7的记录。SELECT * FROM t4 WHERE id 20 FOR UPDATE;锁定的间隙(10, ∞)阻止任何大于10的数据插入。3. Next-Key Lock临键锁定义记录锁Record Lock间隙锁Gap Lock的组合。锁定范围左开右闭的区间(上一个值, 当前值]。默认行为在RR可重复读隔离级别下InnoDB 默认使用 Next-Key Lock 来锁定扫描到的索引范围以防止幻读。示例数据为 1, 4, 7, 10。可能的 Next-Key 区间为(-∞, 1],(1, 4],(4, 7],(7, 10],(10, ∞)。实战SELECT * FROM t4 WHERE id 5 AND id 9 FOR UPDATE;锁定的范围锁定区间(4, 7]和(7, 10]。效果不仅锁住了记录7和10还锁住了(4,7)和(7,10)的间隙。所以你无法插入6也无法插入8或9。4. Insert Intention Lock插入意向锁定义一种特殊的间隙锁在INSERT操作之前设置。设计意图提高并发插入的性能。原理如果两个事务要在同一个间隙如(4,7)中插入数据但插入的位置不同例如插入5和6它们各自持有插入意向锁不需要互相等待可以并发插入除非间隙上有其他冲突的锁存在。六、不同SQL语句的加锁规则总结基于RR隔离级别根据MySQL官方文档的说明不同的SQL语句在加锁时有着严格的规则SQL操作锁类型说明普通SELECT无锁快照读MVCC。但在SERIALIZABLE级别下会加共享Next-Key锁。SELECT ... LOCK IN SHARE MODE共享锁S锁在扫描到的索引记录上加S锁通常为Next-Key Lock。SELECT ... FOR UPDATE排他锁X锁在扫描到的索引记录上加X锁通常为Next-Key Lock。UPDATE / DELETE排他锁X锁对扫描到的每条记录加X锁Next-Key Lock。INSERT排他锁X锁对插入的行加记录锁不是Next-Key之前加插入意向锁。唯一索引等值查询Record Lock记录锁如果记录存在只锁该记录不加间隙锁性能最佳。非唯一索引/范围查询Next-Key Lock锁定扫描到的区间和记录防止幻读。关键点如果你的查询没有走索引InnoDB 会进行全表扫描从而对表中的每一行都加上 Next-Key Lock阻塞全表的插入操作。这就是为什么频繁出现锁等待和死锁时优化 SQL 索引是第一要务。七、总结与避坑指南锁是索引在加不是行记录在加务必确保WHERE条件能用到索引否则行锁变表锁。RR级别下间隙锁虽好但容易锁住范围如果业务可以接受“读已提交”的隔离级别RC可以考虑将隔离级别设为READ-COMMITTED。在RC级别下间隙锁会被禁用除了外键约束和重复键检查从而减少锁冲突但需要注意区分“当前读”和“快照读”。锁等待与死锁锁等待事务A持有锁事务B等待。通常设置innodb_lock_wait_timeout超时参数。死锁两个事务互相持有对方需要的资源。InnoDB 会自动检测死锁并回滚持有较少行锁的事务即代价较小的事务。常用排查工具SHOW ENGINE INNODB STATUS;查看最近一次死锁信息。information_schema.INNODB_TRX、INNODB_LOCKS、INNODB_LOCK_WAITS三表联合查询。