1
0
mirror of https://github.com/MariaDB/server.git synced 2026-10-23 18:49:29 +03:00

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.
This commit is contained in:
greypilgrim-083
2026-10-13 00:39:49 +05:30
committed by Georgi Kodinov
parent bf6311c49e
commit 8226e48ab5
4 changed files with 73 additions and 1 deletions

View File

@@ -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';

View File

@@ -0,0 +1 @@
--innodb-autoinc-lock-mode=2

View File

@@ -0,0 +1,41 @@
--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,
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';
--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 (2, 200);
SET DEBUG_SYNC = 'now SIGNAL t2_done';
--connection con1
--error ER_DUP_ENTRY
--reap
--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';

View File

@@ -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();
/*