Transaction didn't rollback data changes when add unique constraint failed in MySQL
See original GitHub issueIssue Creation Checklist
- I understand that my issue will be automatically closed if I don’t fill in the requested information
- I have read the contribution guidelines
Bug Description
Calling queryInterface.addConstraint
in a transaction, if meet duplicated error, the data changes will not be correctly rolled back.
Reproducible Example
Here is the link to the SSCCE for this issue:
const db = new Sequelize('mysql://root@localhost/nocobase_test');
const Book = db.define('Books', {
title: Sequelize.STRING,
data: Sequelize.JSON
});
await db.sync({ force: true });
await db.transaction(async transaction => {
const b1 = await Book.create({ title: '1' }, { transaction });
const b2 = await Book.create({ title: '1' }, { transaction });
await b2.update({ data: { a: true } }, { transaction });
// throw new Error('custom error');
await db.getQueryInterface().addConstraint('Books', {
type: 'unique',
fields: ['title'],
transaction
});
});
What do you expect to happen?
b1
, b2
should not be inserted into table at last.
What is actually happening?
After run the SSCCE code, the error output:
Executing (default): DROP TABLE IF EXISTS `Books`;
Executing (default): DROP TABLE IF EXISTS `Books`;
Executing (default): CREATE TABLE IF NOT EXISTS `Books` (`id` INTEGER NOT NULL auto_increment , `title` VARCHAR(255), `data` JSON, `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB;
Executing (default): SHOW INDEX FROM `Books`
Executing (294dee0a-dce0-49aa-a4fd-8ac30487c3b3): START TRANSACTION;
Executing (294dee0a-dce0-49aa-a4fd-8ac30487c3b3): INSERT INTO `Books` (`id`,`title`,`createdAt`,`updatedAt`) VALUES (DEFAULT,?,?,?);
Executing (294dee0a-dce0-49aa-a4fd-8ac30487c3b3): INSERT INTO `Books` (`id`,`title`,`createdAt`,`updatedAt`) VALUES (DEFAULT,?,?,?);
Executing (294dee0a-dce0-49aa-a4fd-8ac30487c3b3): UPDATE `Books` SET `data`=?,`updatedAt`=? WHERE `id` = ?
Executing (294dee0a-dce0-49aa-a4fd-8ac30487c3b3): ALTER TABLE `Books` ADD CONSTRAINT `Books_title_uk` UNIQUE (`title`);
Executing (294dee0a-dce0-49aa-a4fd-8ac30487c3b3): ROLLBACK;
Error
at Query.run (.../node_modules/sequelize/lib/dialects/mysql/query.js:46:25)
at .../node_modules/sequelize/lib/sequelize.js:626:28
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async MySQLQueryInterface.addConstraint (.../node_modules/sequelize/lib/dialects/abstract/query-interface.js:722:12)
at async .../db-test.js:27:5
at async .../node_modules/sequelize/lib/sequelize.js:1097:24
at async run (.../db-test.js:22:3) {
name: 'SequelizeUniqueConstraintError',
errors: [
ValidationErrorItem {
message: 'Books_title_uk must be unique',
type: 'unique violation',
path: 'Books_title_uk',
value: '1',
origin: 'DB',
instance: null,
validatorKey: 'not_unique',
validatorName: null,
validatorArgs: []
}
],
fields: { Books_title_uk: '1' },
parent: Error: Duplicate entry '1' for key 'books.Books_title_uk'
at Packet.asError (.../node_modules/mysql2/lib/packets/packet.js:728:17)
at Query.execute (.../node_modules/mysql2/lib/commands/command.js:29:26)
at Connection.handlePacket (.../node_modules/mysql2/lib/connection.js:456:32)
at PacketParser.onPacket (.../node_modules/mysql2/lib/connection.js:85:12)
at PacketParser.executeStart (.../node_modules/mysql2/lib/packet_parser.js:75:16)
at Socket.<anonymous> (.../node_modules/mysql2/lib/connection.js:92:25)
at Socket.emit (node:events:527:28)
at addChunk (node:internal/streams/readable:315:12)
at readableAddChunk (node:internal/streams/readable:289:9)
at Socket.Readable.push (node:internal/streams/readable:228:10) {
code: 'ER_DUP_ENTRY',
errno: 1062,
sqlState: '23000',
sqlMessage: "Duplicate entry '1' for key 'books.Books_title_uk'",
sql: 'ALTER TABLE `Books` ADD CONSTRAINT `Books_title_uk` UNIQUE (`title`);',
parameters: undefined
},
original: Error: Duplicate entry '1' for key 'books.Books_title_uk'
at Packet.asError (.../node_modules/mysql2/lib/packets/packet.js:728:17)
at Query.execute (.../node_modules/mysql2/lib/commands/command.js:29:26)
at Connection.handlePacket (.../node_modules/mysql2/lib/connection.js:456:32)
at PacketParser.onPacket (.../node_modules/mysql2/lib/connection.js:85:12)
at PacketParser.executeStart (.../node_modules/mysql2/lib/packet_parser.js:75:16)
at Socket.<anonymous> (.../node_modules/mysql2/lib/connection.js:92:25)
at Socket.emit (node:events:527:28)
at addChunk (node:internal/streams/readable:315:12)
at readableAddChunk (node:internal/streams/readable:289:9)
at Socket.Readable.push (node:internal/streams/readable:228:10) {
code: 'ER_DUP_ENTRY',
errno: 1062,
sqlState: '23000',
sqlMessage: "Duplicate entry '1' for key 'books.Books_title_uk'",
sql: 'ALTER TABLE `Books` ADD CONSTRAINT `Books_title_uk` UNIQUE (`title`);',
parameters: undefined
},
sql: 'ALTER TABLE `Books` ADD CONSTRAINT `Books_title_uk` UNIQUE (`title`);'
}
SQL log shows the transaction has been rolled back, but the rows were added to table:
mysql> select * from `Books`;
+----+-------+-------------+---------------------+---------------------+
| id | title | data | createdAt | updatedAt |
+----+-------+-------------+---------------------+---------------------+
| 1 | 1 | NULL | 2022-08-24 15:30:03 | 2022-08-24 15:30:03 |
| 2 | 1 | {"a": true} | 2022-08-24 15:30:03 | 2022-08-24 15:30:03 |
+----+-------+-------------+---------------------+---------------------+
2 rows in set (0.00 sec)
Environment
- Sequelize version: 6.11.0
- Node.js version: v16.15.1
- If TypeScript related: TypeScript version:
- Database & Version: mysql Ver 8.0.28 for macos12.2 on x86_64
- Connector library & Version: mysql2@2.3.3
Would you be willing to resolve this issue by submitting a Pull Request?
- Yes, I have the time and I know how to start.
- Yes, I have the time but I will need guidance.
- No, I don’t have the time, but my company or I are supporting Sequelize through donations on OpenCollective.
- No, I don’t have the time, and I understand that I will need to wait until someone from the community or maintainers is interested in resolving my issue.
Indicate your interest in the resolution of this issue by adding the 👍 reaction. Comments such as “+1” will be removed.
Issue Analytics
- State:
- Created a year ago
- Comments:5 (3 by maintainers)
Top Results From Across the Web
Duplicate UNIQUE KEY error after ROLLBACK in MySQL
In one of your comments to your own question, you're saying you're using MyISAM. However, MyISAM does not support transactions (see ref.
Read more >How to handle unique constraint exception to update row after ...
The exception comes up saying the constraint was violated, and what I would like to do then is forget the attemptd illegal addition...
Read more >MySQL Bugs: #28727: ALTER TABLE does not roll back
ROLLBACK transaction, the ALTER TABLE statement is not rolled back and instead is committed to the database. This can cause data loss.
Read more >Using SQL Constraints Within Transactions - Dummies.com
Use SQL constraints to validate your data using this guide from Dummies.com.
Read more >Handling Constraint Violations and Errors in SQL Server
Together with constraints, transactions are the best way of ensuring that the data stored within the database is consistent and error-free.
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
I could replicate the issue, but I don’t know if it’s a sequelize issue.
It is super weird. The requests are being executed, and run in the right transaction. Could it be that mysql decides to commit the transaction before running
ALTER TABLE
?Doing the same thing, but replacing the alter table with a dummy error executes the same queries (apart from alter table), and the resulting table is empty. Makes me think this is something mysql does rather than us
This issue has been automatically marked as stale because it has been open for 14 days without activity. It will be closed if no further activity occurs within the next 14 days. If this is still an issue, just leave a comment or remove the “stale” label. 🙂