MySQL-InnoDB行锁

InnoDB的锁类型

InnoDB存储引擎支持行锁,锁类型有两种:

  • 共享锁(S锁)
  • 排他锁(X锁)

S和S不互斥,其他均互斥。

除了这两种锁以外,innodb还支持一种锁,叫做意向锁

那么什么是意向锁?为什么需要意向锁呢?

考虑这种情况:
SessionA:已经持有表t某一行的X锁,需要对行进行更新操作
SessionB:想申请表t的表锁写锁

在没有意向锁之前,SessionA已经持有了行X锁以后,如果SessionB也成功,意味着SessionB可以对这个表中的所有数据进行更新操作,与SessionA的行锁是冲突的。

所以数据库在同意SessionB的申请之前,会做以下的判断:

  1. 检查是否有其他的表级写锁存在
  2. 判断表中每一行是否有行级X锁存在

步骤2的判断本身效率不高。所以产生了意向锁,在申请行级锁之前,先申请意向锁,成功之后才能申请行锁。(简单理解为多保存一个变量,便于申请表锁的时候进行快速判断)。

意向锁分为两种:

  • 意向共享锁(IS锁)
  • 意向排他锁(iX锁)

IS锁和IX锁之间彼此都不互斥,IS和IX只和表级别的读写锁互斥。其中:

  • IX和表X和表S互斥
  • IS和表X互斥
一致性锁定读和一致性非锁定读
一致性非锁定读

当隔离类型设置为读已提交和可重复读时,我们写的普通的select语句,不会申请锁,也不会被阻塞,会从MVCC选择一个视图读取数据。
两种隔离状态的区别在于:

  • 读已提交永远拿到的是最新的视图
  • 可重复读永远使用事务刚建立时拿到的视图
一致性锁定读

当写这两种select语句时,会进行加锁操作。

  • select ... lock in share mode
    • 这个语句表示加入一个S锁,其他事务也可以加S锁
    • 使用场景:不同表之间的相同数据保持一致性时使用(存疑,暂时放着)
  • select ... for update
    • 这个语句表示加入一个X锁,其他事务不能读也不能写
    • 使用场景:先读,根据读结果进行修改的操作时可以使用
行锁的三种算法(判断锁定的范围)

InnoDB存储引擎有三种行锁的锁定算法:

  • record lock:锁定单行
  • gap lock:锁定范围,不包含记录本身
  • next-key lock:gap lock + record lock 既锁定范围又锁定记录本身

当select操作需要加锁时,会按照上面的next-key lock进行加锁。

  • 当查询条件中有索引,并且是唯一索引时,可以只锁定单条记录,由next-key lock降级为 record lock。
  • 当查询条件中有索引,并且索引是辅助索引时,不仅锁住记录本身,还会锁定记录前一个索引键值范围(不包括区间头部的值),除此之外,还会锁定记录值到下一个索引键值的范围(不包括区间尾端的值);同时还会在主键索引的对应行上加record lock
实验验证

输入下列建表语句:

create table z(a int,b int,primary key(a),key(b));
insert into z select 1,1;
insert into z select 3,1;
insert into z select 5,3;
insert into z select 7,6;
insert into z select 10,8;

在sessionA中,输入下面查询语句

select * from z where b=3 for update;

在sessionB中,输入下面查询语句

select * from z where b=1 for update;
select * from z where b=3 for update;
insert into z (a,b) values(4,2);
select * from z where b=6 for update;

其中,第一条和第四条可以正常返回,第二条第三条语句会阻塞,因为SessionA一直没有commit,所以这两条语句阻塞一段时间后会报锁等待超时异常。

mysql> select * from z where b=1 for update;
+---+------+
| a | b    |
+---+------+
| 1 |    1 |
| 3 |    1 |
+---+------+
2 rows in set (0.00 sec)

mysql> select * from z where b=6 for update;
+---+------+
| a | b    |
+---+------+
| 7 |    6 |
+---+------+
1 row in set (0.00 sec)

mysql> select * from z where b=3 for update;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into z (a,b) values(4,2);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

结果分析
查询条件为b=3,所以会使用b列的辅助索引来查询,找到记录3后

  • 使用record lock锁定主键索引a=5的记录
  • 使用next-key lock锁定辅助索引(1,3)3 (3,6)这个范围的记录

在SessionA提交事务之前,这个范围内的记录都加的是X排他锁,所以第二条和第三条记录都需要阻塞等待。

原文地址:https://www.cnblogs.com/ging/p/13467825.html