有一张表A,先删除数据,如果影响行数为0,则执行INSERT插入数据。很常见的场景,在生产上也跑了很久,没有出现什么问题。但是有一次在测试环境做压测时居然出现了死锁,Deadlock found when trying to get lock; try restarting transaction
因为对mysql锁不熟悉,为什么insert也会死锁,不是一般在update的时候会死锁吗? 很好奇,于是开始寻找原因…
mysql锁是跟数据库设置的隔离级别有关系的,不同的隔离级别,锁也各不相同,只要是为了解决类似脏读、幻读、可重复读的问题。
select @@tx_isolation; – 查看隔离级别
隔离级别是RR(可重复读),我们知道,在此隔离级别下,为了解决幻读的问题,会有存在间隙锁和Next-key lock(行锁 + 间隙锁),即在update、delete语句后会产生间隙锁和Next-key lock,如果在并发下,存在两个事务都事前执行了UPDATE语句(各自持有了gap lock),当INSERT时,要先在插入间隙上获取插入意向锁,由于插入数据的间隙存在冲突,所以会互相等待获取插入意向锁,即相互竞争,最终会导致一方死锁。
这也解释了为什么在测试环境出现死锁。
解决:
- 不采用事务,即无事务方式执行,但是如果出现异常则会出现数据不一致的情况。
- 调整事务隔离级别为read commit,RC级别不会产生gap lock
由于我们都知道事务的重要性,所以选择第二种方式,将隔离级别改为RC。我们在生产环境一般也就这个级别,所以也解释了为什么在生产没有出现这个问题。
调整事务隔离级别为read commit方式:
方案一:事务隔离级别调整可以通过spring的声明式事务方式来实现,通过注解@Transactional
@Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED)
public void calculationEntry(List<CampaignFeedback> campaignFeedbackList, String groovyScript) {
//todo
}
方案二:调整mysql的默认事务隔离级别(或者mycat的默认事务隔离级别)
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
如果出现下面错误则继续执行以下语句
SET GLOBAL binlog_format = 'STATEMENT';
SET GLOBAL binlog_format = 'ROW';
SET GLOBAL binlog_format = 'MIXED';
修改mycat的默认事务隔离级别 <property name="txIsolation">2</property>
property name="txIsolation">3</property>
前端连接的初始化事务隔离级别,只在初始化的时候使用,后续会根据客户端传递过来的属性对后端数据库连接进行同步。默认为 REPEATED_READ,设置值为数字默认 3。
READ_UNCOMMITTED = 1;
READ_COMMITTED = 2;
REPEATED_READ = 3;
SERIALIZABLE = 4;