Skip to content

Commit 7afee25

Browse files
MDEV-35115 Inconsistent Replace behaviour when multiple unique index exist
- Replace statement fails with duplicate key error when multiple unique key conflict happens. Reason is that Server expects the InnoDB engine to store the confliciting keys in ascending order. But the InnoDB doesn't store the conflicting keys in ascending order. Fix: === - Enable HA_DUPLICATE_KEY_NOT_IN_ORDER for InnoDB storage engine only when unique index order is different in .frm and innodb dictionary.
1 parent 98d5771 commit 7afee25

File tree

6 files changed

+156
-5
lines changed

6 files changed

+156
-5
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
--- innodb-replace.result
2+
+++ innodb-replace,INPLACE.result
3+
@@ -31,10 +31,10 @@
4+
REPLACE INTO t1 (c1,c2,c3) VALUES (0,1,b'11');
5+
SELECT * FROM INFORMATION_SCHEMA.SESSION_STATUS WHERE VARIABLE_NAME IN ('HANDLER_DELETE','HANDLER_WRITE','HANDLER_READ_KEY','HANDLER_UPDATE');
6+
VARIABLE_NAME VARIABLE_VALUE
7+
-HANDLER_DELETE 1
8+
+HANDLER_DELETE 2
9+
HANDLER_READ_KEY 2
10+
-HANDLER_UPDATE 1
11+
-HANDLER_WRITE 2
12+
+HANDLER_UPDATE 0
13+
+HANDLER_WRITE 3
14+
SELECT * FROM t1;
15+
c1 c2 c3
16+
0 1 

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

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,53 @@ ERROR HY000: DELAYED option not supported for table 't1'
1111
select * from t1;
1212
c1 c2 stamp
1313
drop table t1;
14+
#
15+
# MDEV-35115 Inconsistent Replace behaviour when multiple
16+
# unique index exist
17+
#
18+
CREATE TABLE t1 (c1 NUMERIC UNSIGNED NOT NULL,
19+
c2 INT3 UNIQUE,
20+
c3 BIT(2) PRIMARY KEY)ENGINE=InnoDB;
21+
ALTER TABLE t1 ADD UNIQUE INDEX(c1);
22+
INSERT INTO t1 (c1,c2,c3) VALUES (0,0,b'01');
23+
INSERT INTO t1 (c1,c2,c3) VALUES (1,1,b'10');
24+
FLUSH STATUS;
25+
SELECT * FROM INFORMATION_SCHEMA.SESSION_STATUS WHERE VARIABLE_NAME IN ('HANDLER_DELETE','HANDLER_WRITE','HANDLER_READ_KEY','HANDLER_UPDATE');
26+
VARIABLE_NAME VARIABLE_VALUE
27+
HANDLER_DELETE 0
28+
HANDLER_READ_KEY 0
29+
HANDLER_UPDATE 0
30+
HANDLER_WRITE 0
31+
REPLACE INTO t1 (c1,c2,c3) VALUES (0,1,b'11');
32+
SELECT * FROM INFORMATION_SCHEMA.SESSION_STATUS WHERE VARIABLE_NAME IN ('HANDLER_DELETE','HANDLER_WRITE','HANDLER_READ_KEY','HANDLER_UPDATE');
33+
VARIABLE_NAME VARIABLE_VALUE
34+
HANDLER_DELETE 1
35+
HANDLER_READ_KEY 2
36+
HANDLER_UPDATE 1
37+
HANDLER_WRITE 2
38+
SELECT * FROM t1;
39+
c1 c2 c3
40+
0 1 
41+
DROP TABLE t1;
42+
CREATE TABLE t1 (f1 INT NOT NULL PRIMARY KEY,
43+
f2 INT, f3 INT, f4 INT,
44+
UNIQUE INDEX i1(f2))ENGINE=InnoDB;
45+
ALTER TABLE t1 ADD INDEX i3(f4);
46+
ALTER TABLE t1 ADD UNIQUE INDEX i2(f3);
47+
INSERT INTO t1 VALUES (0,0,0,0);
48+
INSERT INTO t1 VALUES (1,1,1,1);
49+
FLUSH STATUS;
50+
SELECT * FROM INFORMATION_SCHEMA.SESSION_STATUS WHERE VARIABLE_NAME IN ('HANDLER_DELETE','HANDLER_WRITE','HANDLER_READ_KEY','HANDLER_UPDATE');
51+
VARIABLE_NAME VARIABLE_VALUE
52+
HANDLER_DELETE 0
53+
HANDLER_READ_KEY 0
54+
HANDLER_UPDATE 0
55+
HANDLER_WRITE 0
56+
REPLACE INTO t1 VALUES (0,0,1,1);
57+
SELECT * FROM INFORMATION_SCHEMA.SESSION_STATUS WHERE VARIABLE_NAME IN ('HANDLER_DELETE','HANDLER_WRITE','HANDLER_READ_KEY','HANDLER_UPDATE');
58+
VARIABLE_NAME VARIABLE_VALUE
59+
HANDLER_DELETE 1
60+
HANDLER_READ_KEY 2
61+
HANDLER_UPDATE 1
62+
HANDLER_WRITE 2
63+
DROP TABLE t1;
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[COPY]
2+
[INPLACE]

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

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,67 @@ select * from t1;
2020
drop table t1;
2121

2222
# End of 4.1 tests
23+
24+
--echo #
25+
--echo # MDEV-35115 Inconsistent Replace behaviour when multiple
26+
--echo # unique index exist
27+
--echo #
28+
let $get_handler_status_counts= SELECT * FROM INFORMATION_SCHEMA.SESSION_STATUS WHERE VARIABLE_NAME IN ('HANDLER_DELETE','HANDLER_WRITE','HANDLER_READ_KEY','HANDLER_UPDATE');
29+
30+
let $MYSQLD_DATADIR= `select @@datadir`;
31+
let $algorithm=`select regexp_replace('$MTR_COMBINATIONS', 'innodb,\|,innodb', '')`;
32+
33+
CREATE TABLE t1 (c1 NUMERIC UNSIGNED NOT NULL,
34+
c2 INT3 UNIQUE,
35+
c3 BIT(2) PRIMARY KEY)ENGINE=InnoDB;
36+
37+
replace_result ,ALGORITHM=COPY '' ,ALGORITHM=INPLACE '';
38+
eval ALTER TABLE t1 ADD UNIQUE INDEX(c1),ALGORITHM=$algorithm;
39+
INSERT INTO t1 (c1,c2,c3) VALUES (0,0,b'01');
40+
INSERT INTO t1 (c1,c2,c3) VALUES (1,1,b'10');
41+
42+
FLUSH STATUS;
43+
44+
--disable_ps2_protocol
45+
eval $get_handler_status_counts;
46+
--enable_ps2_protocol
47+
48+
# INPLACE algorithm appends the index, so unique index
49+
# reordering happened between innodb and .frm file. This
50+
# lead to deletion of 2 existing rows for the replace statement
51+
52+
# COPY algorithm does table rebuild everytime. No reordering
53+
# happened in this case. This lead to 1 deletion of record
54+
# and 1 update on the existing record
55+
REPLACE INTO t1 (c1,c2,c3) VALUES (0,1,b'11');
56+
57+
--disable_ps2_protocol
58+
eval $get_handler_status_counts;
59+
--enable_ps2_protocol
60+
SELECT * FROM t1;
61+
DROP TABLE t1;
62+
63+
CREATE TABLE t1 (f1 INT NOT NULL PRIMARY KEY,
64+
f2 INT, f3 INT, f4 INT,
65+
UNIQUE INDEX i1(f2))ENGINE=InnoDB;
66+
replace_result ,ALGORITHM=COPY '' ,ALGORITHM=INPLACE '';
67+
eval ALTER TABLE t1 ADD INDEX i3(f4),ALGORITHM=$algorithm;
68+
69+
replace_result ,ALGORITHM=COPY '' ,ALGORITHM=INPLACE '';
70+
eval ALTER TABLE t1 ADD UNIQUE INDEX i2(f3),ALGORITHM=$algorithm;
71+
72+
INSERT INTO t1 VALUES (0,0,0,0);
73+
INSERT INTO t1 VALUES (1,1,1,1);
74+
75+
FLUSH STATUS;
76+
--disable_ps2_protocol
77+
eval $get_handler_status_counts;
78+
--enable_ps2_protocol
79+
80+
REPLACE INTO t1 VALUES (0,0,1,1);
81+
82+
--disable_ps2_protocol
83+
eval $get_handler_status_counts;
84+
--enable_ps2_protocol
85+
86+
DROP TABLE t1;

storage/innobase/handler/ha_innodb.cc

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5590,15 +5590,15 @@ innobase_build_v_templ(
55905590
}
55915591

55925592
/** Check consistency between .frm indexes and InnoDB indexes.
5593-
@param[in] table table object formed from .frm
55945593
@param[in] ib_table InnoDB table definition
55955594
@retval true if not errors were found */
5596-
static bool
5597-
check_index_consistency(const TABLE* table, const dict_table_t* ib_table)
5595+
bool
5596+
ha_innobase::check_index_consistency(const dict_table_t* ib_table) noexcept
55985597
{
55995598
ulint mysql_num_index = table->s->keys;
56005599
ulint ib_num_index = UT_LIST_GET_LEN(ib_table->indexes);
56015600
bool ret = true;
5601+
ulint last_unique = 0;
56025602

56035603
/* If there exists inconsistency between MySQL and InnoDB dictionary
56045604
(metadata) information, the number of index defined in MySQL
@@ -5633,8 +5633,21 @@ check_index_consistency(const TABLE* table, const dict_table_t* ib_table)
56335633
ret = false;
56345634
goto func_exit;
56355635
}
5636-
}
56375636

5637+
if (index->is_unique()) {
5638+
ulint i = 0;
5639+
while ((index = UT_LIST_GET_PREV(indexes, index))) i++;
5640+
/* Check if any unique index in InnoDB
5641+
dictionary are re-ordered compared to
5642+
the index in .frm */
5643+
if (last_unique > i) {
5644+
m_int_table_flags
5645+
|= HA_DUPLICATE_KEY_NOT_IN_ORDER;
5646+
}
5647+
5648+
last_unique = i;
5649+
}
5650+
}
56385651
func_exit:
56395652
return ret;
56405653
}
@@ -5874,7 +5887,7 @@ ha_innobase::open(const char* name, int, uint)
58745887
mutex_exit(&dict_sys.mutex);
58755888
}
58765889

5877-
if (!check_index_consistency(table, ib_table)) {
5890+
if (!check_index_consistency(ib_table)) {
58785891
sql_print_error("InnoDB indexes are inconsistent with what "
58795892
"defined in .frm for table %s",
58805893
name);

storage/innobase/handler/ha_innodb.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,12 @@ class ha_innobase final : public handler
437437
const KEY_PART_INFO& old_part,
438438
const KEY_PART_INFO& new_part) const override;
439439

440+
/** Check consistency between .frm indexes and InnoDB indexes
441+
Set HA_DUPLICATE_KEY_NOT_IN_ORDER if multiple unique index
442+
are not in the correct order.
443+
@param ib_table InnoDB table definition
444+
@retval true if not errors were found */
445+
bool check_index_consistency(const dict_table_t* ib_table) noexcept;
440446
protected:
441447
bool
442448
can_convert_string(const Field_string* field,

0 commit comments

Comments
 (0)