mysql分区表导致的死锁

死锁异常:

org.springfreamwork.dao.DeadlockLoserDataAccessException:......

Cause:java.sql.BatchUpdateException:Deadlock found when trying to get lock;try restarting transaction at.....

1、java代码如下

@Transactional

public void handle(){

selectDao.select1(...);

updateDao.insert(...);

}

2、和java代码(碰巧)同时运行的job中的sql代码如下:

alter table add partition .....(添加或者删除分区)

3、被操作的表是一个分区表

4、java代码在一个线程中,job在另外一个线程中

 

发生死锁的原因:

1、java中发起的sql(dao的select方法和insert方法中的sql语句)中的where语句没有分区键,所以就锁定了全部分区。

注:dml sql语句执行前是一定要申请mdl读锁的(mdl表示meta data lock)。目的是防止在dml语句执行期间有其它ddl语句的执行(ddl语句执行前要申请mdl写锁)。而mdl读锁是可以共享的(mdl读和mdl读之间共享),但是和mdl写锁是互斥的。

2、job中的sql因为是alter table的语句(ddl语句),所以要申请mdl写锁(mdl写和mdl写或者mdl写和mdl读之间是互斥的)。

注:ddl sql语句(包括alter table)执行前是一定要申请mdl写锁的。目的是防止在ddl期间其它dml语句执行。

3、如果mdl写锁被block后需要等待持有mdl读锁的事务结束后才可以获取到写锁,而不是事务中的某个sql结束后就能够获取到写锁

4、mdl读、写锁是公平锁。事务中的第一条dml语句先持有了mdl读锁、job发起了mdl写锁申请、同一个事务中的第二条dml语句再发起mdl读锁申请的时候就需要排队等待mdl写锁的释放。

 

时间线 java事务中的代码 java事务中的锁 job代码 job代码中的锁
T1 select 持有mdl读锁    
T2     alter table

想要获取mdl写锁;

等待事务的mdl读锁释放后才可以获取到,所以block住

T3 insert

等待job的mdl写锁释放后获取mdl读锁;

因为job的mdl写锁block住了,所以事务无法提交

   

 

 

 

 

 

 

 

分析:T2时刻在等待整个事务的释放以便获取到mdl写锁,T3时刻等待T2时刻申请的mdl写锁释放掉。即,T2等T3释放资源、而T3也在等T2释放资源,于是死锁。

 

解决办法:把java代码方法上的事务删除掉,使得select语句结束后持有的mdl读锁就立刻释放掉。

思考:

1、如果java方法上的事务必须使用,那么在方法中的sql语句中的where语句上使用分区键是否可以解决问题?

因为mysql的分区表是innodb引擎层的概念,在server层仍然认为是一张表,也只有一个mdl锁(所有分区共用一个mdl锁);

2、如果java事务必须存在(比如事务中有两个insert语句),那么这个死锁的发生是不可避免的?那么最终如何解决?

死锁是不可避免的。只能尽量使用短事务。可以在ddl语句上使用timeout,具体而言:

 

对于分区表的一些知识补充:

1、所谓分区是对innodb存储层而言的。在上层(比如server层或者应用层)看来这就是一张表。对于mysql分区表而言,是由server层来决定使用哪个分区,而对于手工分表(比如sharding jdbc)而言是由应用层代码决定使用哪个分区。而从innodb角度看来,多个分区到底是由server层还是由代码来决定的,这两种方式并无区别。

2、在server层看来,分区表就是一张表,比如,一张表的多个分区共用一个mdl锁

3、mysql在第一次访问分区表的时候必须要访问所有分区,因此性能会有损失(相对于真正的单表而言)

4、在innodb层看来,分区表就是多张表,因此mdl锁之后的执行过程(比如dml或者ddl)会根据分区表规则只访问必要的分区,这相对于单表而言又有了一定性能优势(如果是等值分区键那么性能优势明显,如果是范围那么可能没那么明显)。

5、使用分区表的场景:业务代码更简介,因为对于业务代码而言这就是一张表;删除历史记录更容易,因为可以直接alter table drop partition,效果和drop 单表类似。

6、对业务代码而言,到底使用分区表还是使用分库分表中间件,这个要看中间件的成熟度。

7、分区表如果使用了自增主键,mysql要求主键中必须包含分区字段(唯一索引也必须包含分区字段),这对于业务编码而言也是有一定复杂性的。

 

posted on 2023-07-23 17:06  北方白杨  阅读(300)  评论(0编辑  收藏  举报