Skip to content

Commit 4b80c11

Browse files
MDEV-15250 UPSERT during ALTER TABLE results in 'Duplicate entry' error for alter
- InnoDB DDL results in `Duplicate entry' if concurrent DML throws duplicate key error. The following scenario explains the problem connection con1: ALTER TABLE t1 FORCE; connection con2: INSERT INTO t1(pk, uk) VALUES (2, 2), (3, 2); In connection con2, InnoDB throws the 'DUPLICATE KEY' error because of unique index. Alter operation will throw the error when applying the concurrent DML log. - Inserting the duplicate key for unique index logs the insert operation for online ALTER TABLE. When insertion fails, transaction does rollback and it leads to logging of delete operation for online ALTER TABLE. While applying the insert log entries, alter operation encounters 'DUPLICATE KEY' error. - To avoid the above fake duplicate scenario, InnoDB should not write any log for online ALTER TABLE before DML transaction commit. - User thread which does DML can apply the online log if InnoDB ran out of online log and index is marked as completed. Set online log error if apply phase encountered any error. It can also clear all other indexes log, marks the newly added indexes as corrupted. - Removed the old online code which was a part of DML operations commit_inplace_alter_table() : Does apply the online log for the last batch of secondary index log and does frees the log for the completed index. trx_t::apply_online_log: Set to true while writing the undo log if the modified table has active DDL trx_t::apply_log(): Apply the DML changes to online DDL tables dict_table_t::is_active_ddl(): Returns true if the table has an active DDL dict_index_t::online_log_make_dummy(): Assign dummy value for clustered index online log to indicate the secondary indexes are being rebuild. dict_index_t::online_log_is_dummy(): Check whether the online log has dummy value ha_innobase_inplace_ctx::log_failure(): Handle the apply log failure for online DDL transaction row_log_mark_other_online_index_abort(): Clear out all other online index log after encountering the error during row_log_apply() row_log_get_error(): Get the error happened during row_log_apply() row_log_online_op(): Does apply the online log if index is completed and ran out of memory. Returns false if apply log fails UndorecApplier: Introduced a class to maintain the undo log record, latched undo buffer page, parse the undo log record, maintain the undo record type, info bits and update vector UndorecApplier::get_old_rec(): Get the correct version of the clustered index record that was modified by the current undo log record UndorecApplier::clear_undo_rec(): Clear the undo log related information after applying the undo log record UndorecApplier::log_update(): Handle the update, delete undo log and apply it on online indexes UndorecApplier::log_insert(): Handle the insert undo log and apply it on online indexes UndorecApplier::is_same(): Check whether the given roll pointer is generated by the current undo log record information trx_t::rollback_low(): Set apply_online_log for the transaction after partially rollbacked transaction has any active DDL prepare_inplace_alter_table_dict(): After allocating the online log, InnoDB does create fulltext common tables. Fulltext index doesn't allow the index to be online. So removed the dead code of online log removal Thanks to Marko Mäkelä for providing the initial prototype and Matthias Leich for testing the issue patiently.
1 parent 4ed30b2 commit 4b80c11

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+1640
-1463
lines changed

include/my_compiler.h

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
#define MY_COMPILER_INCLUDED
33

44
/* Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
5-
Copyright (c) 2017, 2020, MariaDB Corporation.
5+
Copyright (c) 2017, 2022, MariaDB Corporation.
66
77
This program is free software; you can redistribute it and/or modify
88
it under the terms of the GNU General Public License as published by
@@ -39,15 +39,8 @@
3939

4040
/* GNU C/C++ */
4141
#if defined __GNUC__
42-
/* Convenience macro to test the minimum required GCC version. */
43-
# define MY_GNUC_PREREQ(maj, min) \
44-
((__GNUC__ << 16) + __GNUC_MINOR__ >= ((maj) << 16) + (min))
45-
/* Any after 2.95... */
4642
# define MY_ALIGN_EXT
47-
/* Comunicate to the compiler the unreachability of the code. */
48-
# if MY_GNUC_PREREQ(4,5)
49-
# define MY_ASSERT_UNREACHABLE() __builtin_unreachable()
50-
# endif
43+
# define MY_ASSERT_UNREACHABLE() __builtin_unreachable()
5144

5245
/* Microsoft Visual C++ */
5346
#elif defined _MSC_VER
@@ -158,7 +151,6 @@ struct my_aligned_storage
158151
#ifdef __GNUC__
159152
# define ATTRIBUTE_NORETURN __attribute__((noreturn))
160153
# define ATTRIBUTE_NOINLINE __attribute__((noinline))
161-
# if MY_GNUC_PREREQ(4,3)
162154
/** Starting with GCC 4.3, the "cold" attribute is used to inform the
163155
compiler that a function is unlikely executed. The function is
164156
optimized for size rather than speed and on many targets it is placed
@@ -167,8 +159,7 @@ appears close together improving code locality of non-cold parts of
167159
program. The paths leading to call of cold functions within code are
168160
marked as unlikely by the branch prediction mechanism. optimize a
169161
rarely invoked function for size instead for speed. */
170-
# define ATTRIBUTE_COLD __attribute__((cold))
171-
# endif
162+
# define ATTRIBUTE_COLD __attribute__((cold))
172163
#elif defined _MSC_VER
173164
# define ATTRIBUTE_NORETURN __declspec(noreturn)
174165
# define ATTRIBUTE_NOINLINE __declspec(noinline)

mysql-test/suite/gcol/r/innodb_virtual_debug.result

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ SET lock_wait_timeout = 1;
105105
ALTER TABLE t1 ADD UNIQUE INDEX(c, b);
106106
connection default;
107107
SET DEBUG_SYNC = 'now WAIT_FOR s1';
108-
SET DEBUG_SYNC = 'row_ins_sec_index_enter SIGNAL s2 WAIT_FOR s3';
108+
SET DEBUG_SYNC = 'row_log_insert_handle SIGNAL s2 WAIT_FOR s3';
109109
INSERT INTO t1(a, b) VALUES(2, 2);
110110
connection con1;
111111
ERROR HY000: Lock wait timeout exceeded; try restarting transaction

mysql-test/suite/gcol/t/innodb_virtual_debug.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ SET lock_wait_timeout = 1;
295295

296296
connection default;
297297
SET DEBUG_SYNC = 'now WAIT_FOR s1';
298-
SET DEBUG_SYNC = 'row_ins_sec_index_enter SIGNAL s2 WAIT_FOR s3';
298+
SET DEBUG_SYNC = 'row_log_insert_handle SIGNAL s2 WAIT_FOR s3';
299299
--send INSERT INTO t1(a, b) VALUES(2, 2)
300300

301301
connection con1;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ connection con1;
7474
SET DEBUG_SYNC='now WAIT_FOR dml';
7575
BEGIN;
7676
INSERT INTO t1 SET a=NULL;
77-
ROLLBACK;
77+
COMMIT;
7878
set DEBUG_SYNC='now SIGNAL dml_done';
7979
connection default;
8080
ERROR 22004: Invalid use of NULL value

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -169,9 +169,6 @@ INSERT INTO t1(f1, f2) VALUES(2, "This is column2 value");
169169
ROLLBACK;
170170
set DEBUG_SYNC = 'now SIGNAL insert_done';
171171
connection default;
172-
Warnings:
173-
Warning 1265 Data truncated for column 'f3' at row 3
174-
Warning 1265 Data truncated for column 'f4' at row 3
175172
SHOW CREATE TABLE t1;
176173
Table Create Table
177174
t1 CREATE TABLE `t1` (
@@ -202,6 +199,7 @@ connection default;
202199
SET DEBUG_SYNC = 'now WAIT_FOR scanned';
203200
BEGIN;
204201
INSERT INTO t1 VALUES(2,1);
202+
COMMIT;
205203
SET DEBUG_SYNC = 'now SIGNAL commit';
206204
SET DEBUG_SYNC = 'now WAIT_FOR c';
207205
SET GLOBAL innodb_fil_make_page_dirty_debug=0;
@@ -221,4 +219,5 @@ t1 CREATE TABLE `t1` (
221219
SELECT * FROM t1;
222220
a b
223221
1 1
222+
2 1
224223
DROP TABLE t1;
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
CREATE TABLE t1(f1 INT NOT NULL, f2 INT NOT NULL,
2+
f3 CHAR(200), f4 CHAR(200),
3+
PRIMARY KEY(f1))ENGINE=InnoDB;
4+
INSERT INTO t1 VALUES(6000, 6000, "InnoDB", "MariaDB");
5+
SET DEBUG_SYNC="inplace_after_index_build SIGNAL dml_start WAIT_FOR dml_commit";
6+
ALTER TABLE t1 ADD UNIQUE KEY(f2), ADD UNIQUE INDEX(f4(10));
7+
connect con1,localhost,root,,,;
8+
SET DEBUG_SYNC="now WAIT_FOR dml_start";
9+
BEGIN;
10+
DELETE FROM t1 WHERE f1= 6000;
11+
INSERT INTO t1 VALUES(6000, 6000, "InnoDB", "MariaDB");
12+
ROLLBACK;
13+
BEGIN;
14+
DELETE FROM t1 WHERE f1= 6000;
15+
INSERT INTO t1 VALUES(6000, 6000, "InnoDB", "MariaDB");
16+
INSERT INTO t1 SELECT seq, seq, repeat('a', 200), repeat('b', 200) FROM seq_1_to_4000;
17+
COMMIT;
18+
SET DEBUG_SYNC="now SIGNAL dml_commit";
19+
connection default;
20+
ERROR 23000: Duplicate entry '' for key '*UNKNOWN*'
21+
SET DEBUG_SYNC="inplace_after_index_build SIGNAL dml_start WAIT_FOR dml_commit";
22+
ALTER TABLE t1 ADD UNIQUE KEY(f2), ADD INDEX(f3(10));
23+
connection con1;
24+
SET DEBUG_SYNC="now WAIT_FOR dml_start";
25+
BEGIN;
26+
DELETE FROM t1;
27+
INSERT INTO t1 SELECT seq, seq, repeat('d', 200), repeat('e', 200) FROM
28+
seq_1_to_4000;
29+
UPDATE t1 SET f3=repeat('c', 200), f4= repeat('d', 200), f2=3;
30+
COMMIT;
31+
SET DEBUG_SYNC="now SIGNAL dml_commit";
32+
connection default;
33+
ERROR 23000: Duplicate entry '' for key '*UNKNOWN*'
34+
disconnect con1;
35+
CHECK TABLE t1;
36+
Table Op Msg_type Msg_text
37+
test.t1 check status OK
38+
DROP TABLE t1;
39+
SET DEBUG_SYNC=reset;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ begin;
1010
INSERT INTO t1 VALUES('e','e',5, 5);
1111
SET DEBUG_SYNC="now SIGNAL con1_insert";
1212
SET DEBUG_SYNC="now WAIT_FOR con1_wait";
13-
SET DEBUG_SYNC="before_row_upd_sec_new_index_entry SIGNAL con1_update WAIT_FOR alter_rollback";
13+
SET DEBUG_SYNC="after_row_upd_clust SIGNAL con1_update WAIT_FOR alter_rollback";
1414
UPDATE t1 set f4 = 10 order by f1 desc limit 2;
1515
connection default;
1616
ERROR HY000: Lock wait timeout exceeded; try restarting transaction
Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
1-
18,21c18
2-
< affected rows: 0
3-
< info: Records: 0 Duplicates: 0 Warnings: 1
4-
< Warnings:
5-
< Warning 1265 Data truncated for column 'c2' at row 3
6-
---
7-
> ERROR 01000: Data truncated for column 'c2' at row 3
8-
24c21
9-
< 2 0
10-
---
11-
> 2 NULL
1+
@@ -15,13 +15,10 @@
2+
SET DEBUG_SYNC= 'now SIGNAL flushed';
3+
affected rows: 0
4+
connection default;
5+
-affected rows: 0
6+
-info: Records: 0 Duplicates: 0 Warnings: 1
7+
-Warnings:
8+
-Warning 1265 Data truncated for column 'c2' at row 3
9+
+ERROR 22004: Invalid use of NULL value
10+
SELECT * FROM t1;
11+
c1 c2
12+
-2 0
13+
+2 NULL
14+
3 1
15+
DROP TABLE t1;
16+
CREATE TABLE t1(c1 INT NOT NULL, c2 INT, PRIMARY KEY(c1))ENGINE=INNODB;

mysql-test/suite/innodb/r/innodb-alter-debug.result

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,8 @@ SET DEBUG_SYNC = 'now SIGNAL s2';
4343
/* connection default */
4444
connection default;
4545
/* reap */ alter table t1 force, add b int, ALGORITHM=inplace;
46-
ERROR 23000: Duplicate entry '1' for key 'uk'
4746
SET DEBUG_SYNC = 'row_log_table_apply1_before SIGNAL s1 WAIT_FOR s2';
48-
alter table t1 force, add b int, ALGORITHM=inplace;;
47+
alter table t1 force, add c int, ALGORITHM=inplace;;
4948
/* connection con1 */
5049
connection con1;
5150
set DEBUG_SYNC = 'now WAIT_FOR s1';
@@ -55,7 +54,6 @@ SET DEBUG_SYNC = 'now SIGNAL s2';
5554
/* connection default */
5655
connection default;
5756
/* reap */ alter table t1 force, add b int, ALGORITHM=inplace;
58-
ERROR 23000: Duplicate entry '1' for key 'uk'
5957
SET DEBUG_SYNC = 'RESET';
6058
drop table t1;
6159
#
@@ -72,7 +70,6 @@ ERROR 23000: Duplicate entry '1' for key 'a'
7270
SET DEBUG_SYNC = 'now SIGNAL S2';
7371
disconnect con1;
7472
connection default;
75-
ERROR 23000: Duplicate entry '1' for key 'a'
7673
SET DEBUG_SYNC='RESET';
7774
DROP TABLE t1;
7875
#

mysql-test/suite/innodb/r/innodb-index-debug.result

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,20 +118,21 @@ drop table t480;
118118
# MDEV-12827 Assertion failure when reporting duplicate key error
119119
# in online table rebuild
120120
#
121-
CREATE TABLE t1 (j INT UNIQUE, i INT UNIQUE) ENGINE=InnoDB;
121+
CREATE TABLE t1 (j INT UNIQUE, i INT) ENGINE=InnoDB;
122122
INSERT INTO t1 VALUES(2, 2);
123123
connect con1,localhost,root,,test;
124124
SET DEBUG_SYNC='row_log_table_apply1_before SIGNAL built WAIT_FOR log';
125-
ALTER TABLE t1 DROP j, FORCE;
125+
ALTER TABLE t1 DROP j, ADD UNIQUE INDEX(i), FORCE;
126126
connection default;
127127
SET DEBUG_SYNC='now WAIT_FOR built';
128128
SET DEBUG_DBUG='+d,row_ins_row_level';
129129
INSERT INTO t1 (i) VALUES (0),(0);
130-
ERROR 23000: Duplicate entry '0' for key 'i'
131130
SET DEBUG_SYNC='now SIGNAL log';
132131
SET DEBUG_DBUG=@saved_debug_dbug;
133132
connection con1;
134133
ERROR 23000: Duplicate entry '0' for key 'i'
134+
DELETE FROM t1;
135+
ALTER TABLE t1 ADD UNIQUE INDEX(i);
135136
SET DEBUG_SYNC='row_log_table_apply1_before SIGNAL built2 WAIT_FOR log2';
136137
ALTER TABLE t1 DROP j, FORCE;
137138
connection default;
@@ -141,7 +142,6 @@ UPDATE t1 SET i=0;
141142
ERROR 23000: Duplicate entry '0' for key 'i'
142143
SET DEBUG_SYNC='now SIGNAL log2';
143144
connection con1;
144-
ERROR 23000: Duplicate entry '0' for key 'i'
145145
disconnect con1;
146146
connection default;
147147
SET DEBUG_SYNC='RESET';

0 commit comments

Comments
 (0)