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:
committed by
Georgi Kodinov
parent
bf6311c49e
commit
8226e48ab5
28
mysql-test/suite/parts/r/mdev_40995.result
Normal file
28
mysql-test/suite/parts/r/mdev_40995.result
Normal 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';
|
||||
1
mysql-test/suite/parts/t/mdev_40995-master.opt
Normal file
1
mysql-test/suite/parts/t/mdev_40995-master.opt
Normal file
@@ -0,0 +1 @@
|
||||
--innodb-autoinc-lock-mode=2
|
||||
41
mysql-test/suite/parts/t/mdev_40995.test
Normal file
41
mysql-test/suite/parts/t/mdev_40995.test
Normal 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';
|
||||
@@ -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();
|
||||
/*
|
||||
|
||||
Reference in New Issue
Block a user