本文概览1、介绍了三种锁类型,以及RecordLockGapLock等具体锁。2、以及每一sql执行时如何上锁。

附: 本小节使用到数据表结构和数据为

 

1 Mysql锁

1、Latches

在java并发包解除过锁,其实可以理解成LATCHES,控制线程之间争夺资源

2、LOCKS

是控制事务之间争夺资源的。如通过SelectForUpdate

1.1 三个基本锁

常见的有三种锁:共享锁,排他锁,意向锁。其中意向锁分为意向共享锁和意向排他锁。

1.1.1 互斥锁(X Lock)和共享锁(S Lock)

1、互斥锁和共享锁关系

共享锁 排他锁
共享锁 兼容 冲突
排他锁 冲突  冲突

这里需要注意一点,就是锁具有可重入性,即,对于同一事务,在获取锁(S或X lock)之后,仍然可以继续获取锁。上面表格中锁的关系是指不同事务所属的锁之间关系

2、使用锁

1.1.2 意向锁

为了支持多粒度的上锁(表锁和行锁),引入意向锁。比如事务A要在一个表上加S锁,如果表中的一行已被事务B加了X锁,那么该锁的申请也应被阻塞。如果表中的数据很多,逐行检查锁标志的开销将很大,系统的性能将会受到影响。为了解决这个问题,可以在表级上引入新的锁类型来表示其所属行的加锁情况,这就引出了“意向锁”。所以,意向锁只有在使用表锁时,才会用到

每次添加行锁时,都需要首先添加意向锁。加行锁步骤为:

  • step1:先添加意向锁,因为意向锁都兼容,都是都会添加。
  • step2:对行记录添加排他或者共享锁。

1.1.3 兼容关系

三种锁之间的兼容关如下表,这种兼容关系会导致事务死锁。

  • 共享锁和 共享锁、意向共享锁兼容。与其他锁冲突
  • 排他锁 和任何锁都是 冲突。
  • 意向锁和意向锁兼容。    意向共享锁和共享锁兼容;意向排他锁和非意向锁冲突。
共享锁 排他锁 意向共享锁 意向排他锁
共享锁 兼容 冲突 兼容 冲突
排他锁 冲突 冲突 冲突 冲突
意向共享锁 兼容 冲突 兼容 兼容
意向排他锁 冲突 冲突 兼容  兼容

1.2  Innodb 行锁

与MyISAM不同,InnoDB有两大不同点:

  • 支持事务
  • 采用行级锁

mysql innodb提供了三种行锁

  • Record Lock:单个行记录上的锁(X锁或者S锁)
  • Gap Lock:间隙锁,锁定一个范围,但不包含记录本身。Gap Lock引入是为了避免幻读。
  • Next-key Lock算法:Record + Gap Lock,锁定一个范围,并锁定记录本身。
  • Insert Intention Locks,只有insert时,在获取RecordLock之前进行获取该锁,只会跟GapLock进行互斥,与Record、InsertIntentionLocks都是兼容的。

X/S/IS/IX 和 RecordLock/GapLock 之间的关系?
(1)X、S、IS、IX是锁的类型,不是具体的锁。
(2)具体锁有Recored Lock和Gap Lock,每一种具体锁,都包含X、S、IS、IX类型,如共享类型的GapLock,互斥类型的GapLock。

根据不同事务级别,mysql innodb采用的加锁算法不同:

  • 对于Read REPEAT级别,采用Netxt Lock算法。遇到唯一索引和主键降级到Record Lock。
  • 对于Read COMMIT 采用Record Lock。

注意:mysqll默认使用的是Read Repeat事务隔离级别。

1.2.1 Gap Lock

1、GapLock介绍

间隙锁GapLock只在insert时会被用到,防止不同事务在insert在同一个gap插入数据,导致幻读出现。

 

对于同一个GAP区间,GapLock和Record Lock、GapLock和GapLock都是兼容的,GapLock只会跟Insert Intention Locks互斥。如下图

GapLock InsertIntentionLock RecordLock
GapLock 兼容 兼容 兼容
InsertIntentionLock 冲突 冲突 兼容
RecordLock 兼容 兼容  冲突

2、GapLock算法

以普通索引key为例,

对于存在(1,3)(4,6)(6,8),对于key=6,不仅需要添加(4,6)gap锁,还需要对普通索引的下一个键值添加gap lock,即(6,8),则此时会有两个gap 锁。

3、如何理解GapLock可以避免幻读。

在没有GapLock,只有Record lock时可以走通,出现幻读,如下

步骤 事务1 事务2
 1  begin
2 begin
3

mysql> select * from student where score<22 for update ;        

出现2条数据

4

mysql> insert into student(id,name,score,desc) values (10,’ff’,16,’luce’);

5  commit
6

select * from student where score<22 for update ;

出现幻读:发现有3条数据

当存在GapLock时,GapLock可以阻止多个事务将记录插入到同一范围内,而导致上述流程会被阻塞。

步骤 事务1 事务2
1 begin
2 begin
3

mysql> select * from student where adr<‘cc’ for update ;

上锁:Gap锁(10,22

4

mysql> insert into student(id,name,score,desc) values (10,’ff’,16,’luce’);

需要等待事务1Gap锁(10,22),然后上锁Insert Intention Locks

Lock wait timeout exceeded; try restarting transaction

2 基于RR事务隔离级别的sql语句加锁

1、INSERT

只添加 Reocord Lock,没有使用gap 锁。在获取Reocred Lock时,还需要获取一个Insert Intention Locks。

Insert Intention Locks只发生在insert时,在获取RecordLock之前进行获取该锁。Insert Intention Lock只会跟GapLock进行互斥,与Record、InsertIntentionLocks都是兼容的。

2、对于DELETE FROM … WHERE …  、SELECT … FOR UPDATE  和 SELECT  LOCK  IN  SHARE MODEL、Update … WHERE 三种操作,需要根据where条件来加锁,可以通过执行计划查看使用那些索引

  • where中使用普通索引,使用next-key lock,即不仅加RecordLock而且加GapLock。
  • where使用唯一索引/主键。都是使用Recored lock。
  • where 中未使用普通索引。锁表,对所有元素进行上锁RecordLock,而且对所有Gap上锁GapLock

2.1 Insert

1

1、Insert只会设置一个Reocred lock,不是 next-key lock。如下

(1)两个事务插入相同的元素时

步骤 事务1 事务2
1 begin
2 begin
3

mysql> insert into student(id,name,score,des) values (3,’ggg’,12,’luc’);

上锁:Record Lock

4

mysql> insert into student(id,name,score,des) values (3,’ggg’,12,’luc’);

等待事务1的Record Lock

Lock wait timeout exceeded; try restarting transaction

(2)当两个事务插入元素的主键不同时,此时不会进行阻塞

步骤 事务1 事务2
1 begin
2 begin
3

mysql> insert into student(id,name,score,des) values (3,’ggg’,12,’luc’);

Query OK, 1 row affected (0.01 sec)

4

mysql> insert into student(id,name,score,des) values (3,’ggg’,12,’luc’);

Query OK, 1 row affected (0.01 sec)

2、当存在一个事务A已经执行了insert插入相同的元素,则当前事务B的insert操作会尝试获取一个RecordLock共享锁,此时会进行等等待,直到A进行commit或者rollback。

在 如下文中“三个insert”和“delete+2个insert”都是因为这个共享锁导致的死锁。

mysql 死锁

2.2 SelectForUpdate

2

2.2.1 普通索引

SelectForUpdate的where条件为普通索引时,会“score=22”数据上RecordLock),还有(10,22)和(22,无穷大)两个GapLock。

步骤 事务1 事务2
1 begin
2 begin
3

mysql> select * from  student where score = 22 for update;

上锁GapLock和RecordLock

4

mysql> insert into student(id,name,score,des) values (3,’ggg’,12,’luc’);

事务2需要等待事务1的Gap锁,然后上锁Insert Intention Locks,然后在上锁RecordLock

Lock wait timeout exceeded; try restarting transaction

在事务2执行insert进行阻塞时,查看锁信息,发现SelectForUpdate的where条件是普通索引时,此时lock_mode为X和Gap

2.2.2 唯一键/主键

对于上面操作步骤中,是互不影响的。查看锁的信息为

1、唯一键的信息,发现lock_mode只有X没有GAP

执行sql:

查询锁信息为

2、主键的SelectForUpdate的锁信息,发现lock_mode只有X没有GAP

(1)sql

(2)查询锁信息为

3、主键/唯一键 和 普通索引区别。

  • 普通索引在执行SelectForUpdate时,不仅对查询到元素进行加RecordLock,还会获取GapLock。
  • 主键/唯一键在执行SelectForUpdate时,如果含有数据,则此时不会有GapLock,但是没有数据会产生GapLock。

4、当使用主键/唯一键时,如过查询为空,则此时会添加一个GapLock。

如下文中死锁case: 两个selectForUpdate和两个insert

mysql 死锁

2.2.3 无索引

执行SelectForUpdate时,如果where没有使用索引时,如何进行上锁?上的表锁。此时,事务1 在没有索引情况下会锁这个表,事务2无法对任何数据进行上锁。

1、执行步骤为

步骤 事务1 事务2
1 begin
2 begin
3

mysql> select * from student where des = ‘hello’ for update;

查询得到一行数据,但是对整个表的每一个数据进行上锁RecordLock。

4

mysql> select * from student where des = ‘hi’ for update;

等待这个数据RecoredLock。

Lock wait timeout exceeded; try restarting transaction

2.3.4 没有获取到元素

1、唯一键和主键时

当使用唯一索引/主键 没有获取到元素时,此时没有获取到Reocrdlock,但是会获取到一个GapLock。

步骤 事务1 事务2
1 begin
2 begin
3

select * from student where id =5 for update;

Empty set (0.00 sec)

上锁gap锁(负无穷,6)

4

select * from student where id = 5for update;

Empty set (0.00 sec)

上锁gap锁(负无穷,6)

2、where条件使用普通索引或者不使用索引

都会获取GapLock和RecordLock

步骤 事务1 事务2
1 begin
2 begin
3

select * from student where score < 6 for update;

Empty set (0.00 sec)

获取ReadLock

4

select * from student where score < 6 for update;

获取ReadLock

Lock wait timeout exceeded; try restarting transaction

查看锁信息,两个事务都是获取的RecordLock。

2.3 Update

和Select For Update类似,分为普通索引、主键/唯一键、没有索引。

3

2.3.1 普通索引

此时,会产生gap锁

步骤 事务1 事务2
1 begin
2 begin
3

mysql> update student set name= ‘ff10’ where score=10;

上锁GapLcok(6,10)、GAPLock(10,22)、RecoredLock:10

4

mysql> insert into student(id,name,score) values (9,’fdd’,9);

需要事务2等待Gap锁(6,10),然后上锁Insert Intention Locks,然后再上锁RecoredLock

Lock wait timeout exceeded; try restarting transaction

5

mysql> insert into student(id,name,score) values (10,’fdd’,12);

需要事务2等待Gap锁(6,10),然后上锁Insert Intention Locks,然后再上锁RecoredLock

Lock wait timeout exceeded; try restarting transaction

6

mysql> insert into student(id,name,score) values (11,’fdd’,23);

可以正常执行

Query OK, 1 row affected (0.00 sec)

2.3.2 主键/唯一键

主键和唯一键只会添加 Record Lock,不会添加Gap Lock。

1、主键

更新不同行不会进行阻塞

步骤 事务1 事务2
1 begin
2 begin
3

mysql> update student set des = ‘test’ where id = 5;

上锁Record lock:id=5

Query OK, 1 row affected (0.00 sec)

4

mysql> update student set des = ‘test’ where id = 7;

上锁RecordLock:id=7,与事务1的RecordLock不冲突,所以不会被阻塞

Query OK, 1 row affected (0.00 sec)

2、唯一键

更新不同行不会进行阻塞

步骤 事务1 事务2
1 begin
2 begin
3

mysql> update student set des = ‘test’ where name=’ff10′;

上锁Record lock:name=’ff10′

Query OK, 1 row affected (0.00 sec)

4

mysql> update student set des = ‘test’ where name=’ffg’;

上锁RecordLock:name=’ffg’,与事务1的RecordLock不冲突,所以不会被阻塞

Query OK, 1 row affected (0.00 sec)

2.3.3 没有使用索引

此时,事务1 在没有索引情况下会锁这个表,事务2无法对任何数据进行上锁。

步骤 事务1 事务2
1 begin
2 begin
3

mysql> update student set score = 100 where  des = ‘koko’ ;

Query OK, 1 row affected (0.00 sec)

查询得到一行数据,但是对整个表的每一个数据进行上锁RecordLock。

4

mysql> update student set score = 100 where  des = ‘hi’ ;

等待事务1这个数据RecoredLock。

Lock wait timeout exceeded; try restarting transaction

5

mysql> update student set score = 100 where  des = ‘hello’ ;

等待事务1这个数据RecoredLock。

Lock wait timeout exceeded; try restarting transaction

6

mysql> update student set score = 100 where  des = ‘tommorrow’ ;

等待事务1这个数据RecoredLock。

Lock wait timeout exceeded; try restarting transaction

2.3.4没有记录时

参考selectForUpdate没有记录的情况

2.4 Delete

5

2.4.1 普通索引

此时,会产生gap锁

步骤 事务1 事务2
1 begin
2 begin
3

mysql> delete from student where score = 10;

Query OK, 1 row affected (0.00 sec)

上锁GapLcok(6,10)、GAPLock(10,22)、RecoredLock:10

4

mysql> insert into student(id,name,score,des) values (12,’c’,9,’luc’);

事务2等待Gap锁(6,10),然后上锁Insert Intention Locks,然后再上锁RecoredLock

Lock wait timeout exceeded; try restarting transaction

5

mysql> insert into student(id,name,score,des) values (13,’cc’,12,’luc’);

事务2等待Gap锁(10,22),然后上锁Insert Intention Locks,然后再上锁RecoredLock

Lock wait timeout exceeded; try restarting transaction

6

mysql> insert into student(id,name,score) values (14,’fdd’,23);

Query OK, 1 row affected (0.00 sec)

2.4.2 主键/唯一键

此时,只会上锁一个RecordLock,以主键为例,如下

步骤 事务1 事务2
1 begin
2 begin
3

mysql> delete from student where name= ‘ff10’;

只锁了一个Record Lock,没有Gap Lock

Query OK, 1 row affected (0.00 sec)

4

mysql> insert into student(id,name,score,des) values (12,’c’,9,’luc’);

因为事务1没有GapLock,所以不会阻塞事务2的insert的Insert Intention Locks,此时事务2可以直接获取RecoredLock,然后执行insert语句

Query OK, 1 row affected (0.00 sec)

2.4.3 没有使用索引

此时,事务1 在没有索引情况下会锁这个表,事务2无法对任何数据进行上锁。

步骤 事务1 事务2
1 begin
2 begin
3

mysql> delete from  student  where  des = ‘koko’.

对表中所有记录上了RecordLock

4

mysql> update student set score = 100 where  id = 0 ;

等待事务1的RecoreLock

Lock wait timeout exceeded; try restarting transaction

5

mysql> iupdate student set score = 100 where  id = 2 ;

等待事务1的RecoreLock

Lock wait timeout exceeded; try restarting transaction

6

mysql>update student set score = 100 where  id = 7 ;

等待事务1的RecoreLock

Lock wait timeout exceeded; try restarting transaction

2.4.4没有记录时

参考selectForUpdate没有记录的情况

附1  分析锁

1、查看事务信息

2、查看事务之间等待

3、查看事务之间锁

对于information_schema#innodb_locks的字段解释。参考https://dev.mysql.com/doc/refman/5.5/en/innodb-locks-table.html

4、查看死锁信息

查看如下信息

  • lock_mode X locks rec but not gap waiting  代表锁住的是一个索引,不是一个范围
  • locks gap before rec:表示的是gap锁。

附2 mysql开启日志

默认情况下是没有输出sql日志的。

开启日志

此时general_log的属性为ON了,如下

然后查看日志/usr/local/mysql/data/B000000064800.log 如下:

参考资料

1、sql语句对应的锁,官网:https://dev.mysql.com/doc/refman/8.0/en/innodb-locks-set.html

2、msyql锁,官网:https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html

2、mysql锁,翻译:https://blog.csdn.net/tbwood/article/details/79004523

3、Myslq Locks.官网:https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html

(全文完)

分类&标签