question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Transaction didn't rollback data changes when add unique constraint failed in MySQL

See original GitHub issue

Issue 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:closed
  • Created a year ago
  • Comments:5 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
ephyscommented, Sep 17, 2022

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

0reactions
github-actions[bot]commented, Oct 2, 2022

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. 🙂

Read more comments on GitHub >

github_iconTop 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 >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found