目录
1、InnoDB中的行锁
行锁 (Row Lok)也称为记录锁,顾名思义,就是锁住某一行(某条记录 row) 。需要的注意的是,MySOL 服务器层并没有实现行锁机制,行级锁只在存储引擎层实现
优点: 锁定力度小,发生 锁冲突概率低,可以实现的 并发度高。
缺点: 对于 锁的开销比较大,加锁会比较慢,容易出现 死锁 情况。
InnoDB与MyISAM的最大不同有两点: 一是支持事务 (TRANSACTION);二是采用了行级锁.。
首先我们创建表如下:
CREATE TABLE student(
id INT,
NAME VARCHAR(20),
class VARCHAR(10),
PRIMARY KEY (id)
)ENGINE =INNODB CHARSET=utf8;
INSERT INTO student VALUES
(1,'张三','一班'),
(3,'李四','一班'),
(8,'王五','二班'),
(15,'赵六','二班'),(20,'钱七','二班');
SELECT * FROM student;
1.1、记录锁(Record Locks)
记录锁也就是仅仅把一条记录锁上,官方的类型名称为: LOCK_REC_NOT_GAP 。比如我们把id值为8的 那条记录加一个记录锁的示意图如图所示。仅仅是锁住了id值为8的记录,对周围的数据没有影响。
举例如下:
根据student表 我们进行如下操作:
# set autocommit=0 ,可以使用begin commit 组合实现事务
#在获取行X锁后 其他事务 或取行 S 锁 一样会阻塞
select * from student where id =1 Lock in share mode;
或者
select * from studet where id =1 for share;
已获得X锁的情况下,其他事务获取S锁,一样会阻塞
记录锁是有S锁和X锁之分的,称之为 S型记录锁 和 X型记录锁 。
当一个事务获取了一条记录的S型记录锁后,其他事务也可以继续获取该记录的S型记录锁,但不可 以继续获取X型记录锁;
当一个事务获取了一条记录的X型记录锁后,其他事务既不可以继续获取该记录的S型记录锁,也不 可以继续获取X型记录锁。
1.2、间隙锁(Gap Locks)
MySQL 在 REPEATABLE READ 隔离级别下是可以解决幻读问题的,解决方案有两种,可以使用 MVCC 方 案解决,也可以采用 加锁 方案解决。
但是在使用加锁方案解决时有个大问题,就是事务在第一次执行读 取操作时,那些幻影记录尚不存在,我们无法给这些 幻影记录 加上 记录锁 。InnoDB提出了一种称之为 Gap Locks 的锁,官方的类型名称为: LOCK_GAP ,我们可以简称为 gap锁 。比如,把id值为8的那条 记录加一个gap锁的示意图如下。
图中id值为8的记录加了gap锁,意味着 不允许别的事务在id值为8的记录前边的间隙插入新记录 ,其实就是 id列的值(3, 8)这个区间的新记录是不允许立即插入的。比如,有另外一个事务再想插入一条id值为4的新 记录,它定位到该条新记录的下一条记录的id值为8,而这条记录上又有一个gap锁,所以就会阻塞插入 操作,直到拥有这个gap锁的事务提交了之后,id列的值在区间(3, 8)中的新记录才可以被插入。
gap锁的提出仅仅是为了防止插入幻影记录而提出的。
间隙锁 X锁 和S 锁 是没有区别的,下面两种写法都可以
SELECT * from student where id =5 lock inshare mode;
SELECT * from student where id =5 for update;
间隙锁 是给一个存在的开区间内的 不存在的数据进行上锁, 比如 上面student表
(1,3) (3,8), (8,15)
上面给5加间隙锁,其实 是给 (3,8)内的所有记录进行加锁, 比如 主键为 4,5,6,7 如果进行 insert 都会被阻塞
INSERT INTO student (id,name,class) values(6,'tom','三班');
此语句将会被阻塞
另外注意:
间隙锁之间是不冲突的
这里 session 2 并不会被堵住。因为表里并没有 id=5 这个记录,因此 session 1加的是间锁 (3,8)。而 session 2也是在这个间隙加的间隙锁。它们有共同的目标,即: 保护这个间隙,不允许插入值。但,它们之间是不冲突的。
注意,给一条记录加了 gap锁 只是 不允许 其他事务往这条记录前边的间隙 插入新记录,那对于最后一条记录之后的间隙,也就是student 表中id值为 20的记录之后的间隙该咋办呢?也就是说给哪条记录加 gap锁 才能阻止其他事务插入 id 值在(20,+∞)这个区间的新记录呢?这时候我们在讲数据页时介绍的两条伪记录派上用场了:
- Infimum 记录,表示该页面中最小的记录。
- supremum 记录,表示该页面中最大的记录。
为了实现阻止其他事务插入id值在(20,+∞)这个区间的新记录,我们可以给索引中的最后一条记录,也就是id值为20的那条记录所在页面的Supremum记录加上一个gap锁,如图所示。
select * from student where id=20 for update;
检测:(在加间隙锁的事务中查询)
SELECT * FROM performance_schema.data_locks\G;
注意: 间隙锁会发生死锁的情况
注意:死锁在mysql中有自己的处理策略,从上面的结果我们能看出,session 1 的 insert 语句是执行成功的,
死锁的执行策略有很多方式 比如 在redis构建分布式锁的时候,可以设置超时时间,一旦 EX时间到了,自动释放NX 锁,在Mysql中的死锁执行策略,我们后面再叙述。
1.3、临键锁
有时候我们既想 锁住某条记录 ,又想 阻止 其他事务在该记录前边的 间隙插入新记录 ,所以InnoDB就提 出了一种称之为 Next-Key Locks 的锁,官方的类型名称为: LOCK_ORDINARY ,我们也可以简称为 next-key锁 。Next-Key Locks是在存储引擎 innodb 、事务级别在 可重复读 的情况下使用的数据库锁, innodb默认的锁就是Next-Key locks。
比如,我们把id值为8的那条记录加上一个next-key 锁的示意图如下:
next-key锁 的本质就是一个 记录锁 和一个 gap锁 的合体,它既能保护该条记录,又能阻止别的事务将新记录插入被保护记录前边的 间隙。
begin;
select * from student where id <=8 and id > 3 for update;
此事务 锁定了 8 (X锁) 以及 (3,8)之间的间隙
会话1 (左) 加入 临键锁 ,会话2 给id=8的记录获取 行锁 s锁 阻塞(写——读)
会话2 id=8的记录 获取 x锁 阻塞
会话2 想插入 id =6 的数据 阻塞
1.4 插入意向锁
我们说一个事务在 插入 一条记录时需要判断一下插入位置是不是被别的事务加了 gap锁 ( next-key锁 也包含 gap锁 ),如果有的话,插入操作需要等待,直到拥有 gap锁 的那个事务提交。
但是InnoDB规 定事务在等待的时候也需要在内存中生成一个锁结构,表明有事务想在某个 间隙 中 插入 新记录,但是 现在在等待。InnoDB就把这种类型的锁命名为 Insert Intention Locks ,官方的类型名称为: LOCK_INSERT_INTENTION ,我们称为 插入意向锁 。插入意向锁是一种 Gap锁 ,不是意向锁,在insert 操作时产生。
插入意向锁是在插入一条记录行前,由 NSERT 操作产生的一种间隙锁。该锁用以表示插入意向,当多个事务在同一区间(gap)插入位置不同的多条数据时,事务之间不需要互相等待。假设存在两条值分别为 4和7的记录,两个不同的事务分别试图插入值为 5 和 6 的两条记录,每个事务在获取插入行上独占的(排他)锁前,都会获取(4,7)之间的间隙锁,但是因为数据行之间并 不冲突(比如都插入id为5 就是冲突了),所以两个事务之间并不会产生冲突(阻塞等待)。总结来说,插入意向锁的特性可以分成两部分:
(1) 插入意向锁是一种 特殊的间隙锁 -- 间隙锁可以锁定开区间内的部分记录。
(2)插入意向锁之间 互不排斥,所以即使多个事务在同一区间插入多条记录,只要记录本身 (主键、唯一索引)不冲突,那么事务之间就不会出现冲突等待。
注意,虽然插入意向锁中含有意向锁三个字,但是它并不属于意向锁而属于间隙锁,因为意向锁是表锁而插入意向锁是 行锁。
比如,把id值为8的那条记录加一个插入意向锁的示意图如下:
事实上插入意向锁并不会阻止别的事务继续获取该记录上任何类型的锁。
从图中可以看到,由于T1持有gap锁,所以T2和T3需要生成一个插入意向锁的锁结构并且处于等待状态。当T1提交后会把它获取到的锁都释放掉,这样T2和T3就能获取到对应的插入意向锁了(本质上就是把插入意向锁对应锁结构的is_waiting属性改为false),T2和T3之间也并不会相互阻塞,它们可以同时获取到id值为8的插入意向锁,然后执行插入操作。事实上插入意向锁并不会阻止别的事务继续获取该记录上任何类型的锁。
#注意 t2 和 t3 的插入语句 不能插入有冲突的数据。