-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Unexpected behaviour of Retry feature using some RDBMSs versions #8352
Description
What are you doing?
The Retry feature fails to identify a rolled back transaction caused by a deadlock (or there is a lack of documentation).
The default behaviour of some MySQL and MariaDb versions is rolling back a transaction if a dead lock is detected (https://dev.mysql.com/doc/refman/5.7/en/innodb-deadlock-detection.html).
Although it may be changed by configuration option (MySQL
https://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html#sysvar_innodb_deadlock_detect, and MariaDb >= 10.2.26 https://mariadb.com/kb/en/library/xtradbinnodb-server-system-variables/#innodb_deadlock_detect), the default action is rolling back the whole transaction, not only the last query.
Due this behaviour, the Retry option may hide an error because the Transaction Promise chain is not broken even after the transaction in the database has been rolled back.
In summary, the Sequelize Transaction promise chain may finish successfully even when all data has been lost.
const Promise = require('bluebird');
const Sequelize = require('sequelize');
let sequelize = new Sequelize('maydb', 'myuser', 'a-secure-password',
{
host: 'localhost',
dialect: 'mysql',
retry: {
match: /Deadlock/i,
max: 5
},
logging: console.log
});
/*
CREATE TABLE foo (col1 TEXT);
CREATE TABLE tableA (col1 INT);
INSERT INTO tableA VALUES (0);
CREATE TABLE tableB (col1 INT);
INSERT INTO tableB VALUES (0);
*/
let update = (t, table, value, delay) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(`${table} -> ${value};`);
sequelize
.query(`UPDATE ${table} SET col1 = ${value};`, {transaction: t})
.then(resolve)
.catch(reject);
}, delay);
});
};
let process1 = () => {
return sequelize.transaction((t1) => {
return writeInFoo("Process1 has started", t1).then(()=> {
return update(t1, 'tableA', 1, 0);//first update: timer to keep the right order to force a dead lock
}).then(() => {
return writeInFoo("Process1 changed tableA", t1);
}).then(() => {
return update(t1, 'tableB', 1, 10);//fourth update: timer to keep the right order to force a dead lock
}).then(() => {
return writeInFoo("Process1 changed tableB", t1);
});
}).then(() => {
return true;
}).catch((err) => {
console.error(err);
return false;
});
};
let process2 = () => {
return sequelize.transaction((t2) => {
return writeInFoo("Process2 has started", t2).then(()=> {
return update(t2, 'tableB', 2, 5);//second update: timer to keep the right order to force a dead lock
}).then(() => {
return writeInFoo("Process2 changed tableB", t2);
}).then(() => {
return update(t2, 'tableA', 2, 0);//third to keep the right order to force a dead lock
}).then(() => {
return writeInFoo("Process2 changed tableA", t2);
});
}).then(() => {
return true;
}).catch((err) => {
console.error(err);
return false;
});
};
let writeInFoo = (message, t) => {
return sequelize.query(`INSERT INTO foo VALUES (:msg);`, {
replacements: {msg: message},
type: sequelize.QueryTypes.INSERT,
transaction: t
});
};
Promise.all([process1(), process2()]).then((r) => {
console.log(r);
process.exit()
});What do you expect to happen?
Both process should be atomic.
- The table
fooshould have 3 or 6 entries. (all success or only one success) - The tables
tableAandtableBshould have all entries with the same value (all 1 or all 2). - If one of the process has been failed, the final result should have one FALSE value.
- The MySQL/MariaDb general log should not have a COMMIT query in a thread which lost the transaction.
What is actually happening?
-
The table
foohas 4 entries (3 from the succeeded process, and the last one of the failed process).

-
The tables
tableAandtableBhas different values because the last update was made by the failed process. -
Both process returns TRUE
- Both treads has all query executed, even after one of them has been rolled back.
Note that there are twoUPDATE tableB SET col1 = 1been executed! The second one is the second try done by Sequelize, and the transaction of the thread 3231 has already been rolled back at this moment.
Dialect: mysql
Dialect version: 2
Database version: 10.0.31-MariaDB-1~trusty-wsrep mariadb.org binary distribution, wsrep_25.19.rc3fc46e
Sequelize version: 4.9.0
Tested with master branch: Yes

