Bug #115644 UNIQUE constraint violations are possible in 8.0.36 (and upstream 8.0.37)
Submitted: 19 Jul 1:35 Modified: 24 Aug 5:41
Reporter: Marcos Albe (OCA) Email Updates:
Status: No Feedback Impact on me:
None 
Category:MySQL Server: InnoDB storage engine Severity:S1 (Critical)
Version:8.0.37 OS:Any
Assigned to: MySQL Verification Team CPU Architecture:Any

[19 Jul 1:35] Marcos Albe
Description:
Under some circumstances it's possible to insert duplicate values in a UNIQUE key.

How to repeat:
In a MySQL 8.0.37 with the following my.cnf:

[mysqld]
user               = marcos.albe
port               = 8037
socket             = /tmp/mysql_sandbox8037.sock
basedir            = /tmp/8.0.37
datadir            = /home/marcos.albe/sandboxes/msb_8_0_37/data
tmpdir             = /home/marcos.albe/sandboxes/msb_8_0_37/tmp
pid-file           = /home/marcos.albe/sandboxes/msb_8_0_37/data/mysql_sandbox8037.pid
bind-address       = 127.0.0.1
report-host=single-8037
report-port=8037
log-error=/home/marcos.albe/sandboxes/msb_8_0_37/data/msandbox.err
early-plugin-load=keyring_file.so
loose-keyring_file_data=/home/marcos.albe/sandboxes/msb_8_0_37/keyring-file
auto_increment_increment=2

We create table like:

mysql> CREATE DATABASE IF NOT EXISTS cdrn_olap;
mysql> USE cdrn_olap;
mysql> DROP TABLE IF EXISTS email_warning_notification;  
CREATE TABLE email_warning_notification ( email_warning_notification_key bigint NOT NULL AUTO_INCREMENT, case_id bigint NOT NULL, email varchar(255) NOT NULL, updated timestamp NULL DEFAULT NULL, created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (email_warning_notification_key), UNIQUE KEY case_id_UNIQUE (case_id,email), KEY email_idx (email) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 ENCRYPTION='y';  

Then run mysql_random_data_load (https://github.com/Percona-Lab/mysql_random_data_load) like:

~$ mysql_random_data_load --user=root --password=msandbox --host=127.0.0.1 --port=8037 --bulk-size=1000 cdrn_olap email_warning_notification 200000
~$ mysql_random_data_load --user=root --password=msandbox --host=127.0.0.1 --port=8037 --bulk-size=1000 cdrn_olap email_warning_notification 200000
~$ mysql_random_data_load --user=root --password=msandbox --host=127.0.0.1 --port=8037 --bulk-size=1000 cdrn_olap email_warning_notification 200000
~$ mysql_random_data_load --user=root --password=msandbox --host=127.0.0.1 --port=8037 --bulk-size=1000 cdrn_olap email_warning_notification 200000

The flush table for export so we can copy the ibd and companion files:

mysql> FLUSH TABLE cdrn_olap.email_warning_notification FOR EXPORT;

Then copy the files to some other folder:

~$ cp -v /home/marcos.albe/sandboxes/msb_8_0_37/data/cdrn_olap/email_warning_notification.*   /tmp/;

Then we unlock:

mysql> UNLOCK TABLES;

Then we emulate a situation where table needs to be restored:

mysql> DROP TABLE IF EXISTS email_warning_notification;  
mysql> CREATE TABLE email_warning_notification ( email_warning_notification_key bigint NOT NULL AUTO_INCREMENT, case_id bigint NOT NULL, email varchar(255) NOT NULL, updated timestamp NULL DEFAULT NULL, created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (email_warning_notification_key), UNIQUE KEY case_id_UNIQUE (case_id,email), KEY email_idx (email) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 ENCRYPTION='y';  

And we discard the tablespace to import the backup ibd we exported before:

mysql> ALTER TABLE cdrn_olap.email_warning_notification DISCARD TABLESPACE;

Then we copy the ibd and companion files back into datadir

~$ cp -v /tmp/email_warning_notification.* /home/marcos.albe/sandboxes/msb_8_0_37/data/cdrn_olap/;

And finally re-import the tablspace we had exported before

mysql> ALTER TABLE cdrn_olap.email_warning_notification IMPORT TABLESPACE;

Finally, to have some duplicate values for the UNIQUE key, I dumped  rows with mysqldump:

~$ mysqldump --set-gtid-purged=OFF --no-create-db --no-create-info --single-transaction --skip-add-drop-table --insert-ignore --extended-insert=FALSE cdrn_olap email_warning_notification  | head -n100 > /tmp/dupes_artificial.sql

And with a bit of sed we added "2" at the end of every PK so that would not conflict and leave only the UNIQUE values to generate conflicts

~$ head -n40 /tmp/dupes_artificial.sql |sed -E 's/VALUES \(([0-9]{1,3}),/VALUES \(\12,/g'

When we imported the dupes_artificial.sql some duplicates were correctly rejected…

$ ./use -f cdrn_olap < /tmp/dupes_artificial.sql
ERROR 1062 (23000) at line 24: Duplicate entry '5577006791947779410-id accusantium quam voluptas.' for key 'email_warning_notification.case_id_UNIQUE'
ERROR 1062 (23000) at line 25: Duplicate entry '167868692170975257-repellat eligendi quia et.' for key 'email_warning_notification.case_id_UNIQUE'
ERROR 1062 (23000) at line 26: Duplicate entry '7157304416132439175-et et illo rem doloribus suscipit labore.' for key 'email_warning_notification.case_id_UNIQUE'
ERROR 1062 (23000) at line 27: Duplicate entry '4763098960440079301-deserunt impedit eum.' for key 'email_warning_notification.case_id_UNIQUE'
ERROR 1062 (23000) at line 29: Duplicate entry '9007530027722078111-eos aut est repellat cum consectetur nihil u' for key 'email_warning_notification.case_id_UNIQUE'
ERROR 1062 (23000) at line 30: Duplicate entry '4690723110252556944-excepturi possimus suscipit ea illo perspici' for key 'email_warning_notification.case_id_UNIQUE'
ERROR 1062 (23000) at line 31: Duplicate entry '7338244982795680889-nulla numquam sit dicta sequi.' for key 'email_warning_notification.case_id_UNIQUE'
ERROR 1062 (23000) at line 32: Duplicate entry '7173304593106757345-deleniti dolorum ipsum.' for key 'email_warning_notification.case_id_UNIQUE'
ERROR 1062 (23000) at line 35: Duplicate entry '6446587998651698001-mollitia doloremque voluptatem qui sit.' for key 'email_warning_notification.case_id_UNIQUE'
ERROR 1062 (23000) at line 37: Duplicate entry '4391829128276964741-ad aperiam corrupti dolorem!' for key 'email_warning_notification.case_id_UNIQUE'
ERROR 1062 (23000) at line 38: Duplicate entry '6970717182405559690-molestias qui quod recusandae eaque.' for key 'email_warning_notification.case_id_UNIQUE'
ERROR 1062 (23000) at line 39: Duplicate entry '7980308103030017105-eos quia et possimus eveniet officiis dolori' for key 'email_warning_notification.case_id_UNIQUE'
ERROR 1062 (23000) at line 40: Duplicate entry '6209229671901111181-rerum aspernatur nobis atque laboriosam eum ' for key 'email_warning_notification.case_id_UNIQUE'
ERROR 1062 (23000) at line 43: Duplicate entry '8113110711788054461-et quasi atque quia.' for key 'email_warning_notification.case_id_UNIQUE'
ERROR 1062 (23000) at line 45: Duplicate entry '7072487209898683153-ut ut laudantium assumenda rerum et eius exp' for key 'email_warning_notification.case_id_UNIQUE'
ERROR 1062 (23000) at line 46: Duplicate entry '9174930518945160568-vitae esse placeat et.' for key 'email_warning_notification.case_id_UNIQUE'
ERROR 1062 (23000) at line 47: Duplicate entry '5113803198571153338-aperiam possimus exercitationem aut optio ve' for key 'email_warning_notification.case_id_UNIQUE'
ERROR 1062 (23000) at line 49: Duplicate entry '7858661751021223706-sint sit sunt.' for key 'email_warning_notification.case_id_UNIQUE'
ERROR 1062 (23000) at line 50: Duplicate entry '5826049017791960171-aut cum deserunt quo illo natus ipsa.' for key 'email_warning_notification.case_id_UNIQUE'
ERROR 1062 (23000) at line 51: Duplicate entry '7784041578866898299-et at sequi voluptas dignissimos!' for key 'email_warning_notification.case_id_UNIQUE'
ERROR 1062 (23000) at line 53: Duplicate entry '7584230277046732231-dolor accusantium quo velit sunt et quia vol' for key 'email_warning_notification.case_id_UNIQUE'
ERROR 1062 (23000) at line 54: Duplicate entry '7462603819444794546-eos ea omnis.' for key 'email_warning_notification.case_id_UNIQUE'
ERROR 1062 (23000) at line 55: Duplicate entry '726724638331233119-esse quasi ea.' for key 'email_warning_notification.case_id_UNIQUE'
ERROR 1062 (23000) at line 56: Duplicate entry '832809026058030206-sunt quisquam repudiandae sit.' for key 'email_warning_notification.case_id_UNIQUE'
ERROR 1062 (23000) at line 61: Duplicate entry '6132319959426530275-officia sit occaecati sunt magnam officiis.' for key 'email_warning_notification.case_id_UNIQUE'
ERROR 1062 (23000) at line 63: Duplicate entry '1537959233509527615-dolores nobis sapiente doloribus sint ab por' for key 'email_warning_notification.case_id_UNIQUE'
ERROR 1062 (23000) at line 64: Duplicate entry '8818970902232354457-quibusdam rerum aliquid eum qui minima et.' for key 'email_warning_notification.case_id_UNIQUE'
ERROR 1062 (23000) at line 65: Duplicate entry '8796598277769138306-incidunt minus inventore laudantium maxime i' for key 'email_warning_notification.case_id_UNIQUE'
ERROR 1062 (23000) at line 66: Duplicate entry '6105407606460537753-molestias natus laboriosam magni eos dolor.' for key 'email_warning_notification.case_id_UNIQUE'
ERROR 1062 (23000) at line 67: Duplicate entry '655858816479577792-earum iste eum placeat commodi!' for key 'email_warning_notification.case_id_UNIQUE'
ERROR 1062 (23000) at line 68: Duplicate entry '444998473070793717-quae quia quisquam et qui omnis itaque vero.' for key 'email_warning_notification.case_id_UNIQUE'

…but others went through:

mysql> SELECT GROUP_CONCAT(email_warning_notification_key), COUNT(*) AS cnt FROM email_warning_notification GROUP BY case_id,email HAVING cnt > 1;
+----------------------------------------------+-----+
| GROUP_CONCAT(email_warning_notification_key) | cnt |
+----------------------------------------------+-----+
| 91,912                                       |   2 |
| 69,692                                       |   2 |
| 37,372                                       |   2 |
| 35,352                                       |   2 |
| 41,412                                       |   2 |
| 77,772                                       |   2 |
| 57,572                                       |   2 |
| 9,92                                         |   2 |
| 71,712                                       |   2 |
| 25,252                                       |   2 |
| 49,492                                       |   2 |
| 19,192                                       |   2 |
| 21,212                                       |   2 |
| 67,672                                       |   2 |
| 73,732                                       |   2 |
+----------------------------------------------+-----+

You can see the id's are the same but with a "2" at the end.

Suggested fix:
Don't allow duplicate values for UNIQUE keys.
[19 Jul 14:29] Marcos Albe
My apologies; The sed line I provided is incorrect (it was the one I used to verify the regex was correct); So where it reads:

> ~$ head -n40 /tmp/dupes_artificial.sql |sed -E 's/VALUES \(([0-9]{1,3}),/VALUES \(\12,/g'

It should read:
> ~$ sed -i -E 's/VALUES \(([0-9]{1,3}),/VALUES \(\12,/g' /tmp/dupes_artificial.sql
[23 Jul 10:07] MySQL Verification Team
Hello Marcos,

Thank you for the report and test case.
I tried to reproduce the issue on 8.0.36 and even on 8.0.38(GA) with provided steps but not seeing the reported issue. Is there anything I'm missing here? Please let me know.

--
-- 8.0.36/8.0.38

BugNumber=115644
rm -rf $BugNumber/
bin/mysqld --no-defaults --initialize-insecure --basedir=$PWD --datadir=$PWD/$BugNumber --log-error-verbosity=3
bin/mysqld_safe --no-defaults --mysqld-version='' --basedir=$PWD --datadir=$PWD/$BugNumber --core-file --socket=/tmp/mysql.sock  --port=3306 --log-error=$PWD/$BugNumber/log.err --mysqlx-port=33330 --mysqlx-socket=/tmp/mysql_x_ushastry.sock --log-error-verbosity=3  --secure-file-priv="" --local-infile=1  --early-plugin-load=keyring_file.so --loose-keyring_file_data=keyring-file 2>&1 &

bin/mysql -uroot -S/tmp/mysql.sock
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 7
Server version: 8.0.36-commercial MySQL Enterprise Server - Commercial

Copyright (c) 2000, 2024, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> CREATE DATABASE IF NOT EXISTS cdrn_olap;
Query OK, 1 row affected (0.01 sec)

mysql> USE cdrn_olap;
Database changed
mysql> DROP TABLE IF EXISTS email_warning_notification;
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> CREATE TABLE email_warning_notification ( email_warning_notification_key bigint NOT NULL AUTO_INCREMENT, case_id bigint NOT NULL, email varchar(255) NOT NULL, updated timestamp NULL DEFAULT NULL, created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (email_warning_notification_key), UNIQUE KEY case_id_UNIQUE (case_id,email), KEY email_idx (email) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 ENCRYPTION='y';
Query OK, 0 rows affected, 1 warning (0.03 sec)

mysql> show create table email_warning_notification\G
*************************** 1. row ***************************
       Table: email_warning_notification
Create Table: CREATE TABLE `email_warning_notification` (
  `email_warning_notification_key` bigint NOT NULL AUTO_INCREMENT,
  `case_id` bigint NOT NULL,
  `email` varchar(255) NOT NULL,
  `updated` timestamp NULL DEFAULT NULL,
  `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`email_warning_notification_key`),
  UNIQUE KEY `case_id_UNIQUE` (`case_id`,`email`),
  KEY `email_idx` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 ENCRYPTION='y'
1 row in set (0.00 sec)

mysql>

-- mysql random data load 

./mysql_random_data_load --user=root --bulk-size=1000 cdrn_olap email_warning_notification 200000; ./mysql_random_data_load --user=root --bulk-size=1000 cdrn_olap email_warning_notification 200000; ./mysql_random_data_load --user=root --bulk-size=1000 cdrn_olap email_warning_notification 200000; ./mysql_random_data_load --user=root --bulk-size=1000 cdrn_olap email_warning_notification 200000
INFO[2024-07-23T11:41:19+02:00] Starting
  12s [====================================================================] 100%
INFO[2024-07-23T11:41:33+02:00] 200000 rows inserted
INFO[2024-07-23T11:41:33+02:00] Starting
  12s [====================================================================] 100%
INFO[2024-07-23T11:41:46+02:00] 200000 rows inserted
INFO[2024-07-23T11:41:46+02:00] Starting
  13s [====================================================================] 100%
INFO[2024-07-23T11:42:00+02:00] 200000 rows inserted
INFO[2024-07-23T11:42:00+02:00] Starting
  13s [====================================================================] 100%
INFO[2024-07-23T11:42:14+02:00] 200000 rows inserted

-- follow steps from the report

-- The flush table for export so we can copy the ibd and companion files:

mysql> FLUSH TABLE cdrn_olap.email_warning_notification FOR EXPORT;
Query OK, 0 rows affected (0.00 sec)

mysql>

-- Then copy the files to some other folder:

cp -v /export/home/tmp/ushastry/mysql-8.0.36/115644/cdrn_olap/email_warning_notification.*   /tmp/;
‘/export/home/tmp/ushastry/mysql-8.0.36/115644/cdrn_olap/email_warning_notification.cfg’ -> ‘/tmp/email_warning_notification.cfg’
‘/export/home/tmp/ushastry/mysql-8.0.36/115644/cdrn_olap/email_warning_notification.cfp’ -> ‘/tmp/email_warning_notification.cfp’
‘/export/home/tmp/ushastry/mysql-8.0.36/115644/cdrn_olap/email_warning_notification.ibd’ -> ‘/tmp/email_warning_notification.ibd’

-- Then we unlock:

mysql> UNLOCK TABLES;
Query OK, 0 rows affected (0.00 sec)

mysql>

-- Then we emulate a situation where table needs to be restored:

mysql> DROP TABLE IF EXISTS email_warning_notification;
Query OK, 0 rows affected (0.03 sec)

mysql> CREATE TABLE email_warning_notification ( email_warning_notification_key bigint NOT NULL AUTO_INCREMENT, case_id bigint NOT NULL, email varchar(255) NOT NULL, updated timestamp NULL DEFAULT NULL, created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (email_warning_notification_key), UNIQUE KEY case_id_UNIQUE (case_id,email), KEY email_idx (email) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
Query OK, 0 rows affected, 1 warning (0.02 sec)

mysql> -- And we discard the tablespace to import the backup ibd we exported before:
mysql>
mysql> ALTER TABLE cdrn_olap.email_warning_notification DISCARD TABLESPACE;
Query OK, 0 rows affected (0.01 sec)

mysql> -- Then we copy the ibd and companion files back into datadir
mysql>
mysql>

cp -v /tmp/email_warning_notification.* /export/home/tmp/ushastry/mysql-8.0.36/115644/cdrn_olap/;
‘/tmp/email_warning_notification.cfg’ -> ‘/export/home/tmp/ushastry/mysql-8.0.36/115644/cdrn_olap/email_warning_notification.cfg’
‘/tmp/email_warning_notification.cfp’ -> ‘/export/home/tmp/ushastry/mysql-8.0.36/115644/cdrn_olap/email_warning_notification.cfp’
‘/tmp/email_warning_notification.ibd’ -> ‘/export/home/tmp/ushastry/mysql-8.0.36/115644/cdrn_olap/email_warning_notification.ibd’

ls -lh 115644/cdrn_olap/
total 237M
-rw-r----- 1 umshastr common 1.3K Jul 23 11:55 email_warning_notification.cfg
-rw-r----- 1 umshastr common  100 Jul 23 11:55 email_warning_notification.cfp
-rw-r----- 1 umshastr common 236M Jul 23 11:57 email_warning_notification.ibd

mysql> -- And finally re-import the tablspace we had exported before
mysql>
mysql> ALTER TABLE cdrn_olap.email_warning_notification IMPORT TABLESPACE;
Query OK, 0 rows affected (0.96 sec)

mysql> -- Finally, to have some duplicate values for the UNIQUE key, I dumped  rows with mysqldump:
mysql>
mysql>

bin/mysqldump -uroot --set-gtid-purged=OFF --no-create-db --no-create-info --single-transaction --skip-add-drop-table --insert-ignore --extended-insert=FALSE cdrn_olap email_warning_notification  | head -n100 > /tmp/dupes_artificial.sql
mysqldump: Got errno 32 on write

ls -l /tmp/dupes_artificial.sql
-rw-r--r-- 1 umshastr common 13897 Jul 23 11:56 /tmp/dupes_artificial.sql
wc -l /tmp/dupes_artificial.sql
100 /tmp/dupes_artificial.sql

-- And with a bit of sed we added "2" at the end of every PK so that would not conflict and leave only the UNIQUE values to generate conflicts

sed -i -E 's/VALUES \(([0-9]{1,3}),/VALUES \(\12,/g' /tmp/dupes_artificial.sql

ls -l /tmp/dupes_artificial.sql
-rw-r--r-- 1 umshastr common 13974 Jul 23 11:57 /tmp/dupes_artificial.sql

-- When we imported the dupes_artificial.sql some duplicates were correctly rejected…

bin/mysql -uroot -S/tmp/mysql.sock -f cdrn_olap < /tmp/dupes_artificial.sql.  <-- no dups reported here

mysql> SELECT GROUP_CONCAT(email_warning_notification_key), COUNT(*) AS cnt FROM email_warning_notification GROUP BY case_id,email HAVING cnt > 1;
Empty set (0.40 sec)

mysql> SELECT GROUP_CONCAT(email_warning_notification_key), COUNT(*) AS cnt FROM email_warning_notification GROUP BY case_id,email HAVING cnt > 1;
Empty set (0.36 sec)

Sincerely,
Umesh
[23 Jul 22:59] Marcos Albe
Hello Umesh,

Thanks for looking into this; I'm genuinely perplexed you can't reproduce.  I had a colleague independently verify it was reproducible after your update, as I wanted to have someone run the steps withouth having more insight, and he indeed reproduced... but he was also using dbdeployer to create the sandbox.

I will try again, but using your mysqld/mysqld_safe commands, to see if that makes a difference. Will get back to you soon.

Thanks!
Marcos
[24 Jul 5:41] MySQL Verification Team
Hello Marcos,

Thank you for the update, will wait for your inputs.
In the meantime will check internally as well as I recall something related was reported by our Shane.

regards,
Umesh
[25 Aug 1:00] Bugs System
No feedback was provided for this bug for over a month, so it is
being suspended automatically. If you are able to provide the
information that was originally requested, please do so and change
the status of the bug back to "Open".