-
页级别:记录锁
记录锁也就是仅仅把一条记录锁上,官方的类型名称为: LOCK_REC_NOT_GAP 。比如我们把id值为8的那条记录加一个记录锁的示意图如图所示。
仅仅是锁住了id值为8的记录,对周围的数据没有影响。
- 结论
记录锁是有S锁和X锁之分的,称之为 S型记录锁 和 X型记录锁 。
当一个事务获取了一条记录的S型记录锁后,其他事务也可以继续获取该记录的S型记录锁,但不可以继续获取X型记录锁;
当一个事务获取了一条记录的X型记录锁后,其他事务既不可以继续获取该记录的S型记录锁,也不可以继续获取X型记录锁
- 案例
# 案例1
# 开启第1个连接
# 开启1个事务
begin;
# 相当于为这1行开启1个排他锁
update student set name = "chen" where id = 1;
# 开启第2个连接
# 开启第2个事务
begin;
# 对其他行开启共享锁,开启成功
select * from student where id = 3 lock in share mode;
# 对第1个连接中的那一行开启共享锁,开启失败,因为不兼容
select * from student where id = 1 lock in share mode;
# 对其他行开启排他锁,开启成功
update student set name = "li" where id = 3;
# 对同一行开启排他锁,开启失败
update student set name = "xing" where id = 1;
# 当第1个连接中释放排他锁,关闭事务后
commit;
# 第2个连接中对这1行开启排他锁,开启成功
update student set name = "xing" where id = 1;
-
页级锁:间隙锁(Gap Locks)
:指定2行中间不允许插入数据
MySQL 在 REPEATABLE READ 隔离级别下是可以解决幻读问题的,解决方案有两种,可以使用 MVCC 方案解决,也可以采用 加锁 方案解决。但是在使用加锁方案解决时有个大问题,
就是事务在第一次执行读取操作时,那些幻影记录尚不存在,我们无法给这些 幻影记录 加上 记录锁 。InnoDB提出了一种称之为Gap Locks 的锁,官方的类型名称为: LOCK_GAP
- 案例
# 案例1
# 开启1个新的连接,开启1个事务
begin;
# 为某个不存在的行添加1个共享锁
select * from student where id= 5 lock in share mode;
# 开启第2个新的连接,开启1个事务
begin;
# 为同一行添加1个排他锁,按道理说不能添加成功,针对同一行记录添加共享锁和排他锁是不兼容的
# 但如果这一行记录是不存在的,则可以同时添加共享锁和排他锁
# 这一行不存在的记录存在与某2行记录的中间,这时添加的这个锁也成为间隙锁
# 添加1个排他锁,添加成功
select * from student where id = 5 for update;
# 在间隙锁还未释放之前,向这个间隙插入数据,会插入失败,会处于阻塞状态,这就是间隙锁的作用
insert into student(id, name, class) value(6, "gou", "man");
# 案例2
# 开启1个新的连接,开启1个事务
begin;
# 由于最大的数是20,如果在20到无穷大的这个区间开启1个间隙锁,则20到无穷大这个区间将被间隙锁锁定,不允许插入数据
select * from student where id = 25 lock in share mode;
# 开启第2个连接,开启1个事务
begin;
# 向20到无穷大的区间插入数据,插入失败
insert into student(id, name, class) value(21, "gou", "man");
insert into student(id, name, class) value(26, "gou", "man");
# 向20之前的区间插入数据,插入成功
insert into student(id, name, class) value(17, "gou", "man");
-
间隙锁导致死锁
# 开启第1个新的连接,开启1个事务
begin;
# 开启1个间隙锁
select * from student where id = 5 lock in share mode;
# 开启第2个连接,开启1个新的事务
begin;
# 开启1个相同的间隙锁
select * from student where id = 5 for update;
# 第2个连接中向间隙中插入1条记录,会处于阻塞状态
insert into student(id, name, class) value(7, "aaa", "giao");
# 切换到第1个连接的页面,向间隙中再次插入1条记录,这时会直接报死锁的错误
insert into student(id, name, class) value(6, "bbb", "giao");
# 死锁的原因:第1个间隙锁不允许第2个连接中的插入操作执行,第2个间隙锁不允许第1个连接中的插入操作执行
# 要想第1个连接中的插入操作执行,第2个连接释放锁,但第2个连接中有1条插入语句处于阻塞状态,不能结束
# 要想第2个连接中的插入操作执行,第1个连接释放锁,但第1个连接中有1条插入语句处于阻塞状态,不能结束
# 最后第1个连接中的插入语句报死锁错误,第2个连接中的插入语句则插入成功了
# 第2个连接中插入语句执行成功的原因:发生死锁时,mysql的处理死锁的1中策略是将某1个处于僵持状态的事务进行回滚,选择成本更低的1个事务执行
# 因此这里回滚了第1个连接中的事务,第2个连接没有阻塞则执行成功了