Skip to content

Commit e46c9a0

Browse files
MDEV-37296 ALTER TABLE allows adding unique hash key with duplicate values
Problem: ======= - During copy algorithm, InnoDB fails to detect the duplicate key error for unique hash key blob index. Unique HASH index treated as virtual index inside InnoDB. When table does unique hash key , server does search on the hash key before doing any insert operation and finds the duplicate value in check_duplicate_long_entry_key(). Bulk insert does all the insert together when copy of intermediate table is finished. This leads to undetection of duplicate key error while building the index. Solution: ======== - Avoid bulk insert operation when table does have unique hash key blob index. dict_table_t::can_bulk_insert(): To check whether the table is eligible for bulk insert operation during alter copy algorithm. Check whether any virtual column name starts with DB_ROW_HASH_ to know whether blob column has unique index on it.
1 parent 8c3f6a1 commit e46c9a0

File tree

4 files changed

+55
-6
lines changed

4 files changed

+55
-6
lines changed

mysql-test/suite/innodb/r/alter_copy_bulk.result

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,3 +112,25 @@ SET innodb_lock_wait_timeout=default;
112112
DROP TABLE t1;
113113
DROP TABLE IF EXISTS t2;
114114
# restart
115+
#
116+
# MDEV-37296 ALTER TABLE allows adding unique hash key
117+
# with duplicate values
118+
#
119+
CREATE TABLE t (pk INT PRIMARY KEY, a INT, b TEXT) ENGINE=InnoDB;
120+
INSERT INTO t VALUES (5,3,'foo'), (1,NULL,'bar'), (3,3,'foo');
121+
ALTER TABLE t ADD UNIQUE (a,b);
122+
ERROR 23000: Duplicate entry '3-foo' for key 'a'
123+
SHOW CREATE TABLE t;
124+
Table Create Table
125+
t CREATE TABLE `t` (
126+
`pk` int(11) NOT NULL,
127+
`a` int(11) DEFAULT NULL,
128+
`b` text DEFAULT NULL,
129+
PRIMARY KEY (`pk`)
130+
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci
131+
SELECT * FROM t;
132+
pk a b
133+
1 NULL bar
134+
3 3 foo
135+
5 3 foo
136+
DROP TABLE t;

mysql-test/suite/innodb/t/alter_copy_bulk.test

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,15 @@ SET innodb_lock_wait_timeout=default;
130130
DROP TABLE t1;
131131
DROP TABLE IF EXISTS t2;
132132
--source include/restart_mysqld.inc
133+
134+
--echo #
135+
--echo # MDEV-37296 ALTER TABLE allows adding unique hash key
136+
--echo # with duplicate values
137+
--echo #
138+
CREATE TABLE t (pk INT PRIMARY KEY, a INT, b TEXT) ENGINE=InnoDB;
139+
INSERT INTO t VALUES (5,3,'foo'), (1,NULL,'bar'), (3,3,'foo');
140+
--error ER_DUP_ENTRY
141+
ALTER TABLE t ADD UNIQUE (a,b);
142+
SHOW CREATE TABLE t;
143+
SELECT * FROM t;
144+
DROP TABLE t;

storage/innobase/include/dict0mem.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2171,6 +2171,11 @@ struct dict_table_t {
21712171
(as part of rolling back TRUNCATE) */
21722172
dberr_t rename_tablespace(span<const char> new_name, bool replace) const;
21732173

2174+
/** Whether the table is eligible to do bulk insert operation
2175+
@param trx transaction which tries to do bulk insert
2176+
@retval true if table can do bulk insert
2177+
@retval false otherwise */
2178+
bool can_bulk_insert(const trx_t &trx) const noexcept;
21742179
private:
21752180
/** Initialize instant->field_map.
21762181
@param[in] table table definition to copy from */

storage/innobase/row/row0ins.cc

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2619,6 +2619,21 @@ static uint64_t row_parse_int(const byte *data, size_t len,
26192619
return 0;
26202620
}
26212621

2622+
inline bool dict_table_t::can_bulk_insert(const trx_t &trx) const noexcept
2623+
{
2624+
if (is_temporary() || versioned() || has_spatial_index())
2625+
return false;
2626+
/* Bulk insert is not compatible with HA_CHECK_UNIQUE_AFTER_WRITE.
2627+
Refuse bulk insert if HA_KEY_ALG_LONG_HASH indexes exist.
2628+
handler::ha_check_long_uniques() assumes that all data
2629+
passed to ha_innobase::write_row() is available immediately. */
2630+
if (const char *s= v_col_names)
2631+
for (auto n= n_v_cols; n--; s+= strlen(s) + 1)
2632+
if (!strncmp(s, C_STRING_WITH_LEN("DB_ROW_HASH_")))
2633+
return false; /* make_long_hash_field_name() */
2634+
return !trx.check_foreigns || (foreign_set.empty() && referenced_set.empty());
2635+
}
2636+
26222637
/***************************************************************//**
26232638
Tries to insert an entry into a clustered index, ignoring foreign key
26242639
constraints. If a record with the same unique key is found, the other
@@ -2824,12 +2839,7 @@ row_ins_clust_index_entry_low(
28242839
/* If foreign key exist and foreign key is enabled
28252840
then avoid using bulk insert for copy algorithm */
28262841
if (innodb_alter_copy_bulk
2827-
&& !index->table->is_temporary()
2828-
&& !index->table->versioned()
2829-
&& !index->table->has_spatial_index()
2830-
&& (!trx->check_foreigns
2831-
|| (index->table->foreign_set.empty()
2832-
&& index->table->referenced_set.empty()))) {
2842+
&& index->table->can_bulk_insert(*trx)) {
28332843
ut_ad(page_is_empty(block->page.frame));
28342844
/* This code path has been executed at the
28352845
start of the alter operation. Consecutive

0 commit comments

Comments
 (0)