Bug #111703 INSERT ON DUPLICATE KEY locks INSERT if table have many PRIMARY and UNIQUE key
Submitted: 10 Jul 2023 6:40 Modified: 15 Jul 2023 1:58
Reporter: Aristotle Po Email Updates:
Status: Not a Bug Impact on me:
None 
Category:MySQL Server: Documentation Severity:S3 (Non-critical)
Version:8.0.33, 5.7.42 OS:Any
Assigned to: Jon Stephens CPU Architecture:Any
Tags: ON DUPLICATE KEY UPDATE

[10 Jul 2023 6:40] Aristotle Po
Description:
INSERT ON DUPLICATE KEY locks an INSERT of non existent record in another session when a table has multiple PRIMARY and UNIQUE key.

Server version: 8.0.33 MySQL Community Server - GPL
Server version: 5.7.42 MySQL Community Server (GPL)

######################
# Session 1
######################
t1> BEGIN;
Query OK, 0 rows affected (0.00 sec)

t1> INSERT INTO t1(a, b) values(1, 'test1') ON DUPLICATE KEY UPDATE b = VALUES(b);
Query OK, 0 rows affected, 1 warning (0.00 sec)

######################
# Session 2
######################
t2> INSERT INTO t1(a, b) values(3, 'test3'); 
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

How to repeat:
############################################
Summary of steps :
############################################
######################
# 1) create the table with multiple PK/UK
######################
CREATE TABLE t1(i int NOT NULL AUTO_INCREMENT primary key, a int NOT NULL, b varchar(9) DEFAULT NULL, UNIQUE KEY uk1(a, b));
-- OR 
-- CREATE TABLE t1(i int NOT NULL AUTO_INCREMENT primary key, a int NOT NULL, b varchar(9) NOT     NULL, UNIQUE KEY uk1(a, b));  -- make the UK to another PK my making it's column NOT NULLABLE

######################
# 2) Insert records
######################
INSERT INTO t1(a, b) values(1, 'test1'), (2, 'test2');
SHOW CREATE TABLE t1\G
SELECT * FROM t1;

######################
# 3) Session1
# mysql test --prompt='t1> '
######################
BEGIN;
INSERT INTO t1(a, b) values(1, 'test1') ON DUPLICATE KEY UPDATE b = VALUES(b);
SELECT * FROM t1;

######################
# 4) Session 2
# mysql test --prompt='t2> '
######################
INSERT INTO t1(a, b) values(3, 'test3');    -- gets lock even if new records, does not exists yet.

############################################
# Outputs :
############################################
# 1
######################
mysql [localhost:8033] {msandbox} (test) > CREATE TABLE t1(i int NOT NULL AUTO_INCREMENT primary key, a int NOT NULL, b varchar(9) DEFAULT NULL, UNIQUE KEY uk1(a, b));
Query OK, 0 rows affected (0.07 sec)

######################
# 2
######################
mysql [localhost:8033] {msandbox} (test) > INSERT INTO t1(a, b) values(1, 'test1'), (2, 'test2');
Query OK, 2 rows affected (0.01 sec)
Records: 2  Duplicates: 0  Warnings: 0

mysql [localhost:8033] {msandbox} (test) > SHOW CREATE TABLE t1\G
*************************** 1. row ***************************
       Table: t1
Create Table: CREATE TABLE `t1` (
  `i` int NOT NULL AUTO_INCREMENT,
  `a` int NOT NULL,
  `b` varchar(9) DEFAULT NULL,
  PRIMARY KEY (`i`),
  UNIQUE KEY `uk1` (`a`,`b`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)

mysql [localhost:8033] {msandbox} (test) > SELECT * FROM t1;
+---+---+-------+
| i | a | b     |
+---+---+-------+
| 1 | 1 | test1 |
| 2 | 2 | test2 |
+---+---+-------+
2 rows in set (0.00 sec)

######################
# 3) Session 1
######################
t1> BEGIN;
Query OK, 0 rows affected (0.00 sec)

t1> INSERT INTO t1(a, b) values(1, 'test1') ON DUPLICATE KEY UPDATE b = VALUES(b);
Query OK, 0 rows affected, 1 warning (0.00 sec)

t1> SELECT * FROM t1;
+---+---+-------+
| i | a | b     |
+---+---+-------+
| 1 | 1 | test1 |
| 2 | 2 | test2 |
+---+---+-------+
2 rows in set (0.00 sec)

######################
# 4) Session 2
######################
t2> INSERT INTO t1(a, b) values(3, 'test3'); 
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

Suggested fix:
If it is an expected behavior, please update the documentation
We can see notes about multiple unique from https://dev.mysql.com/doc/refman/8.0/en/insert-on-duplicate.html, but none mentioned about locking an INSERT :
> In general, you should try to avoid using an ON DUPLICATE KEY UPDATE clause on tables with multiple unique indexes.
> An INSERT ... ON DUPLICATE KEY UPDATE statement against a table having more than one unique or primary key is also marked as unsafe. (Bug #11765650, Bug #58637)

Else, please avoid the locking for new records that still does not exists.

############################################
# Possible work around
# Retain only one Primary key on a table like below :
############################################
CREATE TABLE t1(a int NOT NULL, b varchar(9)     NOT NULL, UNIQUE KEY uk1(a, b));  -- make the UK to PK by making it's column NOT NULLABLE
INSERT INTO t1(a, b) values(1, 'test1'), (2, 'test2');

######################
# Perform similar steps. It should be able to proceed without locking :
######################
t1> BEGIN;
Query OK, 0 rows affected (0.00 sec)

t1> INSERT INTO t1(a, b) values(1, 'test1') ON DUPLICATE KEY UPDATE b = VALUES(b);
Query OK, 0 rows affected, 1 warning (0.00 sec)

t2> INSERT INTO t1(a, b) values(3, 'test3'); 
Query OK, 1 row affected (0.01 sec)
[10 Jul 2023 7:15] Aristotle Po
I also got similar result with INSERT ... ON DUPLICATE KEY UPDATE on Session 2

######################
# Session 1
######################
mysql [localhost:5742] {msandbox} (test) > BEGIN;
Query OK, 0 rows affected (0.00 sec)

mysql [localhost:5742] {msandbox} (test) > INSERT INTO t1(a, b) values(1, 'test1') ON DUPLICATE KEY UPDATE b = VALUES(b);
Query OK, 0 rows affected (0.00 sec)

######################
# Session 2
######################
mysql [localhost:5742] {msandbox} (test) > INSERT INTO t1(a, b) values(3, 'test3') ON DUPLICATE KEY UPDATE a = VALUES(a), b= VALUES(b);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
[10 Jul 2023 7:43] Aristotle Po
Further testing shows the locking happens for both existing and non existing records :
##############
# Session 1
##############

t1> BEGIN;
Query OK, 0 rows affected (0.00 sec)

t1> INSERT INTO t1(a, b) values(1, 'test1') ON DUPLICATE KEY UPDATE b = VALUES(b);
Query OK, 0 rows affected (0.00 sec)

t1> SELECT * FROM t1;
+---+---+-------+
| i | a | b     |
+---+---+-------+
| 1 | 1 | test1 |
| 2 | 2 | test2 |
+---+---+-------+
2 rows in set (0.01 sec)

##############
# Session 2
##############
t2> INSERT INTO t1(a, b) values(3, 'test3') ON DUPLICATE KEY UPDATE a = VALUES(a), b= VALUES(b);  -- non existing
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
t2> INSERT INTO t1(a, b) values(2, 'test2') ON DUPLICATE KEY UPDATE a = VALUES(a), b= VALUES(b);  -- already existing
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
[10 Jul 2023 7:47] MySQL Verification Team
Hello Aristotle Po,

Thank you for the report and feedback.

regards,
Umesh
[15 Jul 2023 1:58] Jon Stephens
Hi!

I'm sorry, but this is not a bug.

Reason: We already indicate in the Manual that you should not use ON DUPLICATE KEY UPDATE with tables having multiple unique keys. (Note that primary keys are by definition also unique keys.) We provide some reasons for this. It's not an exhaustive list, nor does it need to be; we've established that it's a Bad Thing to be avoided. 

I don't mean to sound harsh, but when you go directly against advice given in the Manual, I don't think we can't really be held responsible for what happens next, and I don't see how enshrining it in our documentation provides any benefit to users.

Thanks and regards,

jon.