From b03380953b43534bc4069906e838f98da4257439 Mon Sep 17 00:00:00 2001 From: greypilgrim-083 Date: Fri, 4 Sep 2026 15:15:56 +0530 Subject: [PATCH 1/2] MDEV-40995 concurrent INSERT ... SELECT on a partitioned InnoDB table can leave the table-level AUTO_INCREMENT counter behind MAX(pk), so later inserts are handed ids that already exist and fail with ER_DUP_ENTRY on the PRIMARY KEY For a partitioned InnoDB table with AUTO_INCREMENT as the leftmost PK column, values are issued by the partitioning layer from the shared counter Partition_share::next_auto_inc_val, reserved in doubling blocks (1, 2, 4, 8 ... values per reservation). At end-of-statement, ha_partition::release_auto_increment() returns unused tail values to the shared counter. When a row fails mid-statement, handler::restore_auto_increment() rolls next_insert_id back to the boundary of an earlier block. The guard in release_auto_increment() inspects only the last interval, so it passes even when the returned value is already in use by concurrent sessions. Fix: extend the guard to also verify that next_insert_id is within the statement's own reservation interval, rejecting a lowering caused by restore_auto_increment() rolling back across a block boundary.# --- .../suite/partition/r/mdev_40995.result | 21 ++++++++++ mysql-test/suite/partition/t/mdev_40995.test | 40 +++++++++++++++++++ sql/ha_partition.cc | 4 +- 3 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 mysql-test/suite/partition/r/mdev_40995.result create mode 100644 mysql-test/suite/partition/t/mdev_40995.test diff --git a/mysql-test/suite/partition/r/mdev_40995.result b/mysql-test/suite/partition/r/mdev_40995.result new file mode 100644 index 0000000000000..6f80e4184c7ba --- /dev/null +++ b/mysql-test/suite/partition/r/mdev_40995.result @@ -0,0 +1,21 @@ +# +# MDEV-40995 concurrent INSERT ... SELECT on a partitioned InnoDB table +# can leave the table-level AUTO_INCREMENT counter behind MAX(pk) +# +CREATE TABLE t1 ( + id INT AUTO_INCREMENT, + pk2 INT NOT NULL, + a INT UNIQUE, + PRIMARY KEY (id, pk2) +) ENGINE=InnoDB PARTITION BY HASH(pk2) PARTITIONS 2; +INSERT INTO t1 (pk2, a) VALUES (1, 10), (1, 100), (1, 110); +SET DEBUG_SYNC = 'ha_partition_get_auto_increment_after_reservation SIGNAL start_t2 WAIT_FOR t2_done'; +INSERT INTO t1 (pk2, a) SELECT pk2, a+10 FROM t1 ORDER BY a; +SET DEBUG_SYNC = 'now WAIT_FOR start_t2'; +INSERT INTO t1 (pk2, a) VALUES (1, 200); +SET DEBUG_SYNC = 'now SIGNAL t2_done'; +ERROR 23000: Duplicate entry '110' for key 'a' +INSERT INTO t1 (pk2, a) VALUES (1, 300); +INSERT INTO t1 (pk2, a) VALUES (1, 400); +DROP TABLE t1; +SET DEBUG_SYNC = 'RESET'; diff --git a/mysql-test/suite/partition/t/mdev_40995.test b/mysql-test/suite/partition/t/mdev_40995.test new file mode 100644 index 0000000000000..9a195be198435 --- /dev/null +++ b/mysql-test/suite/partition/t/mdev_40995.test @@ -0,0 +1,40 @@ +--source include/have_debug_sync.inc +--source include/have_partition.inc +--source include/have_innodb.inc + +--echo # +--echo # MDEV-40995 concurrent INSERT ... SELECT on a partitioned InnoDB table +--echo # can leave the table-level AUTO_INCREMENT counter behind MAX(pk) +--echo # + +CREATE TABLE t1 ( + id INT AUTO_INCREMENT, + pk2 INT NOT NULL, + a INT UNIQUE, + PRIMARY KEY (id, pk2) +) ENGINE=InnoDB PARTITION BY HASH(pk2) PARTITIONS 2; + +INSERT INTO t1 (pk2, a) VALUES (1, 10), (1, 100), (1, 110); + +--connect (con1,localhost,root,,) +--connect (con2,localhost,root,,) + +--connection con1 +SET DEBUG_SYNC = 'ha_partition_get_auto_increment_after_reservation SIGNAL start_t2 WAIT_FOR t2_done'; +--send INSERT INTO t1 (pk2, a) SELECT pk2, a+10 FROM t1 ORDER BY a; + +--connection con2 +SET DEBUG_SYNC = 'now WAIT_FOR start_t2'; +INSERT INTO t1 (pk2, a) VALUES (1, 200); +SET DEBUG_SYNC = 'now SIGNAL t2_done'; + +--connection con1 +--error ER_DUP_ENTRY +--reap + +--connection default +INSERT INTO t1 (pk2, a) VALUES (1, 300); +INSERT INTO t1 (pk2, a) VALUES (1, 400); + +DROP TABLE t1; +SET DEBUG_SYNC = 'RESET'; diff --git a/sql/ha_partition.cc b/sql/ha_partition.cc index e1e04c359211b..47dcee1eab13b 100644 --- a/sql/ha_partition.cc +++ b/sql/ha_partition.cc @@ -11107,6 +11107,7 @@ void ha_partition::get_auto_increment(ulonglong offset, ulonglong increment, part_share->next_auto_inc_val+= nb_desired_values * increment; unlock_auto_increment(); + DEBUG_SYNC(ha_thd(), "ha_partition_get_auto_increment_after_reservation"); DBUG_PRINT("info", ("*first_value: %lu", (ulong) *first_value)); *nb_reserved_values= nb_desired_values; } @@ -11139,7 +11140,8 @@ void ha_partition::release_auto_increment() we can lower the reserved value. */ if (next_insert_id < next_auto_inc_val && - auto_inc_interval_for_cur_row.maximum() >= next_auto_inc_val) + auto_inc_interval_for_cur_row.maximum() >= next_auto_inc_val && + next_insert_id >= auto_inc_interval_for_cur_row.minimum()) { THD *thd= ha_thd(); /* From 282a3439e0e24b3d39694ba486feddc7d876a332 Mon Sep 17 00:00:00 2001 From: greypilgrim-083 Date: Mon, 28 Sep 2026 23:24:04 +0530 Subject: [PATCH 2/2] Update test to properly test MDEV-40995 - Fix UNIQUE key in CREATE TABLE to include partition key - Move test to suite/parts - Add EXECUTE 1 to DEBUG_SYNC to avoid timeout - Change con2 to pk2=2 to avoid gap lock - Add mdev_40995-master.opt to use innodb-autoinc-lock-mode=2 to avoid table lock --- .../suite/partition/r/mdev_40995.result | 21 -------------- mysql-test/suite/parts/r/mdev_40995.result | 28 +++++++++++++++++++ .../suite/parts/t/mdev_40995-master.opt | 1 + .../{partition => parts}/t/mdev_40995.test | 15 +++++----- 4 files changed, 37 insertions(+), 28 deletions(-) delete mode 100644 mysql-test/suite/partition/r/mdev_40995.result create mode 100644 mysql-test/suite/parts/r/mdev_40995.result create mode 100644 mysql-test/suite/parts/t/mdev_40995-master.opt rename mysql-test/suite/{partition => parts}/t/mdev_40995.test (72%) diff --git a/mysql-test/suite/partition/r/mdev_40995.result b/mysql-test/suite/partition/r/mdev_40995.result deleted file mode 100644 index 6f80e4184c7ba..0000000000000 --- a/mysql-test/suite/partition/r/mdev_40995.result +++ /dev/null @@ -1,21 +0,0 @@ -# -# MDEV-40995 concurrent INSERT ... SELECT on a partitioned InnoDB table -# can leave the table-level AUTO_INCREMENT counter behind MAX(pk) -# -CREATE TABLE t1 ( - id INT AUTO_INCREMENT, - pk2 INT NOT NULL, - a INT UNIQUE, - PRIMARY KEY (id, pk2) -) ENGINE=InnoDB PARTITION BY HASH(pk2) PARTITIONS 2; -INSERT INTO t1 (pk2, a) VALUES (1, 10), (1, 100), (1, 110); -SET DEBUG_SYNC = 'ha_partition_get_auto_increment_after_reservation SIGNAL start_t2 WAIT_FOR t2_done'; -INSERT INTO t1 (pk2, a) SELECT pk2, a+10 FROM t1 ORDER BY a; -SET DEBUG_SYNC = 'now WAIT_FOR start_t2'; -INSERT INTO t1 (pk2, a) VALUES (1, 200); -SET DEBUG_SYNC = 'now SIGNAL t2_done'; -ERROR 23000: Duplicate entry '110' for key 'a' -INSERT INTO t1 (pk2, a) VALUES (1, 300); -INSERT INTO t1 (pk2, a) VALUES (1, 400); -DROP TABLE t1; -SET DEBUG_SYNC = 'RESET'; diff --git a/mysql-test/suite/parts/r/mdev_40995.result b/mysql-test/suite/parts/r/mdev_40995.result new file mode 100644 index 0000000000000..f187b37412ddf --- /dev/null +++ b/mysql-test/suite/parts/r/mdev_40995.result @@ -0,0 +1,28 @@ +# +# MDEV-40995 concurrent INSERT ... SELECT on a partitioned InnoDB table +# can leave the table-level AUTO_INCREMENT counter behind MAX(pk) +# +CREATE TABLE t1 ( +id INT AUTO_INCREMENT, +pk2 INT NOT NULL, +a INT, +PRIMARY KEY (id, pk2), +UNIQUE KEY (a, pk2) +) ENGINE=InnoDB PARTITION BY HASH(pk2) PARTITIONS 2; +INSERT INTO t1 (pk2, a) VALUES (1, 10), (1, 100), (1, 110); +connect con1,localhost,root,,; +connect con2,localhost,root,,; +connection con1; +SET DEBUG_SYNC = 'ha_partition_get_auto_increment_after_reservation SIGNAL start_t2 WAIT_FOR t2_done EXECUTE 1'; +INSERT INTO t1 (pk2, a) SELECT pk2, a+10 FROM t1 WHERE pk2=1 ORDER BY a;; +connection con2; +SET DEBUG_SYNC = 'now WAIT_FOR start_t2'; +INSERT INTO t1 (pk2, a) VALUES (2, 200); +SET DEBUG_SYNC = 'now SIGNAL t2_done'; +connection con1; +ERROR 23000: Duplicate entry '110-1' for key 'a' +connection default; +INSERT INTO t1 (pk2, a) VALUES (2, 300); +INSERT INTO t1 (pk2, a) VALUES (2, 400); +DROP TABLE t1; +SET DEBUG_SYNC = 'RESET'; diff --git a/mysql-test/suite/parts/t/mdev_40995-master.opt b/mysql-test/suite/parts/t/mdev_40995-master.opt new file mode 100644 index 0000000000000..571c7877f11a3 --- /dev/null +++ b/mysql-test/suite/parts/t/mdev_40995-master.opt @@ -0,0 +1 @@ +--innodb-autoinc-lock-mode=2 diff --git a/mysql-test/suite/partition/t/mdev_40995.test b/mysql-test/suite/parts/t/mdev_40995.test similarity index 72% rename from mysql-test/suite/partition/t/mdev_40995.test rename to mysql-test/suite/parts/t/mdev_40995.test index 9a195be198435..d00ad9e032d61 100644 --- a/mysql-test/suite/partition/t/mdev_40995.test +++ b/mysql-test/suite/parts/t/mdev_40995.test @@ -10,8 +10,9 @@ CREATE TABLE t1 ( id INT AUTO_INCREMENT, pk2 INT NOT NULL, - a INT UNIQUE, - PRIMARY KEY (id, pk2) + a INT, + PRIMARY KEY (id, pk2), + UNIQUE KEY (a, pk2) ) ENGINE=InnoDB PARTITION BY HASH(pk2) PARTITIONS 2; INSERT INTO t1 (pk2, a) VALUES (1, 10), (1, 100), (1, 110); @@ -20,12 +21,12 @@ INSERT INTO t1 (pk2, a) VALUES (1, 10), (1, 100), (1, 110); --connect (con2,localhost,root,,) --connection con1 -SET DEBUG_SYNC = 'ha_partition_get_auto_increment_after_reservation SIGNAL start_t2 WAIT_FOR t2_done'; ---send INSERT INTO t1 (pk2, a) SELECT pk2, a+10 FROM t1 ORDER BY a; +SET DEBUG_SYNC = 'ha_partition_get_auto_increment_after_reservation SIGNAL start_t2 WAIT_FOR t2_done EXECUTE 1'; +--send INSERT INTO t1 (pk2, a) SELECT pk2, a+10 FROM t1 WHERE pk2=1 ORDER BY a; --connection con2 SET DEBUG_SYNC = 'now WAIT_FOR start_t2'; -INSERT INTO t1 (pk2, a) VALUES (1, 200); +INSERT INTO t1 (pk2, a) VALUES (2, 200); SET DEBUG_SYNC = 'now SIGNAL t2_done'; --connection con1 @@ -33,8 +34,8 @@ SET DEBUG_SYNC = 'now SIGNAL t2_done'; --reap --connection default -INSERT INTO t1 (pk2, a) VALUES (1, 300); -INSERT INTO t1 (pk2, a) VALUES (1, 400); +INSERT INTO t1 (pk2, a) VALUES (2, 300); +INSERT INTO t1 (pk2, a) VALUES (2, 400); DROP TABLE t1; SET DEBUG_SYNC = 'RESET';