MySQL 中的锁可以按照粒度分为锁定整个表的表级锁(table-level locking)和锁定数据行的行级锁(row-level locking)表级锁开销小,加锁快,但是支持的并发度低,行级锁相反。
行锁
Innodb实现了以下两种类型的行锁
-
共享锁(S):允许获得该锁的事务读取数据行(读锁),同时允许其他事务获得该数据行上的共享锁,并且阻止其他事务获得数据行上的排他锁。
-
排他锁(X):允许获得该锁的事务更新或删除数据行(写锁),同时阻止其他事务取得该数据行上的共享锁和排他锁。
select * from t where id = 1 for share;
在事务中使用select ... for share
语句获得了数据行 id = 1 上的共享锁
select * from t where id = 1 for update;
使用select ... for update
命令获取排他锁(常用)
InnoDB 通过给索引上的索引记录加锁的方式实现行级锁。具体来说,InnoDB 实现了三种行锁的算法:记录锁(Record Lock)、间隙锁(Gap Lock)和 Next-key 锁(Next-key Lock)又称临键锁。
1. 记录锁(Record Lock)
记录锁是锁定索引记录本身的一种锁。它仅锁定单个行,不会影响其他行或范围。
-
应用场景: 当一个事务需要修改或读取特定记录时,InnoDB会使用记录锁。
-
锁定范围: 锁住单行数据。
-
优点: 粒度细,有利于高并发。
START TRANSACTION;
SELECT * FROM employees WHERE id = 1 FOR UPDATE;
-- This statement locks the row where id = 1 for update.
在上述示例中,FOR UPDATE
子句会对满足条件的行加上记录锁,以防止其他事务修改该行。
2.间隙锁
间隙锁用于锁定两个索引记录之间的范围,防止其他事务在这个范围内插入新记录。间隙锁的主要目的是防止幻读现象。
-
应用场景: 当一个事务需要扫描一系列记录并确保该范围内没有其他插入操作时,会使用间隙锁。
-
锁定范围: 锁住两个记录之间的间隙,但不包括记录本身。
-
优点: 防止幻读,保证数据一致性。
START TRANSACTION;
SELECT * FROM employees WHERE age BETWEEN 30 AND 40 FOR UPDATE;
-- This statement locks the gap between rows satisfying the condition age BETWEEN 30 AND 40.
在上述示例中,FOR UPDATE
子句会锁住所有年龄在30到40岁之间的记录之间的间隙,防止其他事务在这些间隙中插入新记录。
3.Next-key锁(Next-key Lock)
第三种就是next-key锁,这个是是记录锁和间隙锁的组合,它锁定一个范围内的记录及其相邻的间隙。Next-key锁是InnoDB默认的锁定方式,用于防止幻读现象,确保数据一致性。
一帮来说范围不用间隙锁而用next-key锁,具体看mysql底层决定
应用场景: 当一个事务需要扫描和锁定某一范围内的所有记录和间隙时,InnoDB会使用Next-key锁。
锁定范围: 锁住记录及其相邻的间隙。
优点: 提供更强的并发控制,防止幻读。
假设 employees
表中有以下数据:
id | age----------1 | 252 | 303 | 354 | 405 | 45
在执行 SELECT * FROM employees WHERE age BETWEEN 30 AND 40 FOR UPDATE;
时,InnoDB会对满足条件的记录及其相邻的间隙进行加锁。
nex-tkey锁的锁定范围如下:
-
记录锁(Record Lock): 锁住
age = 30
、age = 35
和age = 40
的记录。 -
间隙锁(Gap Lock):
锁住以下间隙:
-
从
age = 25
到age = 30
的间隙。 -
从
age = 30
到age = 35
的间隙。 -
从
age = 40
到age = 45
的间隙。
-
表锁
-
InnoDB除了行级锁,还支持有MYSQL服务层实现的表级锁(LOCK TABLES ... WRITE在指定的表加上表级排他锁)
但是当这两种锁存在时,可能会发生冲突
例如,事务 A 获取了表中一行数据的读锁;然后事务 B 申请该表的写锁(例如修改表的结构)。如果事务 B 加锁成功,那么它就应该能修改表中的任意数据行,但是 A 持有的行锁不允许修改锁定的数据行。显然数据库需要避免这种问题,B 的加锁申请需要等待 A 释放行锁。
那么如何判断事务 B 是否应该获取表级锁呢?首先需要看该表是否已经被其他事务加上了表级锁,然后依次查看该表中的每一行是否已经被其他事务加上了行级锁。这种方式需要遍历整个表中的记录,效率很低。为此,InnoDB 引入了另外一种锁:意向锁
意向锁属于表级锁,由 InnoDB 自动添加,不需要用户干预。意向锁也分为共享和排他两种方式:
-
意向共享锁(IS):事务在给数据行加行级共享锁之前,必须先取得该表的 IS 锁。
-
意向排他锁(IX):事务在给数据行加行级排他锁之前,必须先取得该表的 IX 锁。
此时,事务 A 必须先申请该表的意向共享锁,成功后再申请数据行的行锁。事务 B 申请表锁时,数据库查看该表是否已经被其他事务加上了表级锁;如果发现该表上存在意向共享锁,说明表中某些数据行上存在共享锁,事务 B 申请的写锁会被阻塞。
因此,意向锁是为了使得行锁和表锁能够共存,从而实现多粒度的锁机制。以下是表级锁和表级意向锁的兼容性:
-
(意向锁跟意向锁是兼容的,共享锁跟共享锁也是兼容的,其他不兼容)