首页 > 数据库 > 其他综合 >

innodb的锁讲解

2019-02-27

innodb的锁讲解

innodb的锁,我们可以从几个维度来分析,分为级别,类型

级别

行级锁

表级锁

类型

共享锁(S),也称为写锁, 级别:行级锁

意向共享锁(IS),也称为意向写锁 级别:表级锁

排他锁(X),也称为读锁 属于行级锁 级别: 行级锁

意向排他锁(IX),也称为意向读锁 级别: 表级锁

行锁的算法

Record Locks

Gap Locks

Next-Key Locks

Insert intention Locks

AUTO-INC Locks

Predicate Locks for Spatial Indexes

锁的兼容性请看下图

innodb的锁讲解

我们就先从类型讲起

共享锁

允许持有锁的事务读取行

假如有一个a事务获取了x行的共享锁,这时候b事务也来请求x行的锁,那么会进行一下处理

如果b请求的是x行的共享锁,那么会立刻授予,这时候a事务和b事务都拥有x行的共享锁

如果b请求的是x的排他锁,那么不会立刻授予,因为共享锁和排他锁是不兼容的

排他锁

允许持有锁的事务更新或删除行。

假如有一个a事务获取了x行的排他锁,这时候b事务也来请求x行的锁,这时候不管b事务请求的锁是共享锁还是排他锁,都不能立即授予,只能等到a事务释放了在x行的排他锁才能授予b事务,因为排他锁与任何的锁都不兼容

意向锁的讲述

innodb支持多粒度锁定,这种锁定允许事务在行级上的锁和表级上的锁同行存在,为了支持在不同粒度上进行操作,innodb存储引擎支持一个额外的锁方式,称之为意向锁,意向锁是将锁定的对象分为多个层次,意向锁意味着事务希望在更细粒度上进行加锁,也就是如果一个事务需要针对记录R来加排他锁,那么就需要对记录R所在的数据库,表,页进行加锁,最后对记录R加排他锁,如果有任何一个部分导致等待,那么该操作需要粗粒度锁的完成。

意向锁是表级锁,设计目的主要是为了在下一个事务中揭示下一行将被请求的锁类型,由于innodb存储引擎支持的是行级别的锁,因此意向锁其实不会阻塞除全表扫描以外的任何请求。

上面的可能不好理解,下面我说一个实际的例子

我们有一个student表

mysql> show create table student;

+---------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

| Table | Create Table |

+---------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

| student | CREATE TABLE student (

id int(11) NOT NULL AUTO_INCREMENT,

student_num int(11) NOT NULL DEFAULT '0' COMMENT '学号',

name varchar(32) NOT NULL DEFAULT '' COMMENT '学生姓名',

PRIMARY KEY (id),

UNIQUE KEY uqidx_student_num (student_num)

) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 |

+---------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

1 row in set (0.00 sec)

mysql> select * from student;

+----+-------------+----------+

| id | student_num | name |

+----+-------------+----------+

| 1 | 1 | zhangsan |

| 2 | 2 | lisi |

| 3 | 3 | wangwu |

| 4 | 4 | zhaoliu |

| 5 | 5 | liqi |

+----+-------------+----------+

5 rows in set (0.00 sec)

session1

mysql> start transaction;

Query OK, 0 rows affected (0.00 sec)

mysql> select * from student where student_num=4 for update;

+----+-------------+---------+

| id | student_num | name |

+----+-------------+---------+

| 4 | 4 | zhaoliu |

+----+-------------+---------+

1 row in set (0.00 sec)

session2

mysql> LOCK TABLE student write;

这时候我们发现session2一直在等待,因为session2想获取student整个表的写锁,如果session2申请成功了, 它是可以修改student表的任意一行的,那么大家会说session1已经获取了student_num=4的排他锁呢,如果session2申请成功了,那么student_num=4的记录也会被session2修改了,所以说这时候session2肯定是会阻塞的,那么数据库是根据什么方法来判断使得session2被阻塞呢,无非就下面两种方法

判断这个表是否被其他事务用表锁给锁住

判断这个表的每一行是否有行锁

根据前面的讲述,假如我们要针对某一条记录加排他锁的话,那么会在记录对应的表里面先加一个共享排他锁,然后再到记录上面加一个排他锁,这时候我们可以发现session1可以针对student表加了一个共享排他锁了,那么这时候session2发现student上面已经有其他事务加上共享排他锁了,因此会阻塞。如果没有意向锁的话,那session2就要走方法2了,那么就需要判断每一行,那么需要遍历整个表,这种效率非常差,特别碰到表数据量大的时候。

意向共享锁

事务想要获取一个表中某几行的共享锁

意向排他锁

事务想要获取一个表中某几行的排他锁

请注意:对于insert、update、delete,InnoDB会自动给涉及的数据加排他锁(X);对于一般的 Select 语句,InnoDB 不会加任何锁,可能有人会有疑问,什么是一般的select,什么是特殊的select呢,一般的select就是select column from table where x=1 特殊的select就是select column from table where x=1 lock in share mode或者select column from table where x=1 for udpate ,前面加共享锁,后者加排他锁

行锁

Record Locks

记录锁是索引记录上的锁,例如SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE;防止任何其他事务插入,更新或删除t.c1的值为10的行

记录锁始终锁定索引记录,如果一个表没有定义任何的索引,像这种情况,innodb会创建一个隐藏的聚簇索引并且使用此索引进行记录锁定

需要注意的是:

innodb的记录锁是针对索引加锁,不是针对物理记录加锁,所以虽然是访问不同行的记录,但是如果是使用相同的索引键,将出现锁冲突

Gap Locks

间隙锁是锁定索引记录之间的间隙,或锁定第一个索引记录之前或最后一个索引记录之后的间隙上。。例如 SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE;由于范围内所有现有值之间的间隔都被锁定,因此可以防止其他事务向列t.c1中插入值15,无论该列中是否已有任何此类值。

对于使用唯一索引锁定行的语句,不需要使用间隙锁(这不包括搜索条件仅包括多列唯一索引的一些列的情况; 在这种情况下,确实会出现间隙锁定),例如,如果id列是一个唯一索引,那么下面的语句只会在id=100的那行上面加一个索引记录锁,而不会关心别的会话(session)是否在前面的间隙中插入数据。

SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE;

如果id没有索引或者没有唯一索引,则该语句将锁定前述的间隙

间隙锁的主要作用,就是和记录锁组成Next-key锁,解决幻读问题

间隙锁在不同的隔离级别下,有着不同的作用范围,能发挥间隙锁作用的,是’REPEATTABLE READ’隔离级别,在这个级别下使用带有间隙锁的Next-Key锁,解决了幻行的问题。这个涉及到了事务隔离级别和一致读的相关信息,后面我也会更新对应的文章

Next-Key Locks

next-key锁是索引记录上的记录锁和索引记录之前的间隙上的间隙锁的组合,也就是相当于Record Locks+Gap Locks

InnoDB以这种形式实现行级锁,当它查找或扫描表索引的时候,它会在遇到的索引记录上设置共享或排它锁。因此,行级锁实际上是索引记录锁。next-key锁同样会影响索引记录之前的间隙。就是说,next-key 锁就是一个索引记录锁加上索引记录前间隙的间隙锁。如果一个会话拥有记录 R 的索引上面的一个共享锁或独占锁,另一个会话不能在索引顺序中的R之前的间隙中插入新的索引记录。

下面我们来看一个表

mysql> create table goods(

-> id int not null auto_increment primary key,

-> title varchar(32) not null default '' comment '商品名称',

-> classify tinyint not null default 0 comment '商品类型',

-> index idx_classify (classify)

-> )engine=innodb charset=utf8;

mysql> insert into goods (title,classify) values ('商品1',1),('商品2',3),('商品3',5),('商品4',8),('商品5',10),('商品6',1),('商品7',3),('商品8',5),('商品9',8),('商品10',10);

mysql> select * from goods;

+----+----------+----------+

| id | title | classify |

+----+----------+----------+

| 1 | 商品1 | 1 |

| 2 | 商品2 | 3 |

| 3 | 商品3 | 5 |

| 4 | 商品4 | 8 |

| 5 | 商品5 | 10 |

| 6 | 商品6 | 1 |

| 7 | 商品7 | 3 |

| 8 | 商品8 | 5 |

| 9 | 商品9 | 8 |

| 10 | 商品10 | 10 |

+----+----------+----------+

10 rows in set (0.00 sec)

mysql> select distinct(classify) from goods;

+----------+

| classify |

+----------+

| 1 |

| 3 |

| 5 |

| 8 |

| 10 |

+----------+

5 rows in set (0.02 sec)

#在REPEATTABLE READ隔离级别下,执行查询时,因为Next-Key锁存在,写Next-Key的锁定范围如下

(-∞,1) 锁定索引项1和1之前的间隙,因为1之前没有其他索引项,所以负无穷

(1,3) 锁定1和3之前的间隙,不包括1,包括3

(3,5) 同上

(5,8) 同上

(8,10) 同上

(10,∞) 锁定索引项10和10之后的间隙,因为10之后没有其他索引项,所以为正无穷

下面我们就用一个demo来验证一下

Session1

mysql> start transaction;

Query OK, 0 rows affected (0.00 sec)

mysql> select * from goods where classify=3 for update;

+----+---------+----------+

| id | title | classify |

+----+---------+----------+

| 2 | 商品2 | 3 |

| 7 | 商品7 | 3 |

+----+---------+----------+

2 rows in set (0.00 sec)

Session2

mysql> start transaction;

Query OK, 0 rows affected (0.00 sec)

mysql> insert into goods (title,classify) select '商品11',4;

#这时候可以看到session2给阻塞了,因为session1的Next-Key的锁定范围是(3,5),正好包含了4

mysql> insert into goods (title,classify) select '商品11',6;

Query OK, 1 row affected (0.00 sec)

Records: 1 Duplicates: 0 Warnings: 0

插入classify=6的就立刻成功了, 因为6不在(3,5)的范围内

Insert intention Locks

Insert intention 锁是插入行之前由INSERT操作设置的一种间隙锁。。此锁表示要插入的意图是多个事务插入到相同的索引间隙时, 如果它们没有插入到间隙中的同一位置, 则不必等待对方。。假设存在值为4和7的索引记录。两个事务分别尝试插入5和6,分别用插入意向锁锁住4和7之间的间隙,然后再取得插入行的排它锁,但是相互不会阻塞,因为这些行是不冲突的。

AUTO-INC Locks

AUTO-INC锁是由插入到带有 AUTO_INCREMENT 列的表中的事务所采取的特殊表级锁。。一个最简单的例子,如果一个事务正在向表中插入值,则任何其他事务必须等待对该表执行自己的插入,以便第一个事务插入的行接收连续的主键值。

Predicate Locks for Spatial Indexes

innodb支持空间索引,如果使用Next-Key来支持空间索引,则不能满足要求,这是因为普通的索引都是键值类型,意味着索引存在一个方向,这个方向是单向的,要不升序要不降序,这个方向的存在,使得数据库存储引擎可以利用索引进行常规的范围查询

但是在空间数据类型上面,这个单向的有序变得失去了作用,因为空间数据是多维多向的,是以区域或空间为范围的,没有确定的方向顺序,所以单向的Next-Key满足不了要求

空间索引是建立爱MBR上的,innodb为索引项的MBR增加了一个谓词锁,实现空间索引上的并发控制

相关文章
最新文章
热点推荐