Sequelize 事务大并发下造成的死锁问题。

  • 环境
  • Mysql 5.6 Innodb

1.起因

我们有个需要事物的业务场景,上线之初一直运行正常,可是在晚上高峰的时候一直会有逻辑错误的问题,刚开始绝的是逻辑有问题。在阿里RDS后台发现出现大量的锁,主表的某些数据行持有锁不释放,其它的sql一直等待,知道deadlock报错。因为主表其它业务模块也会用到。所以是一个比较紧急的情况

2.错误的代码

代码可以简写为:

DBSequelize.transaction({autocommit:false,isolationLevel:'SERIALIZABLE'}).then(function(t){
    return db.Tuser.create({...},{transaction:t});
}).then(function(trans){
    return db.TuserRelation.update({...},{where:{...},transaction:t})
}).then(function(trans){
    return db.Twork.update({...},{where:{...}})
}).then(function(result){
    t.commit(res.json(result));
})

大概意思就是如果用户注册了一个账号,就在账号关系里增加一个记录这两个操作是原子的。无论执行是否成功都会执行
向work表里添加一条数据。没并发的情况下,这个逻辑执行是没有问题的,但是一旦有并发就会出问题。

3.分析错误

首先在创建事务的时候我们指定了两个参数”不自动提交“,”一致性读“。问题就出现在不自动提交这里。
刚开始我对事务理解就是:只有我们主动执行commit的时候才会把buffer cache的脏数据写入磁盘并释放事务持有的锁。
但是真是情况不是这样的。在mysql官方文档中找到了关于autocommit transaction相关描述

By default, MySQL runs with autocommit mode enabled. This means that as soon as you execute a statement that updates (modifies) a table, MySQL stores the update on disk to make it permanent. The change cannot be rolled back.

To disable autocommit mode implicitly for a single series of statements, use the START TRANSACTION statement:

START TRANSACTION;
SELECT @A:=SUM(salary) FROM table1 WHERE type=1;
UPDATE table2 SET summary=@A WHERE type=1;
COMMIT;

With START TRANSACTION, autocommit remains disabled until you end the transaction with COMMIT or ROLLBACK. The autocommit mode then reverts to its previous state.

这里有几个概念可以明确:

1.   autocommit的设定是针对session的(一个数据库连接),如果你对这个这个连接设定了autocommit=1那么这个连接
   执行的所有sql都会自动commit,反之不会自动提交必须手动commit提交
2. 无论连接设置的autocommit是什么状态,在执行事务时候事务里的autocommit状态都会被隐式的设置为0,当事物执行
   完成后autocommit会被设置为原先的状态(执行事务前)

所以我们的问题出现在创建事务时候的{tautocommit=false}这个参数其实是对连接设置的,而不是事务。

在并发的情况下,sequelize连接池的连接是会复用的,假如一个执行了事务的连接去执行其它的DML语句,这些语句是不会提交的
也就是说数据行的锁不释放,其它设计到该条数据的DML语句将会被阻塞。造成死锁。

4.解决

我们只需在事务参数中设定autocommit=true就行了。

你可能感兴趣的:(mysql)