版本:mysql5.5.52  

存储引擎:InnoDB

隔离级别:READ-COMMITTED

 

示例一:

 

事务1:左图      事务2:右图

 

1、

 

 

事务2中属于快照读,基于多版本的并发控制协议——MVCC,读取的是记录可见版本,不用加锁,事务1属于当前读,加排它锁,因此事务1虽然未提交,事务2依然可以执行。快照读是mysql InnoDB存储引擎下,隔离级别为READ COMMITTED和REPEATABLE READ时,select语句默认的读取模式。

 

2、

 

 

事务1属于当前读,加排它锁,事务2的读取操作也是需要排他锁的,因此读取被阻塞,导致超时,直到事务1提交后,事务2才能读取:

 

 

3、

 

 

事务1属于当前读,加排它锁,但由于隔离级别为READ-COMMITTED,不加gap锁,依然可以插入,如果将隔离级别换成REPEATABLE READ,第一次插入操作被阻塞,直到事务1提交时,插入操作才执行:

 

 

gap就是索引树中插入新记录的空隙,相应的gap lock就是加在gap上的锁,主要是为了防止幻读,只在REPEATABLE READ或以上的隔离级别下的特定操作才会取得gap lock。

 

4、

 

对于事务1开启后在事务2中插入的记录,由于没有加排它锁,可以直接删除:

 

 

开启前已存在的记录,在事务1中加了排它锁,需等待事务1提交才能在事务2中删除:

 

 

 

 

示例二:

 

 

 

有一个后台的定时任务,定时向第三方发出状态改变请求,同时改变本地数据表的状态,但这个状态是否改变成功是需要第三方确认的,确认的方式是第三方以http请求的形式返回一个处理结果标志(成功或者失败),如果请求没有响应,则重复请求多次,直到我方响应。伪代码如下:

步骤一

我方发送状态改变请求:

 

@Transaction

public  void sendChange(int id){

  HttpUtils.send(id,"change");

  statusDao.update(id,"pendSuccess");

  relatedPeopleDao.update(id,"pendFinishRequest");

}

 

 

步骤二

我方响应第三方返回的处理结果(方法被web层调用):

 

@Transaction

public void doResponse(int id, String resultStatus){

  relatedPeopleDao.update(id,resultStatus);

  statusDao.update(id,resultStatus);

}

 

这个程序大部分情况是可以正常运行的,因为第三方返回处理结果有一段更长的网络延时,但是否存在这种可能,在方法sendChange开始执行 relatedPeopleDao.update(id,"pendFinishRequest")

这段代码的时候, 第三方很快返回了处理结果,relatedPeopleDao.update(id,resultStatus)已经执行且持有related_people表相关记录的锁,同时等待status表的锁被释放,但是此时sendChange的一系列操作尚未提交数据库,status的相关记录表仍被事务1持有,两个事务同时持有对方的资源同时在等待对方释放相关的锁,这就产生了死锁现象。

 

解决方法之一是在执行doResponse操作之前先检查下related_people表相关记录的状态是否处于合适状态,状态检查是一个普通的select操作,数据库隔离级别为读已提交,因此,如果步骤一中事务未提交,则不会读取到其改变的状态,提交后才能读取到。

 

@Transaction

public void doResponse(int id, String resultStatus){

  if(relatedPeopleDao.isPendFinishRequest(id)){

    relatedPeopleDao.update(id,resultStatus);

    statusDao.update(id,resultStatus);

  }

}