MySQL innodb的锁机制解读

网上有许多关于innodb的锁机制的文章,有许多文章讲述的不明白或者有问题,最近研究了很久,结合网上资料和实践操做,记录一下,供你们参考。若是有不对的地方,请随时留言。数据库

一 Innodb具有的锁种类spa

1. 表锁(MySQL提供的,跟存储引擎无关)索引

2. 行锁(Innodb存储引擎实现)事务

二 Innodb内部实现的锁种类io

1. 记录锁innodb

对应Innodb的行锁,记录锁锁的是索引记录,不是具体的数据记录。table

2. 间隙锁date

锁定索引记录间隙的锁,确保索引记录的间隙不变,间隙锁是针对事务隔离等级是可重复读或以上级别而言的!im

例如: create table t1(id int, v1 int,v2 int, primary key(id), key `idx_v1` (`v1`)) engine=innodb;数据

数据行有如下几行:

id v1 v2
1 1 0
2 3 1
3 4 2
5 5 3
7 7 4
10 9 5

 

 

 

 

 

 

间隙锁通常是针对非惟一索引而言的

上面的数据表中v1的索引区间有

(-∞,1)

(1,3)

(3,4)

(4,5)

(5,7)

(7,9)

(9,+∞)

假如更新v1=5的数据行,那么此时会在索引记录idx_v1加上间隙锁,会把5以前的区间锁定,锁定的区间是(4,5)和(5,7),也就是区间(4,7),同时找到v1=5的数据行的主键,在该记录上加上记录锁,锁定该行记录。

 

3. 后码锁

记录锁和间隙锁的结合,对于innodb中,更新非惟一索引记录时,会加上后码锁。若是更新记录为空,就不能加记录锁,此时只剩下间隙锁。多条更新语句可能致使不一样事务中锁定的索引区间重复,致使插入失败。

例子(事务隔离等级为可重复读,主要看一下后码锁是记录锁和间隙锁的结合)

transaction 1 transaction 2
BEGIN; BEGIN;
update t1 set v2=1 where v1=6;(这句SQL会加上后码锁,可是v1=6的记录不存在,后码锁是记录锁和间隙锁组成的,此时只能加上间隙锁,会锁住idx_v1的索引区间是(5,7)) update t1 set v2=2 where v1=7;(这句SQL也是加上后码锁,v1=7的记录存在,后码锁是由记录锁和区间锁组成,首先会使用记录锁,锁定v1=7的主键,即id=7的记录行,同时会使用间隙锁,会锁住idx_v1的索引区间是(5,9))
INSERT INTO t1 set id=8, v1=6,v2=2;(v1=6在本身的idx_v1锁定的索引区间(5,7)); INSERT INTO t1 set id=9, v1=8,v2=2;(v1=8在本身的idx_v1锁定的索引区间(5,9));
锁等待 插入成功
插入成功 rollback;
rollback;  

 

 

 

 

 

 

 

 

由此能够看出,一个索引区间是能够被多个间隙锁锁定的,更新不当的时候,会形成死锁。

其中在transaction 2中,若是"INSERT INTO t1 set id=9, v1=8,v2=2;"这条语句换成"INSERT INTO t1 set id=9, v1=6,v2=2;"就会形成死锁,由于两个间隙锁都锁定了(5,7)这个区间。

同时在transaction 2中,执行update语句的时候,已经在id=7的主键索引上加了记录锁,任何在其余事务(例如transaction 1)中尝试更新id=7的行,都会被挂起,直到transaction 2提交或回滚。

三  锁选择

       执行更新类语句,像SELECT ... FOR UPDATE, UPDATE,DELETE语句

   1.  若是更新条件没有索引,例如执行"SELECT * FROM t1 where v2=2 for update",那么此时更新操做会使用表锁。多条更新SQL语句在不一样的事务中同时执行,先取得表锁的事务会将其余事务挂起,直到当前事务提交或回滚。

       使用表锁的缘由:

       因为更新的数据没有索引,MySQL只能作扫表操做,扫表的时候,要阻止其余任何的更新操做,因此会上升为表锁。

       2. 若是更新条件为索引字段,但并不是惟一索引(包括主键),例如执行"SELECT * FROM t1 where v1=1 for update",那么此时更新会使用后码锁。

   使用后码锁的缘由:

   a)首先要保证在符合条件的记录上加上排他的记录锁,会锁定当前非惟一索引和这些知足条件的记录对应的主键索引;b)还要保证更新的索引记录区间不能插入新数据。

       3. 若是更新条件字段为惟一索引,使用记录锁。

           Innodb会根据惟一索引,找到记录的主键索引,将符合条件的主键索引和惟一索引加上记录锁。

      说明:Innodb的索引结构

      Innodb支持聚簇索引,可是聚簇索引只能是主键索引,而且每张表只能有一个聚簇索引,所谓聚簇索引,就是索引在物理存储上是顺序存放的。主键索引就是聚簇索引,主键索引的叶子节点存放的是记录的物理地址,根据主键索引能够直接访问记录内容。非主键索引在B-tree索引的叶子节点上存放的并非记录的物理地址,而是主键索引的物理地址。

      当给非惟一索引加上后码锁的时候(例如更新非惟一主键索引对应的记录内容),Innodb会采用后码锁。首先将知足条件的非惟一索引对应的主键索引和知足条件的非惟一索引加上记录锁。而后会给非惟一索引加上间隙锁,将当前非惟一索引对应的索引区间加上间隙锁,禁止在该区间的任何INSERT操做。

四 间隙锁演示

    说明:加后码锁的时候,并未锁住间隙两端的记录,那么两端的记录是能够更新的,可是若是更新记录时会影响到间隙锁,那须要被挂起,等待间隙锁被释放。

 

transaction 3 transaction 4
BEGIN; BEGIN;
SELECT * FROM t1 WHERE v1=5 FOR UPDATE;(加上间隙锁,锁定了idx_v1的索引区间是(4,7),同时会把知足条件的记录的主键索引上加上行锁)

UPDATE 类操做

UPDATE t1 set v2=2 WHERE v1=4;(OK,不会被挂起,间隙锁锁只锁间隙,而这条更新SQL并未影响idx_v1在区间(4,7)的间隙锁控制范围);

UPDATE t1 set v2=2 WHERE v1=7;(OK,不会被挂起)

UPDATE t1 set v2=2 WHERE v1=6;(OK,不会被挂起)

UPDATE t1 set v1=1 WHERE v1=4;(OK,不会被挂起,更新索引)

UPDATE t1 set v1=5 WHERE v1=4;(因为4,7区间被封锁,这个操做会被挂起)

UPDATE t1 set v1=8 WHERE v1=7;(OK,不会被挂起,更新索引)

UPDATE t1 set v1=1 WHERE v1=9;(OK,不会被挂起,由于条件和目的索引的值都不在封锁的区间)

UPDATE t1 set v1=5 WHERE v1=7;(因为4,7区间被封锁,这个操做会被挂起)

UPDATE t1 set v1=2 WHERE v1=7(这个操做会被挂起)

由上面这两组SQL能够看出来,间隙锁锁住的区间为4,7,当更新这两端的记录的时候,若是不改变区间的值,压根就跟区间索引不要紧,那么更新操做就不会被间隙锁挂起。若是更新间隙锁区间的两端的索引值,且更新索引后的区间包含当前锁住的区间,那么能够更新成功。若是更新索引后,不能包含已经锁定的区间,那么更新操做会被挂起。

INSERT 类操做

INSERT INTO t1 set id=11, v1=5, v2=5;(挂起,transaction 3封闭的idx_v1间隙是(4,7),插入v1=5确定会被挂起)

INSERT INTO t1 set id=11, v1=8,v2=8(OK,v1不在封锁区间);

主要看一下在封锁区间两端的插入状况

INSERT INTO t1 set id=4,v1=4,v2=2;(v1=4是封锁区间的左侧值,此操做会被挂起)

INSERT INTO t1 set id=0,v1=4,v2=2;(此操做OK,能够执行)

INSERT INTO t1 set id=6, v1=7,v2=2;(v1=7是封锁区间的右侧值,此操做会被挂起)

INSERT INTO t1 set id=8, v1=7,v2=2;(此操做OK,能够执行)

当往封边区间两端插入值的时候,须要看要插入的值的主键是否在封锁区间对应的主键的范围。

具体解释:

id v1 v2
3 4 2
7 7 4

 

 

 

当插入左边界值时,即插入v1=4的时候,要求主键id的值须要在小于id=3的范围,当数据库中v1=4左侧值有多条记录的时候,插入的id小于其中最大的id便可。

当插入右界值时,即插入v1=7的时候,要求主键id值大于id=7的范围,当数据库中v1=7右侧值有多条记录的时候,插入的id大于其中最小的id便可。

ROLLBACK;

ROLLBACK;

相关文章
相关标签/搜索