mysql serializable_mysql SERIALIZABLE隔离级别死锁问题

最近的项目,为了保障绝对的一致性,使用SERIALIZABLE作为隔离级别。

然后就爆出了很诡异的死锁。

报错log如下:

org.springframework.dao.DeadlockLoserDataAccessException: PreparedStatementCallback; SQL [xxxxx]; Deadlock found when trying to get lock;

try restarting transaction; nested exception is com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction

1461 2017-04-17 21:58:26.550 ERROR pool-7-thread-2

我们的sql大概是这样的:

begin;

select * from table_a where id = 6;

INSERT INTO xxx (xxxxx) ON DUPLICATE KEY UPDATE xxxxx;

UPDATE table_a SET column_a = GREATEST(column_a, 0) WHERE id = 6;

commit;

报错的概率并不高,在我们的系统中,大概只有2%的概率左右出现。

单独执行这段sql并不会有任何问题,因此定位了许久。后来灵光一下,同时开启两个事务来模拟这个操作。

方式如下:

//准备工作

1 set session transaction isolation level SERIALIZABLE;

2 create table user (id int PRIMARY KEY, age int) engine = innodb;

3 insert into user values(1, 2);

分别开启两个事务

//事务A

begin;

select * from user wherer id = 1;

//A level_1

update user set age = 3 where id = 1;

//A level_2

commit;

//事务B

begin;

select * from user wherer id = 1;

//B level_1

update user set age = 3 where id = 1;

//B level_2

commit;

两个事务同时开启,当执行到level_1的时候停住,注意观察数据库的输出

c6fa0ce05c4bbc27337a9edbc09a094b.png

level_1

这个时候一切正常

我们继续往下执行事务A,如图

af8c5977868dc70a39a094c023f76a9b.png

level_2.png

我们发现事务A卡住了。

再继续执行事务B,死锁出现了。

223acc73a014a82cca6bc1e4d17f15be.png

dead_lock.png

原因:

究其原因,是SERIALIZABLE隔离级别读写锁竞争导致的。

在SERIALIZABLE级别下,不会使用mysql的mvcc机制,而是在每一个select请求下获得读锁,在每一个update操作下尝试获得写锁。

在我们的例子中,在level_1中,事务A获得了id = 1的读锁A。

而在同时,事务B获得id = 1的读锁B。

在事务A level_2时,事务A尝试获得id = 1的写锁,这个时候,由于id = 1处不仅有事务A的读锁,还有事务B的读锁,因此事务A的update操作获取锁被阻塞。

此时,当事务B继续执行update操作时,由于事务A又拥有id = 1的读锁A,因此进入互相等待状态,造成死锁。

解决方案:

1 将select操作改为select for update,直接加写锁。

2 在业务层将此种类事务串行化。


版权声明:本文为weixin_35791324原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。