有一张表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;