mysql锁以及实践总结

1. 锁分类

innodb中的锁分为S锁,即共享锁,另外一种为X锁,排它锁,好比:html

共享锁(S)mysql

select * from supplier where id=5 lock in share mode;

排他锁(X)sql

select * from supplier where id=5 for update;

或者insert,delete,update语句,这都是排他锁spa

兼容性3d

这两种锁的兼容以下:日志

X S
X N N
S N Y

意向锁code

能够理解为属于S锁和X锁的父节点,即要获取S锁或者X锁的话,必须先提早获取意向锁,即IS或者IX锁,那综合起来看下,这两类锁的兼容状况以下cdn

X S IX IS
X N N N N
S N Y N Y
IX N N Y Y
IS N Y Y Y

隐式锁htm

也是醉了,为何要搞这么多概念。。这种锁,能够认为不冲突的时候不加锁,这个时候的锁就是隐式锁,等遇到冲突了,该锁会升级为显示锁,仍是经过一个例子来讲明吧:blog


事务1(其中age是普通索引)

select * from test01 where age=21 for update;

咱们看到mysql中实际上是没有找到锁的:

事务2

insert into test01(id,name,age) values(8,'zzh',22);

这个时候因为[21,23)有gap锁,因此事务2会被阻塞住,这个时候再看mysql中的锁记录


能够看到一开始事务1虽然使用的是...for update,可是因为没有冲突因此加的是隐式锁,等到事务2开始致使有冲突存在,因此事务1的锁改成现实锁。

2.加锁分析与实践

具体在事务的不一样隔离级别下,不一样的场景加锁分析,在hedecheng加锁分析这篇文章已经讲解的很是详细了,这里就再也不多说了。主要说下其余状况下总结的一些加锁分析。

2.1 插入意向锁(Insert Intention Locks)

先来看官网对该锁的解释


咱们重点关注下红色的区域:在遇到冲突的时候,会在该索引所在的位置加S锁,而该共享锁又很容易致使死锁,官网中就有个例子来专门说明这种共享锁致使的死锁,即三个事务同事执行一条插入语句其中一个事务回滚致使死锁:


咱们能够简单的把这种状况模仿一遍:


事务1

insert into test01(id,name,age)values(12,"zzh",33);

事务2

insert into test01(id,name,age)values(12,"zzh",33);

事务3

insert into test01(id,name,age)values(12,"zzh",33);

以下图操做:


咱们看下Mysql中锁的记录


其中这个就印证了在乎向锁冲突的时候,请求加的是S锁,而后咱们回滚事务1

事务1

rollback


这个时候咱们看到事务2成功了,事务3出现死锁


咱们能够从死锁日志中看到:

  • 事务2:等待锁模式为X的插入意向锁
  • 事务3:等待锁模式为X的插入意向锁;拥有锁模式为S的record lock
  • 暗含条件:事务2也拥有锁模式为S的record lock,这才致使了死锁,死锁日志会把最终成功获取锁了的事务已经拥有的锁不会打印出来

因此存在 事务2->事务3,事务3->事务2 死锁发生

官网中还要另外一个相似的例子,这里就很少作分析了,缘由相似。

下面咱们看下另外一种状况:


事务1(事务id=2944)

select * from test01 where age=21 for update;

事务2(事务id=2945)

insert into test01(id,name,age)values(2,"zzh",22);

事务3(事务id=2946)

select * from test01 where age=21 lock in share mode;

执行以下

mysql中锁记录


咱们能够看到

  • 事务1:在primary key的(X,RECORD LOCK),在age_idx的(X,RECORD LOCK)
  • 事务2:在primay key 的(S,RECORD LOCK),这个就是咱们前面讲到的,事务2在获取插入意向锁出现冲突,因此阻塞在了要获取(S,RECORD LOCK)
  • 事务3:在age_idx的(S,RECORD LOCK),

而后咱们提交事务1
事务1

commit;

  • 事务2:加S锁成功,可是出现惟一键冲突,直接报错
  • 事务3:获取S锁成功,可是是在age_idx上,跟事务2不同。

咱们再从新执行事务1

事务1(事务id=29467)

select * from test01 where age=21 for update;

执行结果被阻塞:


咱们看下mysql中的锁记录:

能够看到事务3拥有在age_idx的S锁,阻塞了事务1要获取的X锁,那这个时候咱们提交事务3
事务3 commit;


咱们发现事务1还处于阻塞的状态,看下mysql中的锁记录


这个时候明白了,事务2虽然执行失败,可是其因为插入意向锁冲突所加的S锁并未释放,因此会致使事务1还处于阻塞中。只有当咱们提交了事务2,事务1才会真正执行。

2.2 select...for update

该语句加锁分为两种状况

  • 记录存在:若是是RC模式下,加的是(X,RECORD LOCK),RR 模式下,加的是(X,NEXT-KEY LOCK),该锁相互不兼容
  • 记录不存在:加(X,GAP)锁,而且该锁是兼容的,可是与Insert Intention Locks 不兼容,这个很容易致使死锁

2.2.1 不一样索引之间等待出现死锁


事务1

select * from test01 where age=21 for update;

事务2

insert into test01(id,name,age)values(3,"zzh",22);

事务1

insert into test01(id,name,age)values(3,"zzh",100);

按如上顺序执行结果:


咱们发现出现死锁,事务2被mysql回滚以后事务1执行成功。咱们看下具体的死锁日志


经过死锁日志咱们能够分析出来

  • 事务1: 拥有age_idx(X,NEXT-KEY LOCK),加插入意向锁存在冲突,因此等待id=3位置的(S,RECORD LOCK)
  • 事务2: 等待age_idx的插入意向锁(插入意向锁不仅是在primary key上才有)
  • 暗含条件:事务2虽然在age_idx上等待插入意向锁,可是在id=3位置上加插入意向锁成功了,这才有了事务1在该位置加插入意向锁存在冲突

2.2.2 记录不存在致使的死锁


事务1(事务id=29684)

select * from test01 where age=21 for update;

事务2(事务id=29683)

select * from test01 where age=21 for update;

事务1

insert into test01(id,name,age)values(3,"zzh",22);

执行结果以下


能够看到

  • 事务1和事务2同时持有了age=22这个记录的gap锁,因为记录不存在,因此此时的gap锁兼容
  • 可是记录不存在兼容的gap锁和插入意向锁兵不兼容,因此事务1向age=22索引所请求的插入意向锁会等待。

事务2

insert into test01(id,name,age)values(3,"zzh",22);

由上面的分析一样得出事务2的等待有两种状况:

(1) 和事务1同样,等待age=22的插入意向锁,此时发现已经有事务1在等待该位置的插入意向锁,那就等待该位置的S锁

(2) 事务1虽然在age=22的插入意向锁等待,可是id=3的插入意向锁是加成功了,因此若是事务2在id=3这里等待插入意向锁的话,也会有冲突,那就等待该位置的S锁

因此,有这两种状况都会致使事务出现死锁,咱们具体看下死锁日志:


咱们看到:

  • 事务1在等待age=22位置的插入意向锁
  • 事务2在等待 id=3位置的S锁,也就是咱们分析的第二种状况,同时事务2拥有age=22位置的gap锁

咱们上面有分析,事务2的等待应该是有两种状况,而出现这两种状况的可能就是事务2的这条语句

insert into test01(id,name,age)values(3,"zzh",22);

咱们了解到在记录不存在的时候,若是对此记录进行select...for update语句,该语句会对空位置加gap锁,这个会比较危险,若是咱们的表用的是自增主键,而此时若是查询一个不存在的记录,那会把将来要插入的全部的空隙都加了gap锁,会致使之后表中没法在插入任何数据,这个极其危险。

3. 小结

本篇,咱们介绍了mysql中的锁以及在实践中可能遇到的一些死锁,专门经过几个demo集中分析了一下,对于加锁的分析,在文中所提到的hedecheng的文章中,已经有很深的讲解,因此本文并无对此总结。重点仍是经过一些实际中用到的例子进行实际的分析一下整个过程。重点是插入意向锁和select...for update中的一些加锁的分析。下一篇来专门介绍下mysql的MVCC机制

相关文章
相关标签/搜索