MySQL锁机制八股整理
整理一下MySQL锁的八股,全文整理自二哥博客。
在MySQL数据库中,锁是用来协调多个进程或线程并发访问同一资源的机制。锁不仅保证了数据的一致性和有效性,而且是影响数据库并发访问性能的一个重要因素。

共享锁与排他锁
共享锁也叫 S(shared) 锁,允许多个事务进行读操作,阻塞写操作。
排他锁也叫 X(exclusive) 锁,只允许一个事务进行读写操作,阻塞其他事务的读写操作。
兼容性如下:

表锁与行锁
表锁:锁定整个表,资源开销小,加锁快,但并发度低,不会出现死锁,适合查询为主、少量更新的场景(如 MyISAM 引擎)。可以细分为表级S锁、表级X锁。
行锁:锁定单行或多行,开销大、加锁慢,可能出现死锁,但并发度高(InnoDB 默认支持)。可以细分为记录锁、间隙锁、临键锁,也可以分为共享锁和排他锁(与表级S锁、表级X锁一个意思,前提是行锁)。
表锁详细版:
表锁常见于 MyISAM 引擎, InnoDB 也可手动加锁,适合读多写少、全表扫描或者表结构变更的场景。
1 | LOCK TABLES table_name READ; -- 显式加读锁 |
MyISAM 在执行 SELECT 时会自动加读锁,执行 INSERT/UPDATE/DELETE 时会加写锁。
对于 InnoDB 引擎,无索引的 UPDATE/DELETE 可能会导致锁升级为表锁。执行 ALTER TABLE 时会自动加表锁,阻塞所有读写操作。
行锁详细版:
行锁是 InnoDB 存储引擎中最细粒度的锁,它锁定表中的一行记录,允许其他事务访问表中的其他行。
底层是通过给索引加锁实现的,这就意味着只有通过索引条件检索数据时,InnoDB 才能使用行级锁,否则会退化为表锁。
默认情况下,InnoDB 在 REPEATABLE READ 事务隔离级别运行,默认的行锁类型为临键锁。

如果使用排他锁,注意两个点:第一就是必须在事务中使用,否则锁会立即释放。第二就是使用时必须注意是否命中索引,否则可能退化为表锁。
行锁又可以细分为记录锁、间隙锁和临键锁三种形式。
记录锁是行锁最基本的表现形式,当我们使用唯一索引或者主键索引进行等值查询时,MySQL 会为该记录自动添加排他锁,禁止其他事务读取或者修改锁定记录。(很奇怪)
间隙锁用于在范围查询时锁定记录之间的“间隙”,防止其他事务在该范围内插入新记录。仅在可重复读及以上的隔离级别下生效,主要用于防止幻读。

临键锁是记录锁和间隙锁的结合体,锁住的是索引记录和索引记录之间的间隙。临键锁的间隙是一个左开右闭区间。间隙锁为左开右开。
MySQL 默认的行锁类型就是临键锁。当使用唯一索引的等值查询匹配到一条记录时,临键锁会退化成记录锁;如果没有匹配到任何记录,会退化成间隙锁。
意向锁
意向锁是一种表级锁,表示事务打算对表中的某些行数据加锁,但不会直接锁定数据行本身。
由 InnoDB 自动管理,当事务需要添加行锁时,会先在表上添加意向锁。这样当要添加表锁的时候,可以通过查看表上的意向锁,快速判断是否有冲突,而无需逐行检查,从而提高加锁效率。
存在的意义:在没有意向锁的情况下,当事务 A 持有某表的行锁时,如果事务 B 想添加表锁,InnoDB 必须检查表中每一行数据是否被加锁,这种全表扫描的方式效率极低。有了意向锁之后,事务在加行锁前,先在表上加对应的意向锁;其他事务加表锁时,只需检查表上的意向锁,无需逐行检查。
意向锁之间相互兼容

乐观锁与悲观锁
悲观锁是一种"先上锁再操作"的保守策略,它假设数据被外界访问时必然会产生冲突,因此在数据处理过程中全程加锁,保证同一时间只有一个线程可以访问数据。MySQL 中的行锁和表锁都是悲观锁。
乐观锁会假设并发操作不会总发生冲突,属于小概率事件,因此不会在读取数据时加锁,而是在提交更新时才检查数据是否被其他事务修改过。它并不是 MySQL 内置的锁机制,而是通过程序逻辑实现的,常见的实现方式有版本号机制和时间戳机制,对应的向表中添加 version 字段或者 timestamp 字段来实现。
死锁问题
MySQL 的死锁是由于多个事务持有资源并相互等待引起的。我通过 SHOW ENGINE INNODB STATUS 查看死锁信息,定位到是加锁顺序不一致导致的,最后通过调整加锁顺序解决了这个问题。
补充:
全局锁就是对整个数据库实例进行加锁,当执行全局锁定操作时,整个数据库将会处于只读状态,所有写操作都会被阻塞,直到全局锁被释放。
在进行全库备份,或者数据迁移时,可以使用全局锁来保证数据的一致性。