diff options
57 files changed, 1640 insertions, 1463 deletions
diff --git a/include/my_compiler.h b/include/my_compiler.h index d35a9104c50..b87125f8a97 100644 --- a/include/my_compiler.h +++ b/include/my_compiler.h @@ -2,7 +2,7 @@ #define MY_COMPILER_INCLUDED /* Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved. - Copyright (c) 2017, 2020, MariaDB Corporation. + Copyright (c) 2017, 2022, MariaDB Corporation. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -39,15 +39,8 @@ /* GNU C/C++ */ #if defined __GNUC__ -/* Convenience macro to test the minimum required GCC version. */ -# define MY_GNUC_PREREQ(maj, min) \ - ((__GNUC__ << 16) + __GNUC_MINOR__ >= ((maj) << 16) + (min)) -/* Any after 2.95... */ # define MY_ALIGN_EXT -/* Comunicate to the compiler the unreachability of the code. */ -# if MY_GNUC_PREREQ(4,5) -# define MY_ASSERT_UNREACHABLE() __builtin_unreachable() -# endif +# define MY_ASSERT_UNREACHABLE() __builtin_unreachable() /* Microsoft Visual C++ */ #elif defined _MSC_VER @@ -158,7 +151,6 @@ struct my_aligned_storage #ifdef __GNUC__ # define ATTRIBUTE_NORETURN __attribute__((noreturn)) # define ATTRIBUTE_NOINLINE __attribute__((noinline)) -# if MY_GNUC_PREREQ(4,3) /** Starting with GCC 4.3, the "cold" attribute is used to inform the compiler that a function is unlikely executed. The function is 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 program. The paths leading to call of cold functions within code are marked as unlikely by the branch prediction mechanism. optimize a rarely invoked function for size instead for speed. */ -# define ATTRIBUTE_COLD __attribute__((cold)) -# endif +# define ATTRIBUTE_COLD __attribute__((cold)) #elif defined _MSC_VER # define ATTRIBUTE_NORETURN __declspec(noreturn) # define ATTRIBUTE_NOINLINE __declspec(noinline) diff --git a/mysql-test/suite/gcol/r/innodb_virtual_debug.result b/mysql-test/suite/gcol/r/innodb_virtual_debug.result index 806cf1a98c8..80b2bde6ca5 100644 --- a/mysql-test/suite/gcol/r/innodb_virtual_debug.result +++ b/mysql-test/suite/gcol/r/innodb_virtual_debug.result @@ -105,7 +105,7 @@ SET lock_wait_timeout = 1; ALTER TABLE t1 ADD UNIQUE INDEX(c, b); connection default; SET DEBUG_SYNC = 'now WAIT_FOR s1'; -SET DEBUG_SYNC = 'row_ins_sec_index_enter SIGNAL s2 WAIT_FOR s3'; +SET DEBUG_SYNC = 'row_log_insert_handle SIGNAL s2 WAIT_FOR s3'; INSERT INTO t1(a, b) VALUES(2, 2); connection con1; ERROR HY000: Lock wait timeout exceeded; try restarting transaction diff --git a/mysql-test/suite/gcol/t/innodb_virtual_debug.test b/mysql-test/suite/gcol/t/innodb_virtual_debug.test index 40446b991cd..5ebc90dac19 100644 --- a/mysql-test/suite/gcol/t/innodb_virtual_debug.test +++ b/mysql-test/suite/gcol/t/innodb_virtual_debug.test @@ -295,7 +295,7 @@ SET lock_wait_timeout = 1; connection default; SET DEBUG_SYNC = 'now WAIT_FOR s1'; -SET DEBUG_SYNC = 'row_ins_sec_index_enter SIGNAL s2 WAIT_FOR s3'; +SET DEBUG_SYNC = 'row_log_insert_handle SIGNAL s2 WAIT_FOR s3'; --send INSERT INTO t1(a, b) VALUES(2, 2) connection con1; diff --git a/mysql-test/suite/innodb/r/alter_candidate_key.result b/mysql-test/suite/innodb/r/alter_candidate_key.result index 79cb225e3b5..2ada5a499a8 100644 --- a/mysql-test/suite/innodb/r/alter_candidate_key.result +++ b/mysql-test/suite/innodb/r/alter_candidate_key.result @@ -74,7 +74,7 @@ connection con1; SET DEBUG_SYNC='now WAIT_FOR dml'; BEGIN; INSERT INTO t1 SET a=NULL; -ROLLBACK; +COMMIT; set DEBUG_SYNC='now SIGNAL dml_done'; connection default; ERROR 22004: Invalid use of NULL value diff --git a/mysql-test/suite/innodb/r/alter_crash.result b/mysql-test/suite/innodb/r/alter_crash.result index 46ea85d3e1e..a98aeb70a15 100644 --- a/mysql-test/suite/innodb/r/alter_crash.result +++ b/mysql-test/suite/innodb/r/alter_crash.result @@ -169,9 +169,6 @@ INSERT INTO t1(f1, f2) VALUES(2, "This is column2 value"); ROLLBACK; set DEBUG_SYNC = 'now SIGNAL insert_done'; connection default; -Warnings: -Warning 1265 Data truncated for column 'f3' at row 3 -Warning 1265 Data truncated for column 'f4' at row 3 SHOW CREATE TABLE t1; Table Create Table t1 CREATE TABLE `t1` ( @@ -202,6 +199,7 @@ connection default; SET DEBUG_SYNC = 'now WAIT_FOR scanned'; BEGIN; INSERT INTO t1 VALUES(2,1); +COMMIT; SET DEBUG_SYNC = 'now SIGNAL commit'; SET DEBUG_SYNC = 'now WAIT_FOR c'; SET GLOBAL innodb_fil_make_page_dirty_debug=0; @@ -221,4 +219,5 @@ t1 CREATE TABLE `t1` ( SELECT * FROM t1; a b 1 1 +2 1 DROP TABLE t1; diff --git a/mysql-test/suite/innodb/r/alter_dml_apply.result b/mysql-test/suite/innodb/r/alter_dml_apply.result new file mode 100644 index 00000000000..c9def9d6a41 --- /dev/null +++ b/mysql-test/suite/innodb/r/alter_dml_apply.result @@ -0,0 +1,39 @@ +CREATE TABLE t1(f1 INT NOT NULL, f2 INT NOT NULL, +f3 CHAR(200), f4 CHAR(200), +PRIMARY KEY(f1))ENGINE=InnoDB; +INSERT INTO t1 VALUES(6000, 6000, "InnoDB", "MariaDB"); +SET DEBUG_SYNC="inplace_after_index_build SIGNAL dml_start WAIT_FOR dml_commit"; +ALTER TABLE t1 ADD UNIQUE KEY(f2), ADD UNIQUE INDEX(f4(10)); +connect con1,localhost,root,,,; +SET DEBUG_SYNC="now WAIT_FOR dml_start"; +BEGIN; +DELETE FROM t1 WHERE f1= 6000; +INSERT INTO t1 VALUES(6000, 6000, "InnoDB", "MariaDB"); +ROLLBACK; +BEGIN; +DELETE FROM t1 WHERE f1= 6000; +INSERT INTO t1 VALUES(6000, 6000, "InnoDB", "MariaDB"); +INSERT INTO t1 SELECT seq, seq, repeat('a', 200), repeat('b', 200) FROM seq_1_to_4000; +COMMIT; +SET DEBUG_SYNC="now SIGNAL dml_commit"; +connection default; +ERROR 23000: Duplicate entry '' for key '*UNKNOWN*' +SET DEBUG_SYNC="inplace_after_index_build SIGNAL dml_start WAIT_FOR dml_commit"; +ALTER TABLE t1 ADD UNIQUE KEY(f2), ADD INDEX(f3(10)); +connection con1; +SET DEBUG_SYNC="now WAIT_FOR dml_start"; +BEGIN; +DELETE FROM t1; +INSERT INTO t1 SELECT seq, seq, repeat('d', 200), repeat('e', 200) FROM +seq_1_to_4000; +UPDATE t1 SET f3=repeat('c', 200), f4= repeat('d', 200), f2=3; +COMMIT; +SET DEBUG_SYNC="now SIGNAL dml_commit"; +connection default; +ERROR 23000: Duplicate entry '' for key '*UNKNOWN*' +disconnect con1; +CHECK TABLE t1; +Table Op Msg_type Msg_text +test.t1 check status OK +DROP TABLE t1; +SET DEBUG_SYNC=reset; diff --git a/mysql-test/suite/innodb/r/alter_mdl_timeout.result b/mysql-test/suite/innodb/r/alter_mdl_timeout.result index 7af1362c69e..e4fba8e260f 100644 --- a/mysql-test/suite/innodb/r/alter_mdl_timeout.result +++ b/mysql-test/suite/innodb/r/alter_mdl_timeout.result @@ -10,7 +10,7 @@ begin; INSERT INTO t1 VALUES('e','e',5, 5); SET DEBUG_SYNC="now SIGNAL con1_insert"; SET DEBUG_SYNC="now WAIT_FOR con1_wait"; -SET DEBUG_SYNC="before_row_upd_sec_new_index_entry SIGNAL con1_update WAIT_FOR alter_rollback"; +SET DEBUG_SYNC="after_row_upd_clust SIGNAL con1_update WAIT_FOR alter_rollback"; UPDATE t1 set f4 = 10 order by f1 desc limit 2; connection default; ERROR HY000: Lock wait timeout exceeded; try restarting transaction diff --git a/mysql-test/suite/innodb/r/alter_not_null_debug,STRICT.rdiff b/mysql-test/suite/innodb/r/alter_not_null_debug,STRICT.rdiff index 09c717c44b0..81466d791ac 100644 --- a/mysql-test/suite/innodb/r/alter_not_null_debug,STRICT.rdiff +++ b/mysql-test/suite/innodb/r/alter_not_null_debug,STRICT.rdiff @@ -1,11 +1,16 @@ -18,21c18 -< affected rows: 0 -< info: Records: 0 Duplicates: 0 Warnings: 1 -< Warnings: -< Warning 1265 Data truncated for column 'c2' at row 3 ---- -> ERROR 01000: Data truncated for column 'c2' at row 3 -24c21 -< 2 0 ---- -> 2 NULL +@@ -15,13 +15,10 @@ + SET DEBUG_SYNC= 'now SIGNAL flushed'; + affected rows: 0 + connection default; +-affected rows: 0 +-info: Records: 0 Duplicates: 0 Warnings: 1 +-Warnings: +-Warning 1265 Data truncated for column 'c2' at row 3 ++ERROR 22004: Invalid use of NULL value + SELECT * FROM t1; + c1 c2 +-2 0 ++2 NULL + 3 1 + DROP TABLE t1; + CREATE TABLE t1(c1 INT NOT NULL, c2 INT, PRIMARY KEY(c1))ENGINE=INNODB; diff --git a/mysql-test/suite/innodb/r/innodb-alter-debug.result b/mysql-test/suite/innodb/r/innodb-alter-debug.result index 81fc67e55c7..aae9432fc35 100644 --- a/mysql-test/suite/innodb/r/innodb-alter-debug.result +++ b/mysql-test/suite/innodb/r/innodb-alter-debug.result @@ -43,9 +43,8 @@ SET DEBUG_SYNC = 'now SIGNAL s2'; /* connection default */ connection default; /* reap */ alter table t1 force, add b int, ALGORITHM=inplace; -ERROR 23000: Duplicate entry '1' for key 'uk' SET DEBUG_SYNC = 'row_log_table_apply1_before SIGNAL s1 WAIT_FOR s2'; -alter table t1 force, add b int, ALGORITHM=inplace;; +alter table t1 force, add c int, ALGORITHM=inplace;; /* connection con1 */ connection con1; set DEBUG_SYNC = 'now WAIT_FOR s1'; @@ -55,7 +54,6 @@ SET DEBUG_SYNC = 'now SIGNAL s2'; /* connection default */ connection default; /* reap */ alter table t1 force, add b int, ALGORITHM=inplace; -ERROR 23000: Duplicate entry '1' for key 'uk' SET DEBUG_SYNC = 'RESET'; drop table t1; # @@ -72,7 +70,6 @@ ERROR 23000: Duplicate entry '1' for key 'a' SET DEBUG_SYNC = 'now SIGNAL S2'; disconnect con1; connection default; -ERROR 23000: Duplicate entry '1' for key 'a' SET DEBUG_SYNC='RESET'; DROP TABLE t1; # diff --git a/mysql-test/suite/innodb/r/innodb-index-debug.result b/mysql-test/suite/innodb/r/innodb-index-debug.result index f6b23eea41a..c36a0531b95 100644 --- a/mysql-test/suite/innodb/r/innodb-index-debug.result +++ b/mysql-test/suite/innodb/r/innodb-index-debug.result @@ -118,20 +118,21 @@ drop table t480; # MDEV-12827 Assertion failure when reporting duplicate key error # in online table rebuild # -CREATE TABLE t1 (j INT UNIQUE, i INT UNIQUE) ENGINE=InnoDB; +CREATE TABLE t1 (j INT UNIQUE, i INT) ENGINE=InnoDB; INSERT INTO t1 VALUES(2, 2); connect con1,localhost,root,,test; SET DEBUG_SYNC='row_log_table_apply1_before SIGNAL built WAIT_FOR log'; -ALTER TABLE t1 DROP j, FORCE; +ALTER TABLE t1 DROP j, ADD UNIQUE INDEX(i), FORCE; connection default; SET DEBUG_SYNC='now WAIT_FOR built'; SET DEBUG_DBUG='+d,row_ins_row_level'; INSERT INTO t1 (i) VALUES (0),(0); -ERROR 23000: Duplicate entry '0' for key 'i' SET DEBUG_SYNC='now SIGNAL log'; SET DEBUG_DBUG=@saved_debug_dbug; connection con1; ERROR 23000: Duplicate entry '0' for key 'i' +DELETE FROM t1; +ALTER TABLE t1 ADD UNIQUE INDEX(i); SET DEBUG_SYNC='row_log_table_apply1_before SIGNAL built2 WAIT_FOR log2'; ALTER TABLE t1 DROP j, FORCE; connection default; @@ -141,7 +142,6 @@ UPDATE t1 SET i=0; ERROR 23000: Duplicate entry '0' for key 'i' SET DEBUG_SYNC='now SIGNAL log2'; connection con1; -ERROR 23000: Duplicate entry '0' for key 'i' disconnect con1; connection default; SET DEBUG_SYNC='RESET'; diff --git a/mysql-test/suite/innodb/r/innodb-index-online.result b/mysql-test/suite/innodb/r/innodb-index-online.result index 1ee352cd402..be86cf7565e 100644 --- a/mysql-test/suite/innodb/r/innodb-index-online.result +++ b/mysql-test/suite/innodb/r/innodb-index-online.result @@ -85,7 +85,8 @@ ddl_sort_file_alter_table 0 ddl_log_file_alter_table 0 BEGIN; INSERT INTO t1 VALUES(7,4,2); -ROLLBACK; +COMMIT; +DELETE FROM t1 where c1 = 7; SET DEBUG_SYNC = 'now SIGNAL rollback_done'; connection con1; ERROR 23000: Duplicate entry '4' for key 'c2' @@ -96,14 +97,14 @@ SET DEBUG_SYNC = 'now WAIT_FOR created'; SELECT name, count FROM INFORMATION_SCHEMA.INNODB_METRICS WHERE subsystem = 'ddl'; name count ddl_background_drop_indexes 0 -ddl_online_create_index 0 +ddl_online_create_index 1 ddl_pending_alter_table 1 ddl_sort_file_alter_table 0 ddl_log_file_alter_table 0 INSERT INTO t1 VALUES(6,3,1); SET DEBUG_SYNC = 'now SIGNAL dml_done'; connection con1; -ERROR 23000: Duplicate entry for key 'c2' +ERROR 23000: Duplicate entry '' for key '*UNKNOWN*' DELETE FROM t1 WHERE c1=6; ALTER TABLE t1 ADD UNIQUE INDEX(c2); SELECT name, count FROM INFORMATION_SCHEMA.INNODB_METRICS WHERE subsystem = 'ddl'; @@ -243,38 +244,22 @@ ddl_online_create_index 1 ddl_pending_alter_table 1 ddl_sort_file_alter_table 0 ddl_log_file_alter_table 0 -BEGIN; -DELETE FROM t1; -ROLLBACK; UPDATE t1 SET c2 = c2 + 1; -BEGIN; +UPDATE t1 SET c2 = c2 + 2; UPDATE t1 SET c2 = c2 + 1; -DELETE FROM t1; -ROLLBACK; -BEGIN; -DELETE FROM t1; -ROLLBACK; +UPDATE t1 SET c2 = c2 + 2; UPDATE t1 SET c2 = c2 + 1; -BEGIN; +UPDATE t1 SET c2 = c2 + 2; UPDATE t1 SET c2 = c2 + 1; -DELETE FROM t1; -ROLLBACK; -BEGIN; -DELETE FROM t1; -ROLLBACK; +UPDATE t1 SET c2 = c2 + 2; UPDATE t1 SET c2 = c2 + 1; -BEGIN; +UPDATE t1 SET c2 = c2 + 2; UPDATE t1 SET c2 = c2 + 1; -DELETE FROM t1; -ROLLBACK; -BEGIN; -DELETE FROM t1; -ROLLBACK; +UPDATE t1 SET c2 = c2 + 2; UPDATE t1 SET c2 = c2 + 1; -BEGIN; +UPDATE t1 SET c2 = c2 + 2; UPDATE t1 SET c2 = c2 + 1; -DELETE FROM t1; -ROLLBACK; +UPDATE t1 SET c2 = c2 + 2; SELECT name, count FROM INFORMATION_SCHEMA.INNODB_METRICS WHERE subsystem = 'ddl'; name count ddl_background_drop_indexes 0 @@ -310,7 +295,7 @@ ERROR HY000: Creating index 'c2e' required more than 'innodb_online_alter_log_ma SELECT name, count FROM INFORMATION_SCHEMA.INNODB_METRICS WHERE subsystem = 'ddl'; name count ddl_background_drop_indexes 1 -ddl_online_create_index 0 +ddl_online_create_index 1 ddl_pending_alter_table 0 ddl_sort_file_alter_table 0 ddl_log_file_alter_table 1 @@ -321,7 +306,7 @@ name pos SELECT name, count FROM INFORMATION_SCHEMA.INNODB_METRICS WHERE subsystem = 'ddl'; name count ddl_background_drop_indexes 1 -ddl_online_create_index 0 +ddl_online_create_index 1 ddl_pending_alter_table 0 ddl_sort_file_alter_table 0 ddl_log_file_alter_table 1 @@ -361,19 +346,45 @@ ddl_log_file_alter_table 1 BEGIN; INSERT INTO t1 SELECT 320 + c1, c2, c3 FROM t1 WHERE c1 > 160; DELETE FROM t1 WHERE c1 > 320; -ROLLBACK; +COMMIT; BEGIN; UPDATE t1 SET c2 = c2 + 1; -DELETE FROM t1; -ROLLBACK; +COMMIT; BEGIN; INSERT INTO t1 SELECT 320 + c1, c2, c3 FROM t1 WHERE c1 > 160; DELETE FROM t1 WHERE c1 > 320; -ROLLBACK; +COMMIT; BEGIN; UPDATE t1 SET c2 = c2 + 1; -DELETE FROM t1; -ROLLBACK; +COMMIT; +BEGIN; +INSERT INTO t1 SELECT 320 + c1, c2, c3 FROM t1 WHERE c1 > 160; +DELETE FROM t1 WHERE c1 > 320; +COMMIT; +BEGIN; +UPDATE t1 SET c2 = c2 + 1; +COMMIT; +BEGIN; +INSERT INTO t1 SELECT 320 + c1, c2, c3 FROM t1 WHERE c1 > 160; +DELETE FROM t1 WHERE c1 > 320; +COMMIT; +BEGIN; +UPDATE t1 SET c2 = c2 + 1; +COMMIT; +BEGIN; +INSERT INTO t1 SELECT 320 + c1, c2, c3 FROM t1 WHERE c1 > 160; +DELETE FROM t1 WHERE c1 > 320; +COMMIT; +BEGIN; +UPDATE t1 SET c2 = c2 + 1; +COMMIT; +BEGIN; +INSERT INTO t1 SELECT 320 + c1, c2, c3 FROM t1 WHERE c1 > 160; +DELETE FROM t1 WHERE c1 > 320; +COMMIT; +BEGIN; +UPDATE t1 SET c2 = c2 + 1; +COMMIT; SELECT name, count FROM INFORMATION_SCHEMA.INNODB_METRICS WHERE subsystem = 'ddl'; name count ddl_background_drop_indexes 0 @@ -452,7 +463,7 @@ name pos SELECT name, count FROM INFORMATION_SCHEMA.INNODB_METRICS WHERE subsystem = 'ddl'; name count ddl_background_drop_indexes 1 -ddl_online_create_index 0 +ddl_online_create_index 1 ddl_pending_alter_table 0 ddl_sort_file_alter_table 0 ddl_log_file_alter_table 2 @@ -460,7 +471,7 @@ connection default; SELECT name, count FROM INFORMATION_SCHEMA.INNODB_METRICS WHERE subsystem = 'ddl'; name count ddl_background_drop_indexes 1 -ddl_online_create_index 0 +ddl_online_create_index 1 ddl_pending_alter_table 0 ddl_sort_file_alter_table 0 ddl_log_file_alter_table 2 @@ -498,7 +509,6 @@ SET DEBUG_SYNC = 'row_log_apply_before SIGNAL t1u_created WAIT_FOR dup_done'; ALTER TABLE t1 ADD UNIQUE(c); connection con1; SET DEBUG_SYNC = 'now WAIT_FOR t1u_created'; -BEGIN; INSERT INTO t1 VALUES('bar'),('bar'); SET DEBUG_SYNC = 'now SIGNAL dup_done'; connection default; diff --git a/mysql-test/suite/innodb/r/innodb-table-online.result b/mysql-test/suite/innodb/r/innodb-table-online.result index 91d9b355125..659d645b5cc 100644 --- a/mysql-test/suite/innodb/r/innodb-table-online.result +++ b/mysql-test/suite/innodb/r/innodb-table-online.result @@ -97,11 +97,11 @@ ddl_online_create_index 1 ddl_pending_alter_table 1 ddl_sort_file_alter_table 0 ddl_log_file_alter_table 0 -BEGIN; INSERT INTO t1 VALUES(4,7,2); SET DEBUG_SYNC = 'now SIGNAL insert_done'; connection con1; ERROR 23000: Duplicate entry '4' for key 'PRIMARY' +DELETE FROM t1 WHERE c1=4 and c2=7; connection default; ROLLBACK; connection con1; @@ -213,30 +213,22 @@ ddl_online_create_index 1 ddl_pending_alter_table 1 ddl_sort_file_alter_table 0 ddl_log_file_alter_table 1 -BEGIN; -DELETE FROM t1; -ROLLBACK; UPDATE t1 SET c2 = c2 + 1; -BEGIN; +UPDATE t1 SET c2 = c2 + 2; UPDATE t1 SET c2 = c2 + 1; -DELETE FROM t1; -ROLLBACK; -BEGIN; -DELETE FROM t1; -ROLLBACK; +UPDATE t1 SET c2 = c2 + 2; UPDATE t1 SET c2 = c2 + 1; -BEGIN; +UPDATE t1 SET c2 = c2 + 2; UPDATE t1 SET c2 = c2 + 1; -DELETE FROM t1; -ROLLBACK; -BEGIN; -DELETE FROM t1; -ROLLBACK; +UPDATE t1 SET c2 = c2 + 2; UPDATE t1 SET c2 = c2 + 1; -BEGIN; +UPDATE t1 SET c2 = c2 + 2; UPDATE t1 SET c2 = c2 + 1; -DELETE FROM t1; -ROLLBACK; +UPDATE t1 SET c2 = c2 + 2; +UPDATE t1 SET c2 = c2 + 1; +UPDATE t1 SET c2 = c2 + 2; +UPDATE t1 SET c2 = c2 + 1; +UPDATE t1 SET c2 = c2 + 2; SELECT name, count FROM INFORMATION_SCHEMA.INNODB_METRICS WHERE subsystem = 'ddl'; name count ddl_background_drop_indexes 0 @@ -286,7 +278,7 @@ SET DEBUG_SYNC = 'row_log_table_apply1_before SIGNAL rebuilt3 WAIT_FOR dml3_done ALTER TABLE t1 ADD PRIMARY KEY(c22f), CHANGE c2 c22f INT; ERROR 42000: Multiple primary key defined ALTER TABLE t1 DROP PRIMARY KEY, ADD PRIMARY KEY(c22f), CHANGE c2 c22f INT; -ERROR 23000: Duplicate entry '5' for key 'PRIMARY' +ERROR 23000: Duplicate entry '26' for key 'PRIMARY' ALTER TABLE t1 DROP PRIMARY KEY, ADD PRIMARY KEY(c22f,c1,c4(5)), CHANGE c2 c22f INT, CHANGE c3 c3 CHAR(255) NULL, CHANGE c1 c1 INT AFTER c22f, ADD COLUMN c4 VARCHAR(6) DEFAULT 'Online', LOCK=NONE; @@ -302,11 +294,8 @@ ddl_log_file_alter_table 1 BEGIN; INSERT INTO t1 SELECT 320 + c1, c2, c3 FROM t1 WHERE c1 > 240; DELETE FROM t1 WHERE c1 > 320; -ROLLBACK; -BEGIN; UPDATE t1 SET c2 = c2 + 1; -DELETE FROM t1; -ROLLBACK; +COMMIT; SELECT name, count FROM INFORMATION_SCHEMA.INNODB_METRICS WHERE subsystem = 'ddl'; name count ddl_background_drop_indexes 0 @@ -374,12 +363,11 @@ SET DEBUG_SYNC = 'now WAIT_FOR c3p5_created0'; BEGIN; INSERT INTO t1 VALUES(347,33101,'Pikku kakkosen posti','YLETV2'); INSERT INTO t1 VALUES(33101,347,NULL,''); +COMMIT; SET DEBUG_SYNC = 'now SIGNAL ins_done0'; connection con1; -ERROR 01000: Data truncated for column 'c3' at row 323 -connection default; -ROLLBACK; -connection con1; +ERROR 22004: Invalid use of NULL value +DELETE FROM t1 WHERE c1= 347 and c22f = 33101; ALTER TABLE t1 MODIFY c3 CHAR(255) NOT NULL; SET DEBUG_SYNC = 'row_log_table_apply1_before SIGNAL c3p5_created WAIT_FOR ins_done'; ALTER TABLE t1 DROP PRIMARY KEY, DROP COLUMN c22f, @@ -405,20 +393,20 @@ ddl_log_file_alter_table 2 connection default; SELECT COUNT(*) FROM t1; COUNT(*) -321 +322 ALTER TABLE t1 ROW_FORMAT=REDUNDANT; SELECT * FROM t1 LIMIT 10; c22f c1 c3 c4 -5 1 1foo Online -5 6 6foofoofoofoofoofoo Online -5 11 11foofoofoofoofoofoofoofoofoofoofoo Online -5 16 16foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo Online -5 21 21foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo Online -5 26 26foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo Online -5 31 31foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo Online -5 36 36foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo Online -5 41 41foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo Online -5 46 46foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo Online +27 1 1foo Online +27 6 6foofoofoofoofoofoo Online +27 11 11foofoofoofoofoofoofoofoofoofoofoo Online +27 16 16foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo Online +27 21 21foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo Online +27 26 26foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo Online +27 31 31foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo Online +27 36 36foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo Online +27 41 41foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo Online +27 46 46foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo Online connection con1; ALTER TABLE t1 DISCARD TABLESPACE; connection default; diff --git a/mysql-test/suite/innodb/r/insert_into_empty_debug.result b/mysql-test/suite/innodb/r/insert_into_empty_debug.result new file mode 100644 index 00000000000..0f5d6a63a17 --- /dev/null +++ b/mysql-test/suite/innodb/r/insert_into_empty_debug.result @@ -0,0 +1,61 @@ +CREATE TABLE t1(f1 INT NOT NULL, f2 INT NOT NULL, +PRIMARY KEY(f1))ENGINE=InnoDB; +INSERT INTO t1 VALUES(1, 2), (2, 2); +SET DEBUG_SYNC="innodb_rollback_inplace_alter_table SIGNAL dml_start WAIT_FOR dml_commit"; +ALTER TABLE t1 ADD UNIQUE KEY(f2); +connect con1,localhost,root,,,; +SET DEBUG_SYNC="now WAIT_FOR dml_start"; +BEGIN; +DELETE FROM t1; +SET DEBUG_SYNC="now SIGNAL dml_commit"; +connection default; +ERROR 23000: Duplicate entry '2' for key 'f2' +connection con1; +COMMIT; +TRUNCATE TABLE t1; +SET unique_checks=0, foreign_key_checks=0; +BEGIN; +INSERT INTO t1 VALUES(1, 2); +ROLLBACK; +connection default; +SELECT * FROM t1; +f1 f2 +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `f1` int(11) NOT NULL, + `f2` int(11) NOT NULL, + PRIMARY KEY (`f1`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +INSERT INTO t1 VALUES(1, 1); +connection con1; +START TRANSACTION WITH CONSISTENT SNAPSHOT; +connect con2,localhost,root,,,; +DELETE FROM t1; +connection default; +SET DEBUG_SYNC="innodb_inplace_alter_table_enter SIGNAL purge_resume WAIT_FOR dml_commit"; +ALTER TABLE t1 ADD INDEX(f2, f1); +connection con1; +COMMIT; +connection con2; +SET GLOBAL innodb_purge_rseg_truncate_frequency=1; +InnoDB 1 transactions not purged +SET unique_checks=0, foreign_key_checks=0; +BEGIN; +INSERT INTO t1 VALUES(2, 2); +ROLLBACK; +SET DEBUG_SYNC="now SIGNAL dml_commit"; +connection default; +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `f1` int(11) NOT NULL, + `f2` int(11) NOT NULL, + PRIMARY KEY (`f1`), + KEY `f2` (`f2`,`f1`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +disconnect con1; +disconnect con2; +DROP TABLE t1; +SET DEBUG_SYNC=RESET; +SET GLOBAL innodb_purge_rseg_truncate_frequency=default; diff --git a/mysql-test/suite/innodb/r/instant_alter_debug,dynamic.rdiff b/mysql-test/suite/innodb/r/instant_alter_debug,dynamic.rdiff deleted file mode 100644 index 379514edad9..00000000000 --- a/mysql-test/suite/innodb/r/instant_alter_debug,dynamic.rdiff +++ /dev/null @@ -1,6 +0,0 @@ -@@ -470,4 +470,4 @@ - FROM information_schema.global_status - WHERE variable_name = 'innodb_instant_alter_column'; - instants --33 -+32 diff --git a/mysql-test/suite/innodb/r/instant_alter_debug,redundant.rdiff b/mysql-test/suite/innodb/r/instant_alter_debug,redundant.rdiff index eafa8e5725d..cff4ff18c70 100644 --- a/mysql-test/suite/innodb/r/instant_alter_debug,redundant.rdiff +++ b/mysql-test/suite/innodb/r/instant_alter_debug,redundant.rdiff @@ -1,4 +1,4 @@ -@@ -509,4 +509,4 @@ +@@ -527,4 +527,4 @@ FROM information_schema.global_status WHERE variable_name = 'innodb_instant_alter_column'; instants diff --git a/mysql-test/suite/innodb/r/instant_alter_debug.result b/mysql-test/suite/innodb/r/instant_alter_debug.result index 82230573c44..5f74c234260 100644 --- a/mysql-test/suite/innodb/r/instant_alter_debug.result +++ b/mysql-test/suite/innodb/r/instant_alter_debug.result @@ -262,7 +262,6 @@ INSERT INTO t1 SET a=3; ROLLBACK; SET DEBUG_SYNC = 'now SIGNAL logged'; connection ddl; -ERROR 22004: Invalid use of NULL value disconnect ddl; connection default; SET DEBUG_SYNC = RESET; diff --git a/mysql-test/suite/innodb/r/online_table_rebuild.result b/mysql-test/suite/innodb/r/online_table_rebuild.result new file mode 100644 index 00000000000..0ba26fca1d9 --- /dev/null +++ b/mysql-test/suite/innodb/r/online_table_rebuild.result @@ -0,0 +1,47 @@ +CREATE TABLE t1(f1 INT NOT NULL, f2 CHAR(200), f3 CHAR(200))ENGINE=InnoDB; +INSERT INTO t1 VALUES(3, "innodb", "alter log"); +SET DEBUG_SYNC="inplace_after_index_build SIGNAL dml_start WAIT_FOR dml_commit"; +ALTER TABLE t1 ADD PRIMARY KEY(f3(10)), ADD UNIQUE KEY(f2(10)); +CONNECT con1,localhost,root,,,; +SET DEBUG_SYNC="now WAIT_FOR dml_start"; +BEGIN; +INSERT INTO t1 VALUES(1, repeat('b', 100), repeat('c', 100)); +INSERT INTO t1 VALUES(2, repeat('b', 100), repeat('a', 100)); +COMMIT; +SET DEBUG_SYNC="now SIGNAL dml_commit"; +connection default; +ERROR 23000: Duplicate entry 'bbbbbbbbbb' for key 'f2' +connection default; +SET DEBUG_SYNC="inplace_after_index_build SIGNAL dml_start WAIT_FOR dml_commit"; +ALTER TABLE t1 ADD PRIMARY KEY(f1); +connection con1; +SET DEBUG_SYNC="now WAIT_FOR dml_start"; +INSERT INTO t1 SELECT 10, repeat('a', 100), repeat('b', 100) FROM seq_1_to_4800; +SET DEBUG_SYNC="now SIGNAL dml_commit"; +connection default; +ERROR HY000: Creating index 'PRIMARY' required more than 'innodb_online_alter_log_max_size' bytes of modification log. Please try again +DELETE FROM t1; +INSERT INTO t1 VALUES(1, repeat('a', 100), repeat('b', 100)); +ALTER TABLE t1 ADD PRIMARY KEY(f1); +set DEBUG_SYNC="innodb_inplace_alter_table_enter SIGNAL dml_start WAIT_FOR dml_commit"; +ALTER TABLE t1 DROP PRIMARY KEY, ADD PRIMARY KEY(f3(10)); +connection con1; +SET DEBUG_SYNC="now WAIT_FOR dml_start"; +BEGIN; +INSERT INTO t1 VALUES(2, repeat('b', 100), repeat('c', 100)); +UPDATE t1 set f3=repeat('c', 100) where f1=1; +COMMIT; +SET DEBUG_SYNC="now SIGNAL dml_commit"; +connection default; +ERROR 23000: Duplicate entry 'cccccccccc' for key 'PRIMARY' +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `f1` int(11) NOT NULL, + `f2` char(200) DEFAULT NULL, + `f3` char(200) DEFAULT NULL, + PRIMARY KEY (`f1`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +DROP TABLE t1; +disconnect con1; +SET DEBUG_SYNC=reset; diff --git a/mysql-test/suite/innodb/r/table_definition_cache_debug.result b/mysql-test/suite/innodb/r/table_definition_cache_debug.result index df171c89cd4..6bd754aaca3 100644 --- a/mysql-test/suite/innodb/r/table_definition_cache_debug.result +++ b/mysql-test/suite/innodb/r/table_definition_cache_debug.result @@ -12,6 +12,7 @@ connection default; SET DEBUG_SYNC = 'now WAIT_FOR scanned'; BEGIN; INSERT INTO to_be_evicted VALUES(3, 2); +COMMIT; SET DEBUG_SYNC = 'now SIGNAL got_duplicate'; connection ddl; ERROR 23000: Duplicate entry '2' for key 'b' diff --git a/mysql-test/suite/innodb/t/alter_candidate_key.test b/mysql-test/suite/innodb/t/alter_candidate_key.test index 7c8f5e30993..824ad1ea799 100644 --- a/mysql-test/suite/innodb/t/alter_candidate_key.test +++ b/mysql-test/suite/innodb/t/alter_candidate_key.test @@ -50,7 +50,7 @@ connection con1; SET DEBUG_SYNC='now WAIT_FOR dml'; BEGIN; INSERT INTO t1 SET a=NULL; -ROLLBACK; +COMMIT; set DEBUG_SYNC='now SIGNAL dml_done'; connection default; --error ER_INVALID_USE_OF_NULL diff --git a/mysql-test/suite/innodb/t/alter_crash.test b/mysql-test/suite/innodb/t/alter_crash.test index 1049efd3e12..e0e294ae4f0 100644 --- a/mysql-test/suite/innodb/t/alter_crash.test +++ b/mysql-test/suite/innodb/t/alter_crash.test @@ -213,6 +213,7 @@ connection default; SET DEBUG_SYNC = 'now WAIT_FOR scanned'; BEGIN; INSERT INTO t1 VALUES(2,1); +COMMIT; SET DEBUG_SYNC = 'now SIGNAL commit'; SET DEBUG_SYNC = 'now WAIT_FOR c'; # Make all pending changes durable for recovery. diff --git a/mysql-test/suite/innodb/t/alter_dml_apply.opt b/mysql-test/suite/innodb/t/alter_dml_apply.opt new file mode 100644 index 00000000000..fa3418284bf --- /dev/null +++ b/mysql-test/suite/innodb/t/alter_dml_apply.opt @@ -0,0 +1,2 @@ +--innodb_online_alter_log_max_size=64k +--innodb_sort_buffer_size=64k diff --git a/mysql-test/suite/innodb/t/alter_dml_apply.test b/mysql-test/suite/innodb/t/alter_dml_apply.test new file mode 100644 index 00000000000..4d6fd418691 --- /dev/null +++ b/mysql-test/suite/innodb/t/alter_dml_apply.test @@ -0,0 +1,58 @@ +--source include/have_innodb.inc +--source include/have_debug.inc +--source include/have_sequence.inc + +CREATE TABLE t1(f1 INT NOT NULL, f2 INT NOT NULL, + f3 CHAR(200), f4 CHAR(200), + PRIMARY KEY(f1))ENGINE=InnoDB; +INSERT INTO t1 VALUES(6000, 6000, "InnoDB", "MariaDB"); + +# InnoDB DML thread applies the online log, aborts other online index + +SET DEBUG_SYNC="inplace_after_index_build SIGNAL dml_start WAIT_FOR dml_commit"; +SEND ALTER TABLE t1 ADD UNIQUE KEY(f2), ADD UNIQUE INDEX(f4(10)); + +# InnoDB DML thread applies insert log + +connect(con1,localhost,root,,,); +SET DEBUG_SYNC="now WAIT_FOR dml_start"; +# Rollback should avoid online index +BEGIN; +DELETE FROM t1 WHERE f1= 6000; +INSERT INTO t1 VALUES(6000, 6000, "InnoDB", "MariaDB"); +ROLLBACK; + +# Insert log will fetch the previous version in this case +BEGIN; +DELETE FROM t1 WHERE f1= 6000; +INSERT INTO t1 VALUES(6000, 6000, "InnoDB", "MariaDB"); +INSERT INTO t1 SELECT seq, seq, repeat('a', 200), repeat('b', 200) FROM seq_1_to_4000; +COMMIT; +SET DEBUG_SYNC="now SIGNAL dml_commit"; + +connection default; +--error ER_DUP_ENTRY +reap; + +# DML Thread applies update log + +SET DEBUG_SYNC="inplace_after_index_build SIGNAL dml_start WAIT_FOR dml_commit"; +SEND ALTER TABLE t1 ADD UNIQUE KEY(f2), ADD INDEX(f3(10)); + +connection con1; +SET DEBUG_SYNC="now WAIT_FOR dml_start"; +BEGIN; +DELETE FROM t1; +INSERT INTO t1 SELECT seq, seq, repeat('d', 200), repeat('e', 200) FROM +seq_1_to_4000; +UPDATE t1 SET f3=repeat('c', 200), f4= repeat('d', 200), f2=3; +COMMIT; +SET DEBUG_SYNC="now SIGNAL dml_commit"; + +connection default; +--error ER_DUP_ENTRY +reap; +disconnect con1; +CHECK TABLE t1; +DROP TABLE t1; +SET DEBUG_SYNC=reset; diff --git a/mysql-test/suite/innodb/t/alter_mdl_timeout.test b/mysql-test/suite/innodb/t/alter_mdl_timeout.test index 15e7f524fd0..ff77921b2d2 100644 --- a/mysql-test/suite/innodb/t/alter_mdl_timeout.test +++ b/mysql-test/suite/innodb/t/alter_mdl_timeout.test @@ -14,7 +14,7 @@ begin; INSERT INTO t1 VALUES('e','e',5, 5); SET DEBUG_SYNC="now SIGNAL con1_insert"; SET DEBUG_SYNC="now WAIT_FOR con1_wait"; -SET DEBUG_SYNC="before_row_upd_sec_new_index_entry SIGNAL con1_update WAIT_FOR alter_rollback"; +SET DEBUG_SYNC="after_row_upd_clust SIGNAL con1_update WAIT_FOR alter_rollback"; SEND UPDATE t1 set f4 = 10 order by f1 desc limit 2; connection default; diff --git a/mysql-test/suite/innodb/t/alter_not_null_debug.test b/mysql-test/suite/innodb/t/alter_not_null_debug.test index 9c5ba0faff0..87113b2b3f8 100644 --- a/mysql-test/suite/innodb/t/alter_not_null_debug.test +++ b/mysql-test/suite/innodb/t/alter_not_null_debug.test @@ -7,7 +7,7 @@ let $sql_mode = `SELECT @@SQL_MODE`; let $error_code = 0; if ($sql_mode == "STRICT_TRANS_TABLES") { - let $error_code = WARN_DATA_TRUNCATED; + let $error_code = ER_INVALID_USE_OF_NULL; } diff --git a/mysql-test/suite/innodb/t/innodb-alter-debug.test b/mysql-test/suite/innodb/t/innodb-alter-debug.test index 1789ec294e4..2241ef5d295 100644 --- a/mysql-test/suite/innodb/t/innodb-alter-debug.test +++ b/mysql-test/suite/innodb/t/innodb-alter-debug.test @@ -52,11 +52,10 @@ SET DEBUG_SYNC = 'now SIGNAL s2'; --echo /* connection default */ connection default; --echo /* reap */ alter table t1 force, add b int, ALGORITHM=inplace; ---error ER_DUP_ENTRY --reap SET DEBUG_SYNC = 'row_log_table_apply1_before SIGNAL s1 WAIT_FOR s2'; ---send alter table t1 force, add b int, ALGORITHM=inplace; +--send alter table t1 force, add c int, ALGORITHM=inplace; --echo /* connection con1 */ connection con1; @@ -68,7 +67,6 @@ SET DEBUG_SYNC = 'now SIGNAL s2'; --echo /* connection default */ connection default; --echo /* reap */ alter table t1 force, add b int, ALGORITHM=inplace; ---error ER_DUP_ENTRY --reap SET DEBUG_SYNC = 'RESET'; @@ -92,7 +90,6 @@ SET DEBUG_SYNC = 'now SIGNAL S2'; disconnect con1; CONNECTION default; ---error ER_DUP_ENTRY reap; SET DEBUG_SYNC='RESET'; diff --git a/mysql-test/suite/innodb/t/innodb-index-debug.test b/mysql-test/suite/innodb/t/innodb-index-debug.test index 204bdfe5540..f03ef061769 100644 --- a/mysql-test/suite/innodb/t/innodb-index-debug.test +++ b/mysql-test/suite/innodb/t/innodb-index-debug.test @@ -122,17 +122,16 @@ drop table t480; --echo # in online table rebuild --echo # -CREATE TABLE t1 (j INT UNIQUE, i INT UNIQUE) ENGINE=InnoDB; +CREATE TABLE t1 (j INT UNIQUE, i INT) ENGINE=InnoDB; INSERT INTO t1 VALUES(2, 2); --connect (con1,localhost,root,,test) SET DEBUG_SYNC='row_log_table_apply1_before SIGNAL built WAIT_FOR log'; --send -ALTER TABLE t1 DROP j, FORCE; +ALTER TABLE t1 DROP j, ADD UNIQUE INDEX(i), FORCE; --connection default SET DEBUG_SYNC='now WAIT_FOR built'; SET DEBUG_DBUG='+d,row_ins_row_level'; ---error ER_DUP_ENTRY INSERT INTO t1 (i) VALUES (0),(0); SET DEBUG_SYNC='now SIGNAL log'; SET DEBUG_DBUG=@saved_debug_dbug; @@ -140,6 +139,8 @@ SET DEBUG_DBUG=@saved_debug_dbug; --connection con1 --error ER_DUP_ENTRY reap; +DELETE FROM t1; +ALTER TABLE t1 ADD UNIQUE INDEX(i); SET DEBUG_SYNC='row_log_table_apply1_before SIGNAL built2 WAIT_FOR log2'; --send ALTER TABLE t1 DROP j, FORCE; @@ -152,7 +153,6 @@ UPDATE t1 SET i=0; SET DEBUG_SYNC='now SIGNAL log2'; --connection con1 ---error ER_DUP_ENTRY reap; --disconnect con1 --connection default diff --git a/mysql-test/suite/innodb/t/innodb-index-online.test b/mysql-test/suite/innodb/t/innodb-index-online.test index 6f30dad531d..2cb84b18402 100644 --- a/mysql-test/suite/innodb/t/innodb-index-online.test +++ b/mysql-test/suite/innodb/t/innodb-index-online.test @@ -98,7 +98,8 @@ eval $innodb_metrics_select; # Insert a duplicate entry (4) for the already started UNIQUE INDEX(c2). BEGIN; INSERT INTO t1 VALUES(7,4,2); -ROLLBACK; +COMMIT; +DELETE FROM t1 where c1 = 7; SET DEBUG_SYNC = 'now SIGNAL rollback_done'; connection con1; @@ -121,7 +122,7 @@ INSERT INTO t1 VALUES(6,3,1); SET DEBUG_SYNC = 'now SIGNAL dml_done'; connection con1; # This is due to the duplicate entry (6,3,1). ---error ER_DUP_UNKNOWN_IN_INDEX +--error ER_DUP_ENTRY reap; DELETE FROM t1 WHERE c1=6; ALTER TABLE t1 ADD UNIQUE INDEX(c2); @@ -237,17 +238,11 @@ SET DEBUG_SYNC = 'now WAIT_FOR c2e_created'; # At this point, the clustered index scan must have completed, # but the modification log keeps accumulating due to the DEBUG_SYNC. eval $innodb_metrics_select; -let $c= 4; +let $c= 8; while ($c) { - BEGIN; - DELETE FROM t1; - ROLLBACK; UPDATE t1 SET c2 = c2 + 1; - BEGIN; - UPDATE t1 SET c2 = c2 + 1; - DELETE FROM t1; - ROLLBACK; + UPDATE t1 SET c2 = c2 + 2; dec $c; } # Incomplete index c2e should exist until the DDL thread notices the overflow. @@ -325,17 +320,16 @@ connection default; SET DEBUG_SYNC = 'now WAIT_FOR c2f_created'; # Generate some log (delete-mark, delete-unmark, insert etc.) eval $innodb_metrics_select; -let $c= 2; +let $c= 6; while ($c) { BEGIN; INSERT INTO t1 SELECT 320 + c1, c2, c3 FROM t1 WHERE c1 > 160; DELETE FROM t1 WHERE c1 > 320; -ROLLBACK; +COMMIT; BEGIN; UPDATE t1 SET c2 = c2 + 1; -DELETE FROM t1; -ROLLBACK; +COMMIT; dec $c; } eval $innodb_metrics_select; @@ -481,7 +475,6 @@ send ALTER TABLE t1 ADD UNIQUE(c); connection con1; SET DEBUG_SYNC = 'now WAIT_FOR t1u_created'; -BEGIN; INSERT INTO t1 VALUES('bar'),('bar'); SET DEBUG_SYNC = 'now SIGNAL dup_done'; diff --git a/mysql-test/suite/innodb/t/innodb-table-online.test b/mysql-test/suite/innodb/t/innodb-table-online.test index 7ed87fcc26b..8e80c3fe8a3 100644 --- a/mysql-test/suite/innodb/t/innodb-table-online.test +++ b/mysql-test/suite/innodb/t/innodb-table-online.test @@ -101,7 +101,6 @@ SET DEBUG_SYNC = 'now WAIT_FOR scanned'; eval $innodb_metrics_select; # Insert a duplicate entry (4) for the already started UNIQUE INDEX(c1). -BEGIN; INSERT INTO t1 VALUES(4,7,2); SET DEBUG_SYNC = 'now SIGNAL insert_done'; @@ -111,7 +110,7 @@ connection con1; # error on the (4,7,2). --error ER_DUP_ENTRY reap; - +DELETE FROM t1 WHERE c1=4 and c2=7; connection default; ROLLBACK; @@ -204,17 +203,11 @@ UPDATE t1 SET c2 = c2 + 1; # At this point, the clustered index scan must have completed, # but the modification log keeps accumulating due to the DEBUG_SYNC. eval $innodb_metrics_select; -let $c= 3; +let $c= 8; while ($c) { - BEGIN; - DELETE FROM t1; - ROLLBACK; UPDATE t1 SET c2 = c2 + 1; - BEGIN; - UPDATE t1 SET c2 = c2 + 1; - DELETE FROM t1; - ROLLBACK; + UPDATE t1 SET c2 = c2 + 2; dec $c; } # Temporary table should exist until the DDL thread notices the overflow. @@ -279,11 +272,8 @@ eval $innodb_metrics_select; BEGIN; INSERT INTO t1 SELECT 320 + c1, c2, c3 FROM t1 WHERE c1 > 240; DELETE FROM t1 WHERE c1 > 320; -ROLLBACK; -BEGIN; UPDATE t1 SET c2 = c2 + 1; -DELETE FROM t1; -ROLLBACK; +COMMIT; eval $innodb_metrics_select; # Release con1. SET DEBUG_SYNC = 'now SIGNAL dml3_done'; @@ -346,16 +336,13 @@ SET DEBUG_SYNC = 'now WAIT_FOR c3p5_created0'; BEGIN; INSERT INTO t1 VALUES(347,33101,'Pikku kakkosen posti','YLETV2'); INSERT INTO t1 VALUES(33101,347,NULL,''); +COMMIT; SET DEBUG_SYNC = 'now SIGNAL ins_done0'; connection con1; ---error WARN_DATA_TRUNCATED +--error ER_INVALID_USE_OF_NULL reap; - -connection default; -ROLLBACK; - -connection con1; +DELETE FROM t1 WHERE c1= 347 and c22f = 33101; ALTER TABLE t1 MODIFY c3 CHAR(255) NOT NULL; SET DEBUG_SYNC = 'row_log_table_apply1_before SIGNAL c3p5_created WAIT_FOR ins_done'; diff --git a/mysql-test/suite/innodb/t/insert_into_empty_debug.test b/mysql-test/suite/innodb/t/insert_into_empty_debug.test new file mode 100644 index 00000000000..3058883a5d0 --- /dev/null +++ b/mysql-test/suite/innodb/t/insert_into_empty_debug.test @@ -0,0 +1,63 @@ +--source include/have_innodb.inc +--source include/have_debug.inc +--source include/have_debug_sync.inc + +# Encounter aborted online index during rollback of bulk insert + +CREATE TABLE t1(f1 INT NOT NULL, f2 INT NOT NULL, + PRIMARY KEY(f1))ENGINE=InnoDB; +INSERT INTO t1 VALUES(1, 2), (2, 2); + +SET DEBUG_SYNC="innodb_rollback_inplace_alter_table SIGNAL dml_start WAIT_FOR dml_commit"; +send ALTER TABLE t1 ADD UNIQUE KEY(f2); + +connect(con1,localhost,root,,,); +SET DEBUG_SYNC="now WAIT_FOR dml_start"; +BEGIN; +DELETE FROM t1; +SET DEBUG_SYNC="now SIGNAL dml_commit"; + +connection default; +--error ER_DUP_ENTRY +reap; +connection con1; +COMMIT; +TRUNCATE TABLE t1; +SET unique_checks=0, foreign_key_checks=0; +BEGIN; +INSERT INTO t1 VALUES(1, 2); +ROLLBACK; + +connection default; +SELECT * FROM t1; +SHOW CREATE TABLE t1; + +# Online alter logs ROW_LOG_EMPTY when table does bulk insert +INSERT INTO t1 VALUES(1, 1); +connection con1; +START TRANSACTION WITH CONSISTENT SNAPSHOT; +connect(con2,localhost,root,,,); +DELETE FROM t1; +connection default; +SET DEBUG_SYNC="innodb_inplace_alter_table_enter SIGNAL purge_resume WAIT_FOR dml_commit"; +send ALTER TABLE t1 ADD INDEX(f2, f1); +connection con1; +COMMIT; +connection con2; +let $wait_all_purged=1; +SET GLOBAL innodb_purge_rseg_truncate_frequency=1; +--source include/wait_all_purged.inc +SET unique_checks=0, foreign_key_checks=0; +BEGIN; +INSERT INTO t1 VALUES(2, 2); +ROLLBACK; +SET DEBUG_SYNC="now SIGNAL dml_commit"; + +connection default; +reap; +SHOW CREATE TABLE t1; +disconnect con1; +disconnect con2; +DROP TABLE t1; +SET DEBUG_SYNC=RESET; +SET GLOBAL innodb_purge_rseg_truncate_frequency=default; diff --git a/mysql-test/suite/innodb/t/instant_alter_debug.test b/mysql-test/suite/innodb/t/instant_alter_debug.test index f102185c27f..c6eca884907 100644 --- a/mysql-test/suite/innodb/t/instant_alter_debug.test +++ b/mysql-test/suite/innodb/t/instant_alter_debug.test @@ -303,7 +303,6 @@ ROLLBACK; SET DEBUG_SYNC = 'now SIGNAL logged'; connection ddl; ---error ER_INVALID_USE_OF_NULL reap; disconnect ddl; diff --git a/mysql-test/suite/innodb/t/online_table_rebuild.opt b/mysql-test/suite/innodb/t/online_table_rebuild.opt new file mode 100644 index 00000000000..fa3418284bf --- /dev/null +++ b/mysql-test/suite/innodb/t/online_table_rebuild.opt @@ -0,0 +1,2 @@ +--innodb_online_alter_log_max_size=64k +--innodb_sort_buffer_size=64k diff --git a/mysql-test/suite/innodb/t/online_table_rebuild.test b/mysql-test/suite/innodb/t/online_table_rebuild.test new file mode 100644 index 00000000000..94bac2f949c --- /dev/null +++ b/mysql-test/suite/innodb/t/online_table_rebuild.test @@ -0,0 +1,62 @@ +--source include/have_innodb.inc +--source include/have_debug.inc +--source include/have_sequence.inc + +CREATE TABLE t1(f1 INT NOT NULL, f2 CHAR(200), f3 CHAR(200))ENGINE=InnoDB; +INSERT INTO t1 VALUES(3, "innodb", "alter log"); + +# InnoDB fails with DUPLICATE KEY error in commit phase + +SET DEBUG_SYNC="inplace_after_index_build SIGNAL dml_start WAIT_FOR dml_commit"; +send ALTER TABLE t1 ADD PRIMARY KEY(f3(10)), ADD UNIQUE KEY(f2(10)); +CONNECT(con1,localhost,root,,,); +SET DEBUG_SYNC="now WAIT_FOR dml_start"; +BEGIN; +INSERT INTO t1 VALUES(1, repeat('b', 100), repeat('c', 100)); +INSERT INTO t1 VALUES(2, repeat('b', 100), repeat('a', 100)); +COMMIT; +SET DEBUG_SYNC="now SIGNAL dml_commit"; + +connection default; +--error ER_DUP_ENTRY +reap; + +# ONLINE_LOG_TOO_BIG error during commit phase + +connection default; + +SET DEBUG_SYNC="inplace_after_index_build SIGNAL dml_start WAIT_FOR dml_commit"; +SEND ALTER TABLE t1 ADD PRIMARY KEY(f1); + +connection con1; +SET DEBUG_SYNC="now WAIT_FOR dml_start"; +INSERT INTO t1 SELECT 10, repeat('a', 100), repeat('b', 100) FROM seq_1_to_4800; +SET DEBUG_SYNC="now SIGNAL dml_commit"; + +connection default; +--error ER_INNODB_ONLINE_LOG_TOO_BIG +reap; +DELETE FROM t1; +INSERT INTO t1 VALUES(1, repeat('a', 100), repeat('b', 100)); +ALTER TABLE t1 ADD PRIMARY KEY(f1); + +# Update operation leads to duplicate key error + +set DEBUG_SYNC="innodb_inplace_alter_table_enter SIGNAL dml_start WAIT_FOR dml_commit"; +SEND ALTER TABLE t1 DROP PRIMARY KEY, ADD PRIMARY KEY(f3(10)); + +connection con1; +SET DEBUG_SYNC="now WAIT_FOR dml_start"; +BEGIN; +INSERT INTO t1 VALUES(2, repeat('b', 100), repeat('c', 100)); +UPDATE t1 set f3=repeat('c', 100) where f1=1; +COMMIT; +SET DEBUG_SYNC="now SIGNAL dml_commit"; + +connection default; +--error ER_DUP_ENTRY +reap; +SHOW CREATE TABLE t1; +DROP TABLE t1; +disconnect con1; +SET DEBUG_SYNC=reset; diff --git a/mysql-test/suite/innodb/t/table_definition_cache_debug.test b/mysql-test/suite/innodb/t/table_definition_cache_debug.test index 6a466af4cc5..8950691e05c 100644 --- a/mysql-test/suite/innodb/t/table_definition_cache_debug.test +++ b/mysql-test/suite/innodb/t/table_definition_cache_debug.test @@ -29,6 +29,7 @@ SET DEBUG_SYNC = 'now WAIT_FOR scanned'; # and then hogs the table lock, so that the unique index cannot be dropped. BEGIN; INSERT INTO to_be_evicted VALUES(3, 2); +COMMIT; SET DEBUG_SYNC = 'now SIGNAL got_duplicate'; connection ddl; diff --git a/mysql-test/suite/innodb_fts/r/misc_debug.result b/mysql-test/suite/innodb_fts/r/misc_debug.result index 2a2afacb052..f40ed1fe9c3 100644 --- a/mysql-test/suite/innodb_fts/r/misc_debug.result +++ b/mysql-test/suite/innodb_fts/r/misc_debug.result @@ -65,3 +65,12 @@ ERROR HY000: Got error -1 "Internal error < 0 (Not system error)" from storage e SET debug_dbug=@saved_debug_dbug; DROP TABLE t1; # End of 10.3 tests +CREATE TABLE t1(f1 INT NOT NULL, f2 CHAR(100))ENGINE=InnoDB; +SET DEBUG_DBUG="+d,stats_lock_fail"; +ALTER TABLE t1 ADD FULLTEXT(f2); +ERROR HY000: Got error 15 "Block device required" from storage engine InnoDB +SET DEBUG_DBUG="-d,stats_lock_fail"; +ALTER TABLE t1 DISCARD TABLESPACE; +ALTER TABLE t1 ADD FULLTEXT(f2); +ERROR HY000: Tablespace has been discarded for table `t1` +DROP TABLE t1; diff --git a/mysql-test/suite/innodb_fts/t/misc_debug.test b/mysql-test/suite/innodb_fts/t/misc_debug.test index 84da1320264..6ab980a3948 100644 --- a/mysql-test/suite/innodb_fts/t/misc_debug.test +++ b/mysql-test/suite/innodb_fts/t/misc_debug.test @@ -95,3 +95,15 @@ TRUNCATE t1; SET debug_dbug=@saved_debug_dbug; DROP TABLE t1; --echo # End of 10.3 tests + +# Fulltext fails in commit phase + +CREATE TABLE t1(f1 INT NOT NULL, f2 CHAR(100))ENGINE=InnoDB; +SET DEBUG_DBUG="+d,stats_lock_fail"; +--error ER_GET_ERRNO +ALTER TABLE t1 ADD FULLTEXT(f2); +SET DEBUG_DBUG="-d,stats_lock_fail"; +ALTER TABLE t1 DISCARD TABLESPACE; +--error ER_TABLESPACE_DISCARDED +ALTER TABLE t1 ADD FULLTEXT(f2); +DROP TABLE t1; diff --git a/storage/innobase/CMakeLists.txt b/storage/innobase/CMakeLists.txt index 83d737f88ca..25ab30fe5bc 100644 --- a/storage/innobase/CMakeLists.txt +++ b/storage/innobase/CMakeLists.txt @@ -213,7 +213,6 @@ SET(INNOBASE_SOURCES include/row0import.h include/row0ins.h include/row0log.h - include/row0log.inl include/row0merge.h include/row0mysql.h include/row0purge.h diff --git a/storage/innobase/btr/btr0cur.cc b/storage/innobase/btr/btr0cur.cc index 05f3128d819..f12561c3126 100644 --- a/storage/innobase/btr/btr0cur.cc +++ b/storage/innobase/btr/btr0cur.cc @@ -5401,10 +5401,6 @@ btr_cur_del_mark_set_clust_rec( << ib::hex(trx->id) << ": " << rec_printer(rec, offsets).str()); - if (dict_index_is_online_ddl(index)) { - row_log_table_delete(rec, index, offsets, NULL); - } - btr_cur_upd_rec_sys(block, rec, index, offsets, trx, roll_ptr, mtr); return(err); } @@ -7061,8 +7057,6 @@ btr_store_big_rec_extern_fields( + prev_block->page.frame, page_no); } - } else if (dict_index_is_online_ddl(index)) { - row_log_table_blob_alloc(index, page_no); } ut_ad(!page_has_siblings(block->page.frame)); @@ -7312,8 +7306,6 @@ btr_free_externally_stored_field( page_t* page; const uint32_t space_id = mach_read_from_4( field_ref + BTR_EXTERN_SPACE_ID); - const uint32_t start_page = mach_read_from_4( - field_ref + BTR_EXTERN_PAGE_NO); uint32_t page_no; uint32_t next_page_no; mtr_t mtr; @@ -7383,10 +7375,6 @@ btr_free_externally_stored_field( return; } - if (page_no == start_page && dict_index_is_online_ddl(index)) { - row_log_table_blob_free(index, start_page); - } - ext_block = buf_page_get( page_id_t(space_id, page_no), ext_zip_size, RW_X_LATCH, &mtr); diff --git a/storage/innobase/dict/dict0dict.cc b/storage/innobase/dict/dict0dict.cc index 12b9574ecda..8c39f7318ab 100644 --- a/storage/innobase/dict/dict0dict.cc +++ b/storage/innobase/dict/dict0dict.cc @@ -2114,7 +2114,6 @@ dict_index_remove_from_cache_low( there can't be any active operations on this index (or table). */ if (index->online_log) { - ut_ad(index->online_status == ONLINE_INDEX_CREATION); row_log_free(index->online_log); index->online_log = NULL; } diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index 00bfd592ae6..5b846807295 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -5240,33 +5240,26 @@ ha_innobase::keys_to_use_for_scanning() return(&key_map_full); } -/****************************************************************//** -Ensures that if there's a concurrent inplace ADD INDEX, being-indexed virtual -columns are computed. They are not marked as indexed in the old table, so the -server won't add them to the read_set automatically */ -void -ha_innobase::column_bitmaps_signal() -/*================================*/ +/** Ensure that indexed virtual columns will be computed. */ +void ha_innobase::column_bitmaps_signal() { - if (!table->vfield || table->current_lock != F_WRLCK) { - return; - } + if (!table->vfield || table->current_lock != F_WRLCK) + return; - dict_index_t* clust_index = dict_table_get_first_index(m_prebuilt->table); - uint num_v = 0; - for (uint j = 0; j < table->s->virtual_fields; j++) { - if (table->vfield[j]->stored_in_db()) { - continue; - } + dict_index_t* clust_index= dict_table_get_first_index(m_prebuilt->table); + uint num_v= 0; + for (uint j = 0; j < table->s->virtual_fields; j++) + { + if (table->vfield[j]->stored_in_db()) + continue; - dict_col_t* col = &m_prebuilt->table->v_cols[num_v].m_col; - if (col->ord_part || - (dict_index_is_online_ddl(clust_index) && - row_log_col_is_indexed(clust_index, num_v))) { - table->mark_virtual_column_with_deps(table->vfield[j]); - } - num_v++; - } + dict_col_t *col= &m_prebuilt->table->v_cols[num_v].m_col; + if (col->ord_part || + (dict_index_is_online_ddl(clust_index) && + row_log_col_is_indexed(clust_index, num_v))) + table->mark_virtual_column_with_deps(table->vfield[j]); + num_v++; + } } @@ -8251,34 +8244,13 @@ calc_row_difference( } } -#ifdef UNIV_DEBUG - bool online_ord_part = false; -#endif - if (is_virtual) { /* If the virtual column is not indexed, we shall ignore it for update */ if (!col->ord_part) { - /* Check whether there is a table-rebuilding - online ALTER TABLE in progress, and this - virtual column could be newly indexed, thus - it will be materialized. Then we will have - to log its update. - Note, we do not support online dropping virtual - column while adding new index, nor with - online alter column order while adding index, - so the virtual column sequence must not change - if it is online operation */ - if (dict_index_is_online_ddl(clust_index) - && row_log_col_is_indexed(clust_index, - num_v)) { -#ifdef UNIV_DEBUG - online_ord_part = true; -#endif - } else { - num_v++; - continue; - } + next: + num_v++; + continue; } if (!uvect->old_vrow) { @@ -8304,8 +8276,7 @@ calc_row_difference( prebuilt, vfield, o_len, col, old_mysql_row_col, col_pack_len, buf); - num_v++; - continue; + goto next; } } @@ -8354,7 +8325,7 @@ calc_row_difference( upd_fld_set_virtual_col(ufield); ufield->field_no = num_v; - ut_ad(col->ord_part || online_ord_part); + ut_ad(col->ord_part); ufield->old_v_val = static_cast<dfield_t*>( mem_heap_alloc( uvect->heap, @@ -8439,7 +8410,7 @@ calc_row_difference( prebuilt, vfield, o_len, col, old_mysql_row_col, col_pack_len, buf); - ut_ad(col->ord_part || online_ord_part); + ut_ad(col->ord_part); num_v++; } } diff --git a/storage/innobase/handler/handler0alter.cc b/storage/innobase/handler/handler0alter.cc index c34f9b2711b..1b184422a80 100644 --- a/storage/innobase/handler/handler0alter.cc +++ b/storage/innobase/handler/handler0alter.cc @@ -836,6 +836,117 @@ inline void dict_table_t::rollback_instant( } } +/* Report an InnoDB error to the client by invoking my_error(). */ +static ATTRIBUTE_COLD __attribute__((nonnull)) +void +my_error_innodb( +/*============*/ + dberr_t error, /*!< in: InnoDB error code */ + const char* table, /*!< in: table name */ + ulint flags) /*!< in: table flags */ +{ + switch (error) { + case DB_MISSING_HISTORY: + my_error(ER_TABLE_DEF_CHANGED, MYF(0)); + break; + case DB_RECORD_NOT_FOUND: + my_error(ER_KEY_NOT_FOUND, MYF(0), table); + break; + case DB_DEADLOCK: + my_error(ER_LOCK_DEADLOCK, MYF(0)); + break; + case DB_LOCK_WAIT_TIMEOUT: + my_error(ER_LOCK_WAIT_TIMEOUT, MYF(0)); + break; + case DB_INTERRUPTED: + my_error(ER_QUERY_INTERRUPTED, MYF(0)); + break; + case DB_OUT_OF_MEMORY: + my_error(ER_OUT_OF_RESOURCES, MYF(0)); + break; + case DB_OUT_OF_FILE_SPACE: + my_error(ER_RECORD_FILE_FULL, MYF(0), table); + break; + case DB_TEMP_FILE_WRITE_FAIL: + my_error(ER_TEMP_FILE_WRITE_FAILURE, MYF(0)); + break; + case DB_TOO_BIG_INDEX_COL: + my_error(ER_INDEX_COLUMN_TOO_LONG, MYF(0), + (ulong) DICT_MAX_FIELD_LEN_BY_FORMAT_FLAG(flags)); + break; + case DB_TOO_MANY_CONCURRENT_TRXS: + my_error(ER_TOO_MANY_CONCURRENT_TRXS, MYF(0)); + break; + case DB_LOCK_TABLE_FULL: + my_error(ER_LOCK_TABLE_FULL, MYF(0)); + break; + case DB_UNDO_RECORD_TOO_BIG: + my_error(ER_UNDO_RECORD_TOO_BIG, MYF(0)); + break; + case DB_CORRUPTION: + my_error(ER_NOT_KEYFILE, MYF(0), table); + break; + case DB_TOO_BIG_RECORD: { + /* Note that in page0zip.ic page_zip_rec_needs_ext() rec_size + is limited to COMPRESSED_REC_MAX_DATA_SIZE (16K) or + REDUNDANT_REC_MAX_DATA_SIZE (16K-1). */ + bool comp = !!(flags & DICT_TF_COMPACT); + ulint free_space = page_get_free_space_of_empty(comp) / 2; + + if (free_space >= ulint(comp ? COMPRESSED_REC_MAX_DATA_SIZE : + REDUNDANT_REC_MAX_DATA_SIZE)) { + free_space = (comp ? COMPRESSED_REC_MAX_DATA_SIZE : + REDUNDANT_REC_MAX_DATA_SIZE) - 1; + } + + my_error(ER_TOO_BIG_ROWSIZE, MYF(0), free_space); + break; + } + case DB_INVALID_NULL: + /* TODO: report the row, as we do for DB_DUPLICATE_KEY */ + my_error(ER_INVALID_USE_OF_NULL, MYF(0)); + break; + case DB_CANT_CREATE_GEOMETRY_OBJECT: + my_error(ER_CANT_CREATE_GEOMETRY_OBJECT, MYF(0)); + break; + case DB_TABLESPACE_EXISTS: + my_error(ER_TABLESPACE_EXISTS, MYF(0), table); + break; + +#ifdef UNIV_DEBUG + case DB_SUCCESS: + case DB_DUPLICATE_KEY: + case DB_ONLINE_LOG_TOO_BIG: + /* These codes should not be passed here. */ + ut_error; +#endif /* UNIV_DEBUG */ + default: + my_error(ER_GET_ERRNO, MYF(0), error, "InnoDB"); + break; + } +} + +/** Get the name of an erroneous key. +@param[in] error_key_num InnoDB number of the erroneus key +@param[in] ha_alter_info changes that were being performed +@param[in] table InnoDB table +@return the name of the erroneous key */ +static +const char* +get_error_key_name( + ulint error_key_num, + const Alter_inplace_info* ha_alter_info, + const dict_table_t* table) +{ + if (error_key_num == ULINT_UNDEFINED) { + return(FTS_DOC_ID_INDEX_NAME); + } else if (ha_alter_info->key_count == 0) { + return(dict_table_get_first_index(table)->name); + } else { + return(ha_alter_info->key_info_buffer[error_key_num].name.str); + } +} + struct ha_innobase_inplace_ctx : public inplace_alter_handler_ctx { /** Dummy query graph */ @@ -1173,6 +1284,45 @@ struct ha_innobase_inplace_ctx : public inplace_alter_handler_ctx return true; return false; } + + /** Handle the apply log failure for online DDL operation. + @param ha_alter_info handler alter inplace info + @param altered_table MySQL table that is being altered + @param error error code + @retval false if error value is DB_SUCCESS or + TRUE in case of error */ + bool log_failure(Alter_inplace_info *ha_alter_info, + TABLE *altered_table, dberr_t error) + { + ulint err_key= thr_get_trx(thr)->error_key_num; + switch (error) { + KEY *dup_key; + case DB_SUCCESS: + return false; + case DB_DUPLICATE_KEY: + if (err_key == ULINT_UNDEFINED) + /* This should be the hidden index on FTS_DOC_ID */ + dup_key= nullptr; + else + { + DBUG_ASSERT(err_key < ha_alter_info->key_count); + dup_key= &ha_alter_info->key_info_buffer[err_key]; + } + print_keydup_error(altered_table, dup_key, MYF(0)); + break; + case DB_ONLINE_LOG_TOO_BIG: + my_error(ER_INNODB_ONLINE_LOG_TOO_BIG, MYF(0), + get_error_key_name(err_key, ha_alter_info, new_table)); + break; + case DB_INDEX_CORRUPT: + my_error(ER_INDEX_CORRUPT, MYF(0), + get_error_key_name(err_key, ha_alter_info, new_table)); + break; + default: + my_error_innodb(error, old_table->name.m_name, old_table->flags); + } + return true; + } }; /********************************************************************//** @@ -1180,96 +1330,6 @@ Get the upper limit of the MySQL integral and floating-point type. @return maximum allowed value for the field */ ulonglong innobase_get_int_col_max_value(const Field *field); -/* Report an InnoDB error to the client by invoking my_error(). */ -static ATTRIBUTE_COLD __attribute__((nonnull)) -void -my_error_innodb( -/*============*/ - dberr_t error, /*!< in: InnoDB error code */ - const char* table, /*!< in: table name */ - ulint flags) /*!< in: table flags */ -{ - switch (error) { - case DB_MISSING_HISTORY: - my_error(ER_TABLE_DEF_CHANGED, MYF(0)); - break; - case DB_RECORD_NOT_FOUND: - my_error(ER_KEY_NOT_FOUND, MYF(0), table); - break; - case DB_DEADLOCK: - my_error(ER_LOCK_DEADLOCK, MYF(0)); - break; - case DB_LOCK_WAIT_TIMEOUT: - my_error(ER_LOCK_WAIT_TIMEOUT, MYF(0)); - break; - case DB_INTERRUPTED: - my_error(ER_QUERY_INTERRUPTED, MYF(0)); - break; - case DB_OUT_OF_MEMORY: - my_error(ER_OUT_OF_RESOURCES, MYF(0)); - break; - case DB_OUT_OF_FILE_SPACE: - my_error(ER_RECORD_FILE_FULL, MYF(0), table); - break; - case DB_TEMP_FILE_WRITE_FAIL: - my_error(ER_TEMP_FILE_WRITE_FAILURE, MYF(0)); - break; - case DB_TOO_BIG_INDEX_COL: - my_error(ER_INDEX_COLUMN_TOO_LONG, MYF(0), - (ulong) DICT_MAX_FIELD_LEN_BY_FORMAT_FLAG(flags)); - break; - case DB_TOO_MANY_CONCURRENT_TRXS: - my_error(ER_TOO_MANY_CONCURRENT_TRXS, MYF(0)); - break; - case DB_LOCK_TABLE_FULL: - my_error(ER_LOCK_TABLE_FULL, MYF(0)); - break; - case DB_UNDO_RECORD_TOO_BIG: - my_error(ER_UNDO_RECORD_TOO_BIG, MYF(0)); - break; - case DB_CORRUPTION: - my_error(ER_NOT_KEYFILE, MYF(0), table); - break; - case DB_TOO_BIG_RECORD: { - /* Note that in page0zip.ic page_zip_rec_needs_ext() rec_size - is limited to COMPRESSED_REC_MAX_DATA_SIZE (16K) or - REDUNDANT_REC_MAX_DATA_SIZE (16K-1). */ - bool comp = !!(flags & DICT_TF_COMPACT); - ulint free_space = page_get_free_space_of_empty(comp) / 2; - - if (free_space >= ulint(comp ? COMPRESSED_REC_MAX_DATA_SIZE : - REDUNDANT_REC_MAX_DATA_SIZE)) { - free_space = (comp ? COMPRESSED_REC_MAX_DATA_SIZE : - REDUNDANT_REC_MAX_DATA_SIZE) - 1; - } - - my_error(ER_TOO_BIG_ROWSIZE, MYF(0), free_space); - break; - } - case DB_INVALID_NULL: - /* TODO: report the row, as we do for DB_DUPLICATE_KEY */ - my_error(ER_INVALID_USE_OF_NULL, MYF(0)); - break; - case DB_CANT_CREATE_GEOMETRY_OBJECT: - my_error(ER_CANT_CREATE_GEOMETRY_OBJECT, MYF(0)); - break; - case DB_TABLESPACE_EXISTS: - my_error(ER_TABLESPACE_EXISTS, MYF(0), table); - break; - -#ifdef UNIV_DEBUG - case DB_SUCCESS: - case DB_DUPLICATE_KEY: - case DB_ONLINE_LOG_TOO_BIG: - /* These codes should not be passed here. */ - ut_error; -#endif /* UNIV_DEBUG */ - default: - my_error(ER_GET_ERRNO, MYF(0), error, "InnoDB"); - break; - } -} - /** Determine if fulltext indexes exist in a given table. @param table MySQL table @return number of fulltext indexes */ @@ -7011,6 +7071,8 @@ error_handling_drop_uncached: row_log_free( index->online_log); index->online_log = NULL; + ctx->old_table->indexes.start + ->online_log = nullptr; ok = false; }); @@ -7133,24 +7195,8 @@ error_handling: ctx->trx->rollback(); - if (ctx->need_rebuild()) { - /* Free the log for online table rebuild, if - one was allocated. */ - - dict_index_t* clust_index = dict_table_get_first_index( - user_table); - - clust_index->lock.x_lock(SRW_LOCK_CALL); - - if (clust_index->online_log) { - ut_ad(ctx->online); - row_log_abort_sec(clust_index); - clust_index->online_status - = ONLINE_INDEX_COMPLETE; - } - - clust_index->lock.x_unlock(); - } + ut_ad(!ctx->need_rebuild() + || !user_table->indexes.start->online_log); ctx->prebuilt->trx->error_info = NULL; ctx->trx->error_state = DB_SUCCESS; @@ -8323,27 +8369,6 @@ alter_templ_needs_rebuild( return(false); } -/** Get the name of an erroneous key. -@param[in] error_key_num InnoDB number of the erroneus key -@param[in] ha_alter_info changes that were being performed -@param[in] table InnoDB table -@return the name of the erroneous key */ -static -const char* -get_error_key_name( - ulint error_key_num, - const Alter_inplace_info* ha_alter_info, - const dict_table_t* table) -{ - if (error_key_num == ULINT_UNDEFINED) { - return(FTS_DOC_ID_INDEX_NAME); - } else if (ha_alter_info->key_count == 0) { - return(dict_table_get_first_index(table)->name); - } else { - return(ha_alter_info->key_info_buffer[error_key_num].name.str); - } -} - /** Alter the table structure in-place with operations specified using Alter_inplace_info. The level of concurrency allowed during this operation depends @@ -8708,6 +8733,7 @@ inline bool rollback_inplace_alter_table(Alter_inplace_info *ha_alter_info, DBUG_ENTER("rollback_inplace_alter_table"); + DEBUG_SYNC_C("innodb_rollback_inplace_alter_table"); if (!ctx) /* If we have not started a transaction yet, (almost) nothing has been or needs to be done. */ @@ -8771,7 +8797,10 @@ inline bool rollback_inplace_alter_table(Alter_inplace_info *ha_alter_info, thd_lock_wait_timeout(ctx->trx->mysql_thd); const uint save_timeout= innodb_lock_wait_timeout; innodb_lock_wait_timeout= ~0U; /* infinite */ - + dict_index_t *old_clust_index= ctx->old_table->indexes.start; + old_clust_index->lock.x_lock(SRW_LOCK_CALL); + old_clust_index->online_log= nullptr; + old_clust_index->lock.x_unlock(); if (fts_exist) { const dict_index_t *fts_index= nullptr; @@ -8855,7 +8884,6 @@ free_and_exit: } } - DBUG_ASSERT(!prebuilt->table->indexes.start->online_log); DBUG_ASSERT(prebuilt->table->indexes.start->online_status == ONLINE_INDEX_COMPLETE); @@ -10734,7 +10762,6 @@ static bool alter_rebuild_apply_log( dropped were not created in the copy of the table. Apply any last bit of the rebuild log and then rename the tables. */ dict_table_t* user_table = ctx->old_table; - dict_table_t* rebuilt_table = ctx->new_table; DEBUG_SYNC_C("row_log_table_apply2_before"); @@ -10763,41 +10790,8 @@ static bool alter_rebuild_apply_log( ctx->new_table->vc_templ = NULL; } - ulint err_key = thr_get_trx(ctx->thr)->error_key_num; - - switch (error) { - KEY* dup_key; - case DB_SUCCESS: - break; - case DB_DUPLICATE_KEY: - if (err_key == ULINT_UNDEFINED) { - /* This should be the hidden index on - FTS_DOC_ID. */ - dup_key = NULL; - } else { - DBUG_ASSERT(err_key < ha_alter_info->key_count); - dup_key = &ha_alter_info->key_info_buffer[err_key]; - } - - print_keydup_error(altered_table, dup_key, MYF(0)); - DBUG_RETURN(true); - case DB_ONLINE_LOG_TOO_BIG: - my_error(ER_INNODB_ONLINE_LOG_TOO_BIG, MYF(0), - get_error_key_name(err_key, ha_alter_info, - rebuilt_table)); - DBUG_RETURN(true); - case DB_INDEX_CORRUPT: - my_error(ER_INDEX_CORRUPT, MYF(0), - get_error_key_name(err_key, ha_alter_info, - rebuilt_table)); - DBUG_RETURN(true); - default: - my_error_innodb(error, ctx->old_table->name.m_name, - user_table->flags); - DBUG_RETURN(true); - } - - DBUG_RETURN(false); + DBUG_RETURN(ctx->log_failure( + ha_alter_info, altered_table, error)); } /** Commit or rollback the changes made during @@ -10988,6 +10982,72 @@ lock_fail: DBUG_RETURN(true); } } + } else { + dberr_t error= DB_SUCCESS; + for (inplace_alter_handler_ctx** pctx= ctx_array; *pctx; + pctx++) { + auto ctx= static_cast<ha_innobase_inplace_ctx*>(*pctx); + + if (!ctx->online || !ctx->old_table->space + || !ctx->old_table->is_readable()) { + continue; + } + + for (ulint i = 0; i < ctx->num_to_add_index; i++) { + dict_index_t *index= ctx->add_index[i]; + + ut_ad(!(index->type & + (DICT_FTS | DICT_SPATIAL))); + + index->lock.x_lock(SRW_LOCK_CALL); + if (!index->online_log) { + /* online log would've cleared + when we detect the error in + other index */ + index->lock.x_unlock(); + continue; + } + + if (index->is_corrupted()) { + /* Online index log has been + preserved to show the error + when it happened via + row_log_apply() by DML thread */ + error= row_log_get_error(index); +err_index: + ut_ad(error != DB_SUCCESS); + ctx->log_failure( + ha_alter_info, + altered_table, error); + row_log_free(index->online_log); + index->online_log= nullptr; + index->lock.x_unlock(); + + ctx->old_table->indexes.start + ->online_log= nullptr; + if (fts_exist) { + purge_sys.resume_FTS(); + } + MONITOR_ATOMIC_INC( + MONITOR_BACKGROUND_DROP_INDEX); + DBUG_RETURN(true); + } + + error = row_log_apply( + m_prebuilt->trx, index, altered_table, + ctx->m_stage); + + if (error != DB_SUCCESS) { + goto err_index; + } + + row_log_free(index->online_log); + index->online_log= nullptr; + index->lock.x_unlock(); + } + + ctx->old_table->indexes.start->online_log= nullptr; + } } dict_table_t *table_stats = nullptr, *index_stats = nullptr; @@ -11020,6 +11080,10 @@ lock_fail: error = lock_table_for_trx(index_stats, trx, LOCK_X); } } + + DBUG_EXECUTE_IF("stats_lock_fail", + error = DB_LOCK_WAIT;); + if (error == DB_SUCCESS) { error = lock_sys_tables(trx); } diff --git a/storage/innobase/include/dict0mem.h b/storage/innobase/include/dict0mem.h index e8233ee18e5..a7efa5a6431 100644 --- a/storage/innobase/include/dict0mem.h +++ b/storage/innobase/include/dict0mem.h @@ -1399,6 +1399,20 @@ public: rollback of TRX_UNDO_EMPTY. The BTR_SEG_LEAF is freed and reinitialized. @param thr query thread */ void clear(que_thr_t *thr); + + /** Check whether the online log is dummy value to indicate + whether table undergoes active DDL. + @retval true if online log is dummy value */ + bool online_log_is_dummy() const + { + return online_log == reinterpret_cast<const row_log_t*>(this); + } + + /** Assign clustered index online log to dummy value */ + void online_log_make_dummy() + { + online_log= reinterpret_cast<row_log_t*>(this); + } }; /** Detach a virtual column from an index. @@ -2360,6 +2374,12 @@ public: return false; } + /** @return whether a DDL operation is in progress on this table */ + bool is_active_ddl() const + { + return UT_LIST_GET_FIRST(indexes)->online_log; + } + /** @return whether the name is mysql.innodb_index_stats or mysql.innodb_table_stats */ bool is_stats_table() const; diff --git a/storage/innobase/include/row0log.h b/storage/innobase/include/row0log.h index 732ef494326..4da61b3ffbb 100644 --- a/storage/innobase/include/row0log.h +++ b/storage/innobase/include/row0log.h @@ -1,7 +1,7 @@ /***************************************************************************** Copyright (c) 2011, 2016, Oracle and/or its affiliates. All Rights Reserved. -Copyright (c) 2017, 2021, MariaDB Corporation. +Copyright (c) 2017, 2022, MariaDB Corporation. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -24,15 +24,15 @@ Modification log for online index creation and online table rebuild Created 2011-05-26 Marko Makela *******************************************************/ -#ifndef row0log_h -#define row0log_h +#pragma once #include "que0types.h" #include "mtr0types.h" #include "row0types.h" #include "rem0types.h" -#include "data0types.h" +#include "dict0dict.h" #include "trx0types.h" +#include "trx0undo.h" class ut_stage_alter_t; @@ -74,37 +74,23 @@ row_log_free( /******************************************************//** Free the row log for an index on which online creation was aborted. */ -UNIV_INLINE -void -row_log_abort_sec( -/*==============*/ - dict_index_t* index) /*!< in/out: index (x-latched) */ - MY_ATTRIBUTE((nonnull)); - -/******************************************************//** -Try to log an operation to a secondary index that is -(or was) being created. -@retval true if the operation was logged or can be ignored -@retval false if online index creation is not taking place */ -UNIV_INLINE -bool -row_log_online_op_try( -/*==================*/ - dict_index_t* index, /*!< in/out: index, S or X latched */ - const dtuple_t* tuple, /*!< in: index tuple */ - trx_id_t trx_id) /*!< in: transaction ID for insert, - or 0 for delete */ - MY_ATTRIBUTE((nonnull, warn_unused_result)); -/******************************************************//** -Logs an operation to a secondary index that is (or was) being created. */ -void -row_log_online_op( -/*==============*/ - dict_index_t* index, /*!< in/out: index, S or X latched */ - const dtuple_t* tuple, /*!< in: index tuple (NULL=empty the index) */ - trx_id_t trx_id) /*!< in: transaction ID for insert, - or 0 for delete */ - ATTRIBUTE_COLD; +inline void row_log_abort_sec(dict_index_t *index) +{ + ut_ad(index->lock.have_u_or_x()); + ut_ad(!index->is_clust()); + dict_index_set_online_status(index, ONLINE_INDEX_ABORTED); + row_log_free(index->online_log); + index->online_log= nullptr; +} + +/** Logs an operation to a secondary index that is (or was) being created. +@param index index, S or X latched +@param tuple index tuple (NULL= empty the index) +@param trx_id transaction ID for insert, or 0 for delete +@retval false if row_log_apply() failure happens +or true otherwise */ +bool row_log_online_op(dict_index_t *index, const dtuple_t *tuple, + trx_id_t trx_id) ATTRIBUTE_COLD; /******************************************************//** Gets the error status of the online index rebuild log. @@ -185,22 +171,6 @@ row_log_table_insert( dict_index_t* index, /*!< in/out: clustered index, S-latched or X-latched */ const rec_offs* offsets);/*!< in: rec_get_offsets(rec,index) */ -/******************************************************//** -Notes that a BLOB is being freed during online ALTER TABLE. */ -void -row_log_table_blob_free( -/*====================*/ - dict_index_t* index, /*!< in/out: clustered index, X-latched */ - ulint page_no)/*!< in: starting page number of the BLOB */ - ATTRIBUTE_COLD __attribute__((nonnull)); -/******************************************************//** -Notes that a BLOB is being allocated during online ALTER TABLE. */ -void -row_log_table_blob_alloc( -/*=====================*/ - dict_index_t* index, /*!< in/out: clustered index, X-latched */ - ulint page_no)/*!< in: starting page number of the BLOB */ - ATTRIBUTE_COLD __attribute__((nonnull)); /** Apply the row_log_table log to a table upon completing rebuild. @param[in] thr query graph @@ -252,6 +222,11 @@ row_log_apply( @return number of n_core_fields */ unsigned row_log_get_n_core_fields(const dict_index_t *index); +/** Get the error code of online log for the index +@param index online index +@return error code present in online log */ +dberr_t row_log_get_error(const dict_index_t *index); + #ifdef HAVE_PSI_STAGE_INTERFACE /** Estimate how much work is to be done by the log apply phase of an ALTER TABLE for this index. @@ -262,7 +237,3 @@ ulint row_log_estimate_work( const dict_index_t* index); #endif /* HAVE_PSI_STAGE_INTERFACE */ - -#include "row0log.inl" - -#endif /* row0log.h */ diff --git a/storage/innobase/include/row0log.inl b/storage/innobase/include/row0log.inl deleted file mode 100644 index f9f3dd006bf..00000000000 --- a/storage/innobase/include/row0log.inl +++ /dev/null @@ -1,80 +0,0 @@ -/***************************************************************************** - -Copyright (c) 2011, 2015, Oracle and/or its affiliates. All Rights Reserved. -Copyright (c) 2020, MariaDB Corporation. - -This program is free software; you can redistribute it and/or modify it under -the terms of the GNU General Public License as published by the Free Software -Foundation; version 2 of the License. - -This program is distributed in the hope that it will be useful, but WITHOUT -ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - -You should have received a copy of the GNU General Public License along with -this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA - -*****************************************************************************/ - -/**************************************************//** -@file include/row0log.ic -Modification log for online index creation and online table rebuild - -Created 2012-10-18 Marko Makela -*******************************************************/ - -#include "dict0dict.h" - -/******************************************************//** -Free the row log for an index on which online creation was aborted. */ -UNIV_INLINE -void -row_log_abort_sec( -/*===============*/ - dict_index_t* index) /*!< in/out: index (x-latched) */ -{ - ut_ad(index->lock.have_u_or_x()); - ut_ad(!dict_index_is_clust(index)); - dict_index_set_online_status(index, ONLINE_INDEX_ABORTED); - row_log_free(index->online_log); - index->online_log = NULL; -} - -/******************************************************//** -Try to log an operation to a secondary index that is -(or was) being created. -@retval true if the operation was logged or can be ignored -@retval false if online index creation is not taking place */ -UNIV_INLINE -bool -row_log_online_op_try( -/*==================*/ - dict_index_t* index, /*!< in/out: index, S or X latched */ - const dtuple_t* tuple, /*!< in: index tuple */ - trx_id_t trx_id) /*!< in: transaction ID for insert, - or 0 for delete */ -{ - ut_ad(index->lock.have_any()); - - switch (dict_index_get_online_status(index)) { - case ONLINE_INDEX_COMPLETE: - /* This is a normal index. Do not log anything. - The caller must perform the operation on the - index tree directly. */ - return(false); - case ONLINE_INDEX_CREATION: - /* The index is being created online. Log the - operation. */ - row_log_online_op(index, tuple, trx_id); - break; - case ONLINE_INDEX_ABORTED: - case ONLINE_INDEX_ABORTED_DROPPED: - /* The index was created online, but the operation was - aborted. Do not log the operation and tell the caller - to skip the operation. */ - break; - } - - return(true); -} diff --git a/storage/innobase/include/trx0rec.h b/storage/innobase/include/trx0rec.h index 0afae491c78..12bf517554c 100644 --- a/storage/innobase/include/trx0rec.h +++ b/storage/innobase/include/trx0rec.h @@ -1,7 +1,7 @@ /***************************************************************************** Copyright (c) 1996, 2016, Oracle and/or its affiliates. All Rights Reserved. -Copyright (c) 2017, 2021, MariaDB Corporation. +Copyright (c) 2017, 2022, MariaDB Corporation. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -82,6 +82,7 @@ trx_undo_rec_get_pars( undo_no_t* undo_no, /*!< out: undo log record number */ table_id_t* table_id) /*!< out: table id */ MY_ATTRIBUTE((nonnull)); + /*******************************************************************//** Builds a row reference from an undo log record. @return pointer to remaining part of undo record */ @@ -208,37 +209,48 @@ fetching the purge record */ the undo log (which is the after image for an update) */ #define TRX_UNDO_GET_OLD_V_VALUE 0x2 -/*******************************************************************//** -Build a previous version of a clustered index record. The caller must -hold a latch on the index page of the clustered index record. +/** Build a previous version of a clustered index record. The caller +must hold a latch on the index page of the clustered index record. +@param index_rec clustered index record in the index tree +@param index_mtr mtr which contains the latch to index_rec page + and purge_view +@param rec version of a clustered index record +@param index clustered index +@param offsets rec_get_offsets(rec, index) +@param heap memory heap from which the memory needed is + allocated +@param old_vers previous version or NULL if rec is the + first inserted version, or if history data + has been deleted (an error), or if the purge + could have removed the version + though it has not yet done so +@param v_heap memory heap used to create vrow + dtuple if it is not yet created. This heap + diffs from "heap" above in that it could be + prebuilt->old_vers_heap for selection +@param vrow virtual column info, if any +@param v_status status determine if it is going into this + function by purge thread or not. + And if we read "after image" of undo log +@param undo_block undo log block which was cached during + online dml apply or nullptr @retval true if previous version was built, or if it was an insert or the table has been rebuilt @retval false if the previous version is earlier than purge_view, -which means that it may have been removed */ +or being purged, which means that it may have been removed */ bool trx_undo_prev_version_build( -/*========================*/ - const rec_t* index_rec,/*!< in: clustered index record in the - index tree */ - mtr_t* index_mtr,/*!< in: mtr which contains the latch to - index_rec page and purge_view */ - const rec_t* rec, /*!< in: version of a clustered index record */ - dict_index_t* index, /*!< in: clustered index */ - rec_offs* offsets,/*!< in/out: rec_get_offsets(rec, index) */ - mem_heap_t* heap, /*!< in: memory heap from which the memory - needed is allocated */ - rec_t** old_vers,/*!< out, own: previous version, or NULL if - rec is the first inserted version, or if - history data has been deleted */ - mem_heap_t* v_heap, /* !< in: memory heap used to create vrow - dtuple if it is not yet created. This heap - diffs from "heap" above in that it could be - prebuilt->old_vers_heap for selection */ - dtuple_t** vrow, /*!< out: virtual column info, if any */ - ulint v_status); - /*!< in: status determine if it is going - into this function by purge thread or not. - And if we read "after image" of undo log */ + const rec_t *index_rec, + mtr_t *index_mtr, + const rec_t *rec, + dict_index_t *index, + rec_offs *offsets, + mem_heap_t *heap, + rec_t **old_vers, + mem_heap_t *v_heap, + dtuple_t **vrow, + ulint v_status, + const buf_block_t *undo_block= nullptr); /** Read from an undo log record a non-virtual column value. @param[in,out] ptr pointer to remaining part of the undo record diff --git a/storage/innobase/include/trx0trx.h b/storage/innobase/include/trx0trx.h index d480cac25c8..23bedfaea43 100644 --- a/storage/innobase/include/trx0trx.h +++ b/storage/innobase/include/trx0trx.h @@ -861,6 +861,10 @@ public: bool auto_commit; /*!< true if it is an autocommit */ bool will_lock; /*!< set to inform trx_start_low() that the transaction may acquire locks */ + /* True if transaction has to read the undo log and + log the DML changes for online DDL table */ + bool apply_online_log = false; + /*------------------------------*/ fts_trx_t* fts_trx; /*!< FTS information, or NULL if transaction hasn't modified tables @@ -934,6 +938,8 @@ public: @retval false if the rollback was aborted by shutdown */ inline bool rollback_finish(); private: + /** Apply any changes to tables for which online DDL is in progress. */ + ATTRIBUTE_COLD void apply_log(); /** Process tables that were modified by the committing transaction. */ inline void commit_tables(); /** Mark a transaction committed in the main memory data structures. */ @@ -1026,6 +1032,7 @@ public: ut_ad(!autoinc_locks || ib_vector_is_empty(autoinc_locks)); ut_ad(UT_LIST_GET_LEN(lock.evicted_tables) == 0); ut_ad(!dict_operation); + ut_ad(!apply_online_log); } /** This has to be invoked on SAVEPOINT or at the end of a statement. diff --git a/storage/innobase/include/trx0undo.h b/storage/innobase/include/trx0undo.h index 62662ffe221..111369e6a0f 100644 --- a/storage/innobase/include/trx0undo.h +++ b/storage/innobase/include/trx0undo.h @@ -1,7 +1,7 @@ /***************************************************************************** Copyright (c) 1996, 2016, Oracle and/or its affiliates. All Rights Reserved. -Copyright (c) 2017, 2021, MariaDB Corporation. +Copyright (c) 2017, 2022, MariaDB Corporation. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -326,6 +326,105 @@ struct trx_undo_t { /*!< undo log objects in the rollback segment are chained into lists */ }; + +/** Cache a pointer to an undo record in a latched buffer pool page, +parse the undo log record and store the record type, update vector +and compiler information */ +class UndorecApplier +{ + /** undo log block which was latched */ + const buf_block_t *block; + /** Undo log record pointer */ + trx_undo_rec_t *undo_rec; + /** Offset of the undo log record within the block */ + ulint offset; + /** Transaction id of the undo log */ + trx_id_t trx_id; + /** Undo log record type */ + ulint type; + /** compiler information */ + ulint cmpl_info; + /** Update vector */ + upd_t *update; + /** memory heap which can be used to build previous version of + the index record and its offsets */ + mem_heap_t *heap; + /** mini-transaction for accessing B-tree pages */ + mtr_t mtr; + +public: + UndorecApplier(const buf_block_t *block, trx_id_t trx_id) + : block(block), trx_id(trx_id) + { + ut_ad(block->page.lock.have_any()); + heap= mem_heap_create(100); + } + + /** Assign the undo log block */ + void assign_block(const buf_block_t *undo_block) + { + block= undo_block; + } + + /** Assign the undo log record and offset */ + void assign_rec(trx_undo_rec_t *rec); + + /** Handle the DML undo log and apply it on online indexes */ + void apply_undo_rec(); + + ~UndorecApplier() + { + mem_heap_free(heap); + } + +private: + /** Handle the insert undo log and apply it on online indexes + @param tuple row reference from undo log record + @param clust_index clustered index */ + void log_insert(const dtuple_t &tuple, dict_index_t *clust_index); + + /** Handle the update, delete undo log and apply it on online + indexes. + @param tuple row reference from undo log record + @param clust_index clustered index */ + void log_update(const dtuple_t &tuple, dict_index_t *clust_index); + + /** Check whether the given roll pointer is generated by + the current undo log record information stored. + @return true if roll pointer matches with current undo log info */ + bool is_same(roll_ptr_t roll_ptr) const + { + uint16_t offset= static_cast<uint16_t>(roll_ptr); + uint32_t page_no= static_cast<uint32_t>(roll_ptr >> 16); + return page_no == block->page.id().page_no() && offset == this->offset; + } + + /** Clear the undo log record information */ + void clear_undo_rec() + { + undo_rec= nullptr; + cmpl_info= 0; + type= 0; + update= nullptr; + offset= 0; + mem_heap_empty(heap); + } + + /** Get the correct version of the clustered index record that + was modified by the current undo log record. Because there could + be the multiple successive updates of the same record within the + same transaction. + @param tuple tuple contains primary key value + @param index clustered index + @param[out] clust_rec current clustered index record + @param offsets offsets points to the record + @return clustered index record which was changed by + the undo log record or nullptr when there is no clustered + index record changed by undo log record */ + const rec_t* get_old_rec(const dtuple_t &tuple, dict_index_t *index, + const rec_t **clust_rec, rec_offs **offsets); +}; + #endif /* !UNIV_INNOCHECKSUM */ /** The offset of the undo log page header on pages of the undo log */ diff --git a/storage/innobase/row/row0ins.cc b/storage/innobase/row/row0ins.cc index 911e33875a1..8daa4f7e8b7 100644 --- a/storage/innobase/row/row0ins.cc +++ b/storage/innobase/row/row0ins.cc @@ -1,7 +1,7 @@ /***************************************************************************** Copyright (c) 1996, 2016, Oracle and/or its affiliates. All Rights Reserved. -Copyright (c) 2016, 2021, MariaDB Corporation. +Copyright (c) 2016, 2022, MariaDB Corporation. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -35,7 +35,6 @@ Created 4/20/1996 Heikki Tuuri #include "que0que.h" #include "row0upd.h" #include "row0sel.h" -#include "row0log.h" #include "rem0cmp.h" #include "lock0lock.h" #include "log0log.h" @@ -2025,7 +2024,6 @@ row_ins_scan_sec_index_for_duplicate( dict_index_t* index, /*!< in: non-clustered unique index */ dtuple_t* entry, /*!< in: index entry */ que_thr_t* thr, /*!< in: query thread */ - bool s_latch,/*!< in: whether index->lock is being held */ mtr_t* mtr, /*!< in/out: mini-transaction */ mem_heap_t* offsets_heap) /*!< in/out: memory heap that can be emptied */ @@ -2042,7 +2040,7 @@ row_ins_scan_sec_index_for_duplicate( rec_offs_init(offsets_); - ut_ad(s_latch == (index->lock.have_u_not_x() || index->lock.have_s())); + ut_ad(!index->lock.have_any()); n_unique = dict_index_get_n_unique(index); @@ -2066,11 +2064,7 @@ row_ins_scan_sec_index_for_duplicate( dtuple_set_n_fields_cmp(entry, n_unique); - btr_pcur_open(index, entry, PAGE_CUR_GE, - s_latch - ? BTR_SEARCH_LEAF_ALREADY_S_LATCHED - : BTR_SEARCH_LEAF, - &pcur, mtr); + btr_pcur_open(index, entry, PAGE_CUR_GE, BTR_SEARCH_LEAF, &pcur, mtr); allow_duplicates = thr_get_trx(thr)->duplicates; @@ -2484,11 +2478,6 @@ row_ins_index_entry_big_rec( &pcur, offsets, big_rec, &mtr, BTR_STORE_INSERT); DEBUG_SYNC_C_IF_THD(thd, "after_row_ins_extern"); - if (error == DB_SUCCESS - && dict_index_is_online_ddl(index)) { - row_log_table_insert(btr_pcur_get_rec(&pcur), index, offsets); - } - mtr.commit(); btr_pcur_close(&pcur); @@ -2742,11 +2731,6 @@ err_exit: &pcur, flags, mode, &offsets, &offsets_heap, entry_heap, entry, thr, &mtr); - if (err == DB_SUCCESS && dict_index_is_online_ddl(index)) { - row_log_table_insert(btr_cur_get_rec(cursor), - index, offsets); - } - mtr_commit(&mtr); mem_heap_free(entry_heap); } else { @@ -2784,9 +2768,9 @@ do_insert: } } - if (big_rec != NULL) { - mtr_commit(&mtr); + mtr.commit(); + if (big_rec) { /* Online table rebuild could read (and ignore) the incomplete record at this point. If online rebuild is in progress, the @@ -2799,14 +2783,6 @@ do_insert: entry, big_rec, offsets, &offsets_heap, index, trx->mysql_thd); dtuple_convert_back_big_rec(index, entry, big_rec); - } else { - if (err == DB_SUCCESS - && dict_index_is_online_ddl(index)) { - row_log_table_insert( - insert_rec, index, offsets); - } - - mtr_commit(&mtr); } } @@ -2820,19 +2796,10 @@ func_exit: DBUG_RETURN(err); } -/** Start a mini-transaction and check if the index will be dropped. +/** Start a mini-transaction. @param[in,out] mtr mini-transaction -@param[in,out] index secondary index -@param[in] check whether to check -@param[in] search_mode flags -@return true if the index is to be dropped */ -static MY_ATTRIBUTE((warn_unused_result)) -bool -row_ins_sec_mtr_start_and_check_if_aborted( - mtr_t* mtr, - dict_index_t* index, - bool check, - ulint search_mode) +@param[in,out] index secondary index */ +static void row_ins_sec_mtr_start(mtr_t *mtr, dict_index_t *index) { ut_ad(!dict_index_is_clust(index)); ut_ad(mtr->is_named_space(index->table->space)); @@ -2842,30 +2809,6 @@ row_ins_sec_mtr_start_and_check_if_aborted( mtr->start(); index->set_modified(*mtr); mtr->set_log_mode(log_mode); - - if (!check) { - return(false); - } - - if (search_mode & BTR_ALREADY_S_LATCHED) { - mtr_s_lock_index(index, mtr); - } else { - mtr_sx_lock_index(index, mtr); - } - - switch (index->online_status) { - case ONLINE_INDEX_ABORTED: - case ONLINE_INDEX_ABORTED_DROPPED: - ut_ad(!index->is_committed()); - return(true); - case ONLINE_INDEX_COMPLETE: - return(false); - case ONLINE_INDEX_CREATION: - break; - } - - ut_error; - return(true); } /***************************************************************//** @@ -2925,28 +2868,6 @@ row_ins_sec_index_entry_low( } } - /* Ensure that we acquire index->lock when inserting into an - index with index->online_status == ONLINE_INDEX_COMPLETE, but - could still be subject to rollback_inplace_alter_table(). - This prevents a concurrent change of index->online_status. - The memory object cannot be freed as long as we have an open - reference to the table, or index->table->n_ref_count > 0. */ - const bool check = !index->is_committed(); - if (check) { - DEBUG_SYNC_C("row_ins_sec_index_enter"); - if (mode == BTR_MODIFY_LEAF) { - search_mode |= BTR_ALREADY_S_LATCHED; - mtr_s_lock_index(index, &mtr); - } else { - mtr_sx_lock_index(index, &mtr); - } - - if (row_log_online_op_try( - index, entry, thr_get_trx(thr)->id)) { - goto func_exit; - } - } - /* Note that we use PAGE_CUR_LE as the search mode, because then the function will return in both low_match and up_match of the cursor sensible values */ @@ -3031,13 +2952,10 @@ row_ins_sec_index_entry_low( DEBUG_SYNC_C("row_ins_sec_index_unique"); - if (row_ins_sec_mtr_start_and_check_if_aborted( - &mtr, index, check, search_mode)) { - goto func_exit; - } + row_ins_sec_mtr_start(&mtr, index); err = row_ins_scan_sec_index_for_duplicate( - flags, index, entry, thr, check, &mtr, offsets_heap); + flags, index, entry, thr, &mtr, offsets_heap); mtr_commit(&mtr); @@ -3066,10 +2984,7 @@ row_ins_sec_index_entry_low( DBUG_RETURN(err); } - if (row_ins_sec_mtr_start_and_check_if_aborted( - &mtr, index, check, search_mode)) { - goto func_exit; - } + row_ins_sec_mtr_start(&mtr, index); DEBUG_SYNC_C("row_ins_sec_index_entry_dup_locks_created"); @@ -3664,7 +3579,8 @@ row_ins( FTS_DOC_ID for history is enough. */ const unsigned type = index->type; - if (index->type & DICT_FTS) { + if (index->type & DICT_FTS + || !index->is_committed()) { } else if (!(type & DICT_UNIQUE) || index->n_uniq > 1 || !node->vers_history_row()) { diff --git a/storage/innobase/row/row0log.cc b/storage/innobase/row/row0log.cc index 610ac1ad2ae..57f4fe28541 100644 --- a/storage/innobase/row/row0log.cc +++ b/storage/innobase/row/row0log.cc @@ -1,7 +1,7 @@ /***************************************************************************** Copyright (c) 2011, 2018, Oracle and/or its affiliates. All Rights Reserved. -Copyright (c) 2017, 2021, MariaDB Corporation. +Copyright (c) 2017, 2022, MariaDB Corporation. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -89,79 +89,6 @@ struct row_log_buf_t { row_log_apply(). */ }; -/** Tracks BLOB allocation during online ALTER TABLE */ -class row_log_table_blob_t { -public: - /** Constructor (declaring a BLOB freed) - @param offset_arg row_log_t::tail::total */ -#ifdef UNIV_DEBUG - row_log_table_blob_t(ulonglong offset_arg) : - old_offset (0), free_offset (offset_arg), - offset (BLOB_FREED) {} -#else /* UNIV_DEBUG */ - row_log_table_blob_t() : - offset (BLOB_FREED) {} -#endif /* UNIV_DEBUG */ - - /** Declare a BLOB freed again. - @param offset_arg row_log_t::tail::total */ -#ifdef UNIV_DEBUG - void blob_free(ulonglong offset_arg) -#else /* UNIV_DEBUG */ - void blob_free() -#endif /* UNIV_DEBUG */ - { - ut_ad(offset < offset_arg); - ut_ad(offset != BLOB_FREED); - ut_d(old_offset = offset); - ut_d(free_offset = offset_arg); - offset = BLOB_FREED; - } - /** Declare a freed BLOB reused. - @param offset_arg row_log_t::tail::total */ - void blob_alloc(ulonglong offset_arg) { - ut_ad(free_offset <= offset_arg); - ut_d(old_offset = offset); - offset = offset_arg; - } - /** Determine if a BLOB was freed at a given log position - @param offset_arg row_log_t::head::total after the log record - @return true if freed */ - bool is_freed(ulonglong offset_arg) const { - /* This is supposed to be the offset at the end of the - current log record. */ - ut_ad(offset_arg > 0); - /* We should never get anywhere close the magic value. */ - ut_ad(offset_arg < BLOB_FREED); - return(offset_arg < offset); - } -private: - /** Magic value for a freed BLOB */ - static const ulonglong BLOB_FREED = ~0ULL; -#ifdef UNIV_DEBUG - /** Old offset, in case a page was freed, reused, freed, ... */ - ulonglong old_offset; - /** Offset of last blob_free() */ - ulonglong free_offset; -#endif /* UNIV_DEBUG */ - /** Byte offset to the log file */ - ulonglong offset; -}; - -/** @brief Map of off-page column page numbers to 0 or log byte offsets. - -If there is no mapping for a page number, it is safe to access. -If a page number maps to 0, it is an off-page column that has been freed. -If a page number maps to a nonzero number, the number is a byte offset -into the index->online_log, indicating that the page is safe to access -when applying log records starting from that offset. */ -typedef std::map< - ulint, - row_log_table_blob_t, - std::less<ulint>, - ut_allocator<std::pair<const ulint, row_log_table_blob_t> > > - page_no_map; - /** @brief Buffer for logging modifications during online index creation All modifications to an index that is being created will be logged by @@ -178,10 +105,6 @@ struct row_log_t { pfs_os_file_t fd; /*!< file descriptor */ mysql_mutex_t mutex; /*!< mutex protecting error, max_trx and tail */ - page_no_map* blobs; /*!< map of page numbers of off-page columns - that have been freed during table-rebuilding - ALTER TABLE (row_log_table_*); protected by - index->lock X-latch only */ dict_table_t* table; /*!< table that is being rebuilt, or NULL when this is a secondary index that is being created online */ @@ -241,6 +164,11 @@ struct row_log_t { const TABLE* old_table; /*< Use old table in case of error. */ uint64_t n_rows; /*< Number of rows read from the table */ + + /** Alter table transaction. It can be used to apply the DML logs + into the table */ + const trx_t* alter_trx; + /** Determine whether the log should be in the 'instant ADD' format @param[in] index the clustered index of the source table @return whether to use the 'instant ADD COLUMN' format */ @@ -334,8 +262,6 @@ static void row_log_empty(dict_index_t *index) row_log_t *log= index->online_log; mysql_mutex_lock(&log->mutex); - UT_DELETE(log->blobs); - log->blobs= nullptr; row_log_block_free(log->tail); row_log_block_free(log->head); row_merge_file_destroy_low(log->fd); @@ -345,15 +271,14 @@ static void row_log_empty(dict_index_t *index) mysql_mutex_unlock(&log->mutex); } -/******************************************************//** -Logs an operation to a secondary index that is (or was) being created. */ -void -row_log_online_op( -/*==============*/ - dict_index_t* index, /*!< in/out: index, S or X latched */ - const dtuple_t* tuple, /*!< in: index tuple (NULL=empty the index) */ - trx_id_t trx_id) /*!< in: transaction ID for insert, - or 0 for delete */ +/** Logs an operation to a secondary index that is (or was) being created. +@param index index, S or X latched +@param tuple index tuple (NULL= empty the index) +@param trx_id transaction ID for insert, or 0 for delete +@retval false if row_log_apply() failure happens +or true otherwise */ +bool row_log_online_op(dict_index_t *index, const dtuple_t *tuple, + trx_id_t trx_id) { byte* b; ulint extra_size; @@ -361,16 +286,19 @@ row_log_online_op( ulint mrec_size; ulint avail_size; row_log_t* log; + bool success= true; ut_ad(!tuple || dtuple_validate(tuple)); ut_ad(!tuple || dtuple_get_n_fields(tuple) == dict_index_get_n_fields(index)); ut_ad(index->lock.have_x() || index->lock.have_s()); if (index->is_corrupted()) { - return; + return success; } - ut_ad(dict_index_is_online_ddl(index)); + ut_ad(dict_index_is_online_ddl(index) + || (index->online_log + && index->online_status == ONLINE_INDEX_COMPLETE)); /* Compute the size of the record. This differs from row_merge_buf_encode(), because here we do not encode @@ -395,6 +323,7 @@ row_log_online_op( log = index->online_log; mysql_mutex_lock(&log->mutex); +start_log: if (trx_id > log->max_trx) { log->max_trx = trx_id; } @@ -450,7 +379,28 @@ row_log_online_op( byte* buf = log->tail.block; if (byte_offset + srv_sort_buf_size >= srv_online_max_size) { - goto write_failed; + if (index->online_status != ONLINE_INDEX_COMPLETE) + goto write_failed; + /* About to run out of log, InnoDB has to + apply the online log for the completed index */ + index->lock.s_unlock(); + dberr_t error= row_log_apply( + log->alter_trx, index, nullptr, nullptr); + index->lock.s_lock(SRW_LOCK_CALL); + if (error != DB_SUCCESS) { + /* Mark all newly added indexes + as corrupted */ + log->error = error; + success = false; + goto err_exit; + } + + /* Recheck whether the index online log */ + if (!index->online_log) { + goto err_exit; + } + + goto start_log; } if (mrec_size == avail_size) { @@ -510,6 +460,7 @@ write_failed: MEM_UNDEFINED(log->tail.buf, sizeof log->tail.buf); err_exit: mysql_mutex_unlock(&log->mutex); + return success; } /******************************************************//** @@ -833,7 +784,6 @@ row_log_table_low_redundant( dtuple_t* tuple; const ulint n_fields = rec_get_n_fields_old(rec); - ut_ad(!page_is_comp(page_align(rec))); ut_ad(index->n_fields >= n_fields); ut_ad(index->n_fields == n_fields || index->is_instant()); ut_ad(dict_tf2_is_valid(index->table->flags, index->table->flags2)); @@ -994,22 +944,6 @@ row_log_table_low( ut_ad(rec_offs_size(offsets) <= sizeof log->tail.buf); ut_ad(index->lock.have_any()); -#ifdef UNIV_DEBUG - switch (fil_page_get_type(page_align(rec))) { - case FIL_PAGE_INDEX: - break; - case FIL_PAGE_TYPE_INSTANT: - ut_ad(index->is_instant()); - ut_ad(!page_has_siblings(page_align(rec))); - ut_ad(page_get_page_no(page_align(rec)) == index->page); - break; - default: - ut_ad("wrong page type" == 0); - } -#endif /* UNIV_DEBUG */ - ut_ad(!rec_is_metadata(rec, *index)); - ut_ad(page_rec_is_leaf(rec)); - ut_ad(!page_is_comp(page_align(rec)) == !rec_offs_comp(offsets)); /* old_pk=row_log_table_get_pk() [not needed in INSERT] is a prefix of the clustered index record (PRIMARY KEY,DB_TRX_ID,DB_ROLL_PTR), with no information on virtual columns */ @@ -1028,7 +962,6 @@ row_log_table_low( return; } - ut_ad(page_is_comp(page_align(rec))); ut_ad(rec_get_status(rec) == REC_STATUS_ORDINARY || rec_get_status(rec) == REC_STATUS_INSTANT); @@ -1471,78 +1404,6 @@ row_log_table_insert( } /******************************************************//** -Notes that a BLOB is being freed during online ALTER TABLE. */ -void -row_log_table_blob_free( -/*====================*/ - dict_index_t* index, /*!< in/out: clustered index, X-latched */ - ulint page_no)/*!< in: starting page number of the BLOB */ -{ - ut_ad(dict_index_is_clust(index)); - ut_ad(dict_index_is_online_ddl(index)); - ut_ad(index->lock.have_u_or_x()); - ut_ad(page_no != FIL_NULL); - - if (index->online_log->error != DB_SUCCESS) { - return; - } - - page_no_map* blobs = index->online_log->blobs; - - if (blobs == NULL) { - index->online_log->blobs = blobs = UT_NEW_NOKEY(page_no_map()); - } - -#ifdef UNIV_DEBUG - const ulonglong log_pos = index->online_log->tail.total; -#else -# define log_pos /* empty */ -#endif /* UNIV_DEBUG */ - - const page_no_map::value_type v(page_no, - row_log_table_blob_t(log_pos)); - - std::pair<page_no_map::iterator,bool> p = blobs->insert(v); - - if (!p.second) { - /* Update the existing mapping. */ - ut_ad(p.first->first == page_no); - p.first->second.blob_free(log_pos); - } -#undef log_pos -} - -/******************************************************//** -Notes that a BLOB is being allocated during online ALTER TABLE. */ -void -row_log_table_blob_alloc( -/*=====================*/ - dict_index_t* index, /*!< in/out: clustered index, X-latched */ - ulint page_no)/*!< in: starting page number of the BLOB */ -{ - ut_ad(dict_index_is_clust(index)); - ut_ad(dict_index_is_online_ddl(index)); - ut_ad(index->lock.have_u_or_x()); - - ut_ad(page_no != FIL_NULL); - - if (index->online_log->error != DB_SUCCESS) { - return; - } - - /* Only track allocations if the same page has been freed - earlier. Double allocation without a free is not allowed. */ - if (page_no_map* blobs = index->online_log->blobs) { - page_no_map::iterator p = blobs->find(page_no); - - if (p != blobs->end()) { - ut_ad(p->first == page_no); - p->second.blob_alloc(index->online_log->tail.total); - } - } -} - -/******************************************************//** Converts a log record to a table row. @return converted row, or NULL if the conversion fails */ static MY_ATTRIBUTE((nonnull, warn_unused_result)) @@ -1618,34 +1479,13 @@ row_log_table_apply_convert_mrec( ut_ad(rec_offs_any_extern(offsets)); index->lock.x_lock(SRW_LOCK_CALL); - if (const page_no_map* blobs = log->blobs) { - data = rec_get_nth_field( - mrec, offsets, i, &len); - ut_ad(len >= BTR_EXTERN_FIELD_REF_SIZE); - - ulint page_no = mach_read_from_4( - data + len - (BTR_EXTERN_FIELD_REF_SIZE - - BTR_EXTERN_PAGE_NO)); - page_no_map::const_iterator p = blobs->find( - page_no); - if (p != blobs->end() - && p->second.is_freed(log->head.total)) { - /* This BLOB has been freed. - We must not access the row. */ - *error = DB_MISSING_HISTORY; - dfield_set_data(dfield, data, len); - dfield_set_ext(dfield); - goto blob_done; - } - } - data = btr_rec_copy_externally_stored_field( mrec, offsets, index->table->space->zip_size(), i, &len, heap); ut_a(data); dfield_set_data(dfield, data, len); -blob_done: + index->lock.x_unlock(); } else { data = rec_get_nth_field(mrec, offsets, i, &len); @@ -1693,6 +1533,12 @@ blob_done: if ((new_col->prtype & DATA_NOT_NULL) && dfield_is_null(dfield)) { + if (!log->allow_not_null) { + /* We got a NULL value for a NOT NULL column. */ + *error = DB_INVALID_NULL; + return NULL; + } + const dfield_t& default_field = log->defaults->fields[col_no]; @@ -1702,12 +1548,6 @@ blob_done: WARN_DATA_TRUNCATED, 1, ulong(log->n_rows)); - if (!log->allow_not_null) { - /* We got a NULL value for a NOT NULL column. */ - *error = DB_INVALID_NULL; - return NULL; - } - *dfield = default_field; } @@ -1818,15 +1658,6 @@ row_log_table_apply_insert( mrec, dup->index, offsets, log, heap, &error); switch (error) { - case DB_MISSING_HISTORY: - ut_ad(log->blobs); - /* Because some BLOBs are missing, we know that the - transaction was rolled back later (a rollback of - an insert can free BLOBs). - We can simply skip the insert: the subsequent - ROW_T_DELETE will be ignored, or a ROW_T_UPDATE will - be interpreted as ROW_T_INSERT. */ - return(DB_SUCCESS); case DB_SUCCESS: ut_ad(row != NULL); break; @@ -2101,20 +1932,6 @@ row_log_table_apply_update( mrec, dup->index, offsets, log, heap, &error); switch (error) { - case DB_MISSING_HISTORY: - /* The record contained BLOBs that are now missing. */ - ut_ad(log->blobs); - /* Whether or not we are updating the PRIMARY KEY, we - know that there should be a subsequent - ROW_T_DELETE for rolling back a preceding ROW_T_INSERT, - overriding this ROW_T_UPDATE record. (*1) - - This allows us to interpret this ROW_T_UPDATE - as ROW_T_DELETE. - - When applying the subsequent ROW_T_DELETE, no matching - record will be found. */ - /* fall through */ case DB_SUCCESS: ut_ad(row != NULL); break; @@ -2144,79 +1961,16 @@ row_log_table_apply_update( } #endif /* UNIV_DEBUG */ - if (page_rec_is_infimum(btr_pcur_get_rec(&pcur)) - || btr_pcur_get_low_match(&pcur) < index->n_uniq) { - /* The record was not found. This should only happen - when an earlier ROW_T_INSERT or ROW_T_UPDATE was - diverted because BLOBs were freed when the insert was - later rolled back. */ - - ut_ad(log->blobs); - - if (error == DB_SUCCESS) { - /* An earlier ROW_T_INSERT could have been - skipped because of a missing BLOB, like this: - - BEGIN; - INSERT INTO t SET blob_col='blob value'; - UPDATE t SET blob_col=''; - ROLLBACK; - - This would generate the following records: - ROW_T_INSERT (referring to 'blob value') - ROW_T_UPDATE - ROW_T_UPDATE (referring to 'blob value') - ROW_T_DELETE - [ROLLBACK removes the 'blob value'] - - The ROW_T_INSERT would have been skipped - because of a missing BLOB. Now we are - executing the first ROW_T_UPDATE. - The second ROW_T_UPDATE (for the ROLLBACK) - would be interpreted as ROW_T_DELETE, because - the BLOB would be missing. - - We could probably assume that the transaction - has been rolled back and simply skip the - 'insert' part of this ROW_T_UPDATE record. - However, there might be some complex scenario - that could interfere with such a shortcut. - So, we will insert the row (and risk - introducing a bogus duplicate key error - for the ALTER TABLE), and a subsequent - ROW_T_UPDATE or ROW_T_DELETE will delete it. */ - mtr_commit(&mtr); - error = row_log_table_apply_insert_low( - thr, row, offsets_heap, heap, dup); - } else { - /* Some BLOBs are missing, so we are interpreting - this ROW_T_UPDATE as ROW_T_DELETE (see *1). - Because the record was not found, we do nothing. */ - ut_ad(error == DB_MISSING_HISTORY); - error = DB_SUCCESS; -func_exit: - mtr_commit(&mtr); - } -func_exit_committed: - ut_ad(mtr.has_committed()); - - if (error != DB_SUCCESS) { - /* Report the erroneous row using the new - version of the table. */ - innobase_row_to_mysql(dup->table, log->table, row); - } - - return(error); - } + ut_ad(!page_rec_is_infimum(btr_pcur_get_rec(&pcur)) + && btr_pcur_get_low_match(&pcur) >= index->n_uniq); /* Prepare to update (or delete) the record. */ rec_offs* cur_offsets = rec_get_offsets( btr_pcur_get_rec(&pcur), index, nullptr, index->n_core_fields, ULINT_UNDEFINED, &offsets_heap); +#ifdef UNIV_DEBUG if (!log->same_pk) { - /* Only update the record if DB_TRX_ID,DB_ROLL_PTR match what - was buffered. */ ulint len; const byte* rec_trx_id = rec_get_nth_field(btr_pcur_get_rec(&pcur), @@ -2231,60 +1985,29 @@ func_exit_committed: + static_cast<const char*>(old_pk_trx_id->data) == old_pk_trx_id[1].data); ut_d(trx_id_check(old_pk_trx_id->data, log->min_trx)); - - if (memcmp(rec_trx_id, old_pk_trx_id->data, - DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN)) { - /* The ROW_T_UPDATE was logged for a different - DB_TRX_ID,DB_ROLL_PTR. This is possible if an - earlier ROW_T_INSERT or ROW_T_UPDATE was diverted - because some BLOBs were missing due to rolling - back the initial insert or due to purging - the old BLOB values of an update. */ - ut_ad(log->blobs); - if (error != DB_SUCCESS) { - ut_ad(error == DB_MISSING_HISTORY); - /* Some BLOBs are missing, so we are - interpreting this ROW_T_UPDATE as - ROW_T_DELETE (see *1). - Because this is a different row, - we will do nothing. */ - error = DB_SUCCESS; - } else { - /* Because the user record is missing due to - BLOBs that were missing when processing - an earlier log record, we should - interpret the ROW_T_UPDATE as ROW_T_INSERT. - However, there is a different user record - with the same PRIMARY KEY value already. */ - error = DB_DUPLICATE_KEY; - } - - goto func_exit; - } - } - - if (error != DB_SUCCESS) { - ut_ad(error == DB_MISSING_HISTORY); - ut_ad(log->blobs); - /* Some BLOBs are missing, so we are interpreting - this ROW_T_UPDATE as ROW_T_DELETE (see *1). */ - error = row_log_table_apply_delete_low( - &pcur, cur_offsets, heap, &mtr); - goto func_exit_committed; + ut_ad(!memcmp(rec_trx_id, old_pk_trx_id->data, + DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN)); } +#endif dtuple_t* entry = row_build_index_entry_low( row, NULL, index, heap, ROW_BUILD_NORMAL); upd_t* update = row_upd_build_difference_binary( index, entry, btr_pcur_get_rec(&pcur), cur_offsets, false, NULL, heap, dup->table, &error); - if (error != DB_SUCCESS) { - goto func_exit; - } + if (error != DB_SUCCESS || !update->n_fields) { +func_exit: + mtr.commit(); +func_exit_committed: + ut_ad(mtr.has_committed()); - if (!update->n_fields) { - /* Nothing to do. */ - goto func_exit; + if (error != DB_SUCCESS) { + /* Report the erroneous row using the new + version of the table. */ + innobase_row_to_mysql(dup->table, log->table, row); + } + + return error; } const bool pk_updated @@ -2739,7 +2462,8 @@ ulint row_log_estimate_work( const dict_index_t* index) { - if (index == NULL || index->online_log == NULL) { + if (index == NULL || index->online_log == NULL + || index->online_log_is_dummy()) { return(0); } @@ -3230,7 +2954,6 @@ row_log_allocate( log->fd = OS_FILE_CLOSED; mysql_mutex_init(index_online_log_key, &log->mutex, nullptr); - log->blobs = NULL; log->table = table; log->same_pk = same_pk; log->defaults = defaults; @@ -3280,6 +3003,15 @@ row_log_allocate( } index->online_log = log; + + if (!table) { + /* Assign the clustered index online log to table. + It can be used by concurrent DML to identify whether + the table has any online DDL */ + index->table->indexes.start->online_log_make_dummy(); + log->alter_trx = trx; + } + /* While we might be holding an exclusive data dictionary lock here, in row_log_abort_sec() we will not always be holding it. Use atomic operations in both cases. */ @@ -3297,7 +3029,6 @@ row_log_free( { MONITOR_ATOMIC_DEC(MONITOR_ONLINE_CREATE_INDEX); - UT_DELETE(log->blobs); UT_DELETE_ARRAY(log->non_core_fields); row_log_block_free(log->tail); row_log_block_free(log->head); @@ -3698,7 +3429,8 @@ interrupted) @param[in,out] dup for reporting duplicate key errors @param[in,out] stage performance schema accounting object, used by ALTER TABLE. If not NULL, then stage->inc() will be called for each block -of log that is applied. +of log that is applied or nullptr when row log applied done by DML +thread. @return DB_SUCCESS, or error code on failure */ static dberr_t @@ -3720,7 +3452,9 @@ row_log_apply_ops( const ulint i = 1 + REC_OFFS_HEADER_SIZE + dict_index_get_n_fields(index); - ut_ad(dict_index_is_online_ddl(index)); + ut_ad(dict_index_is_online_ddl(index) + || (index->online_log + && index->online_status == ONLINE_INDEX_COMPLETE)); ut_ad(!index->is_committed()); ut_ad(index->lock.have_x()); ut_ad(index->online_log); @@ -3740,7 +3474,9 @@ next_block: ut_ad(index->lock.have_x()); ut_ad(index->online_log->head.bytes == 0); - stage->inc(row_log_progress_inc_per_block()); + if (stage) { + stage->inc(row_log_progress_inc_per_block()); + } if (trx_is_interrupted(trx)) { goto interrupted; @@ -3794,6 +3530,8 @@ all_done: ut_ad(has_index_lock); ut_ad(index->online_log->head.blocks == 0); ut_ad(index->online_log->tail.blocks == 0); + index->online_log->tail.bytes = 0; + index->online_log->head.bytes = 0; error = DB_SUCCESS; goto func_exit; } @@ -4029,7 +3767,8 @@ interrupted) @param[in,out] table MySQL table (for reporting duplicates) @param[in,out] stage performance schema accounting object, used by ALTER TABLE. stage->begin_phase_log_index() will be called initially and then -stage->inc() will be called for each block of log that is applied. +stage->inc() will be called for each block of log that is applied or nullptr +when row log has been applied by DML thread. @return DB_SUCCESS, or error code on failure */ dberr_t row_log_apply( @@ -4039,20 +3778,24 @@ row_log_apply( ut_stage_alter_t* stage) { dberr_t error; - row_log_t* log; row_merge_dup_t dup = { index, table, NULL, 0 }; DBUG_ENTER("row_log_apply"); - ut_ad(dict_index_is_online_ddl(index)); + ut_ad(dict_index_is_online_ddl(index) + || (index->online_log + && index->online_status == ONLINE_INDEX_COMPLETE)); ut_ad(!dict_index_is_clust(index)); - stage->begin_phase_log_index(); + if (stage) { + stage->begin_phase_log_index(); + } log_free_check(); index->lock.x_lock(SRW_LOCK_CALL); - if (!dict_table_is_corrupted(index->table)) { + if (!dict_table_is_corrupted(index->table) + && index->online_log) { error = row_log_apply_ops(trx, index, &dup, stage); } else { error = DB_SUCCESS; @@ -4067,17 +3810,15 @@ row_log_apply( index->table->drop_aborted = TRUE; dict_index_set_online_status(index, ONLINE_INDEX_ABORTED); - } else { + } else if (stage) { + /* Mark the index as completed only when it is + being called by DDL thread */ ut_ad(dup.n_dup == 0); dict_index_set_online_status(index, ONLINE_INDEX_COMPLETE); } - log = index->online_log; - index->online_log = NULL; index->lock.x_unlock(); - row_log_free(log); - DBUG_RETURN(error); } @@ -4102,6 +3843,12 @@ static void row_log_table_empty(dict_index_t *index) } } +dberr_t row_log_get_error(const dict_index_t *index) +{ + ut_ad(index->online_log); + return index->online_log->error; +} + void dict_table_t::clear(que_thr_t *thr) { bool rebuild= false; @@ -4138,3 +3885,279 @@ void dict_table_t::clear(que_thr_t *thr) index->clear(thr); } } + +const rec_t * +UndorecApplier::get_old_rec(const dtuple_t &tuple, dict_index_t *index, + const rec_t **clust_rec, rec_offs **offsets) +{ + ut_ad(index->is_primary()); + btr_pcur_t pcur; + + bool found= row_search_on_row_ref(&pcur, BTR_MODIFY_LEAF, + index->table, &tuple, &mtr); + ut_a(found); + *clust_rec= btr_pcur_get_rec(&pcur); + + ulint len= 0; + rec_t *prev_version; + const rec_t *version= *clust_rec; + do + { + *offsets= rec_get_offsets(version, index, *offsets, + index->n_core_fields, ULINT_UNDEFINED, + &heap); + roll_ptr_t roll_ptr= trx_read_roll_ptr( + rec_get_nth_field(version, *offsets, index->db_roll_ptr(), &len)); + ut_ad(len == DATA_ROLL_PTR_LEN); + if (is_same(roll_ptr)) + return version; + trx_undo_prev_version_build(*clust_rec, &mtr, version, index, + *offsets, heap, &prev_version, nullptr, + nullptr, 0, block); + version= prev_version; + } + while (version); + + return nullptr; +} + +/** Clear out all online log of other online indexes after +encountering the error during row_log_apply() in DML thread +@param table table which does online DDL */ +static void row_log_mark_other_online_index_abort(dict_table_t *table) +{ + dict_index_t *clust_index= dict_table_get_first_index(table); + for (dict_index_t *index= dict_table_get_next_index(clust_index); + index; index= dict_table_get_next_index(index)) + { + if (index->online_log && + index->online_status <= ONLINE_INDEX_CREATION && + !index->is_corrupted()) + { + index->lock.x_lock(SRW_LOCK_CALL); + row_log_abort_sec(index); + index->type|= DICT_CORRUPT; + index->lock.x_unlock(); + MONITOR_ATOMIC_INC(MONITOR_BACKGROUND_DROP_INDEX); + } + } + + clust_index->lock.x_lock(SRW_LOCK_CALL); + clust_index->online_log= nullptr; + clust_index->lock.x_unlock(); + table->drop_aborted= TRUE; +} + +void UndorecApplier::log_insert(const dtuple_t &tuple, + dict_index_t *clust_index) +{ + DEBUG_SYNC_C("row_log_insert_handle"); + ut_ad(clust_index->is_primary()); + rec_offs offsets_[REC_OFFS_NORMAL_SIZE]; + rec_offs *offsets= offsets_; + + rec_offs_init(offsets_); + mtr.start(); + const rec_t *rec; + const rec_t *match_rec= get_old_rec(tuple, clust_index, &rec, &offsets); + if (!match_rec) + { + mtr.commit(); + return; + } + const rec_t *copy_rec= match_rec; + if (match_rec == rec) + { + copy_rec= rec_copy(mem_heap_alloc( + heap, rec_offs_size(offsets)), match_rec, offsets); + rec_offs_make_valid(copy_rec, clust_index, true, offsets); + } + mtr.commit(); + + dict_table_t *table= clust_index->table; + clust_index->lock.s_lock(SRW_LOCK_CALL); + if (clust_index->online_log && + !clust_index->online_log_is_dummy() && + clust_index->online_status <= ONLINE_INDEX_CREATION) + { + row_log_table_insert(copy_rec, clust_index, offsets); + clust_index->lock.s_unlock(); + } + else + { + clust_index->lock.s_unlock(); + row_ext_t *ext; + dtuple_t *row= row_build(ROW_COPY_POINTERS, clust_index, + copy_rec, offsets, table, nullptr, nullptr, &ext, heap); + + if (table->n_v_cols) + { + /* Update the row with virtual column values present + in the undo log or update vector */ + if (type == TRX_UNDO_UPD_DEL_REC) + row_upd_replace_vcol(row, table, update, false, + nullptr, + (cmpl_info & UPD_NODE_NO_ORD_CHANGE) + ? nullptr : undo_rec); + else + trx_undo_read_v_cols(table, undo_rec, row, false); + } + + bool success= true; + dict_index_t *index= dict_table_get_next_index(clust_index); + while (index) + { + index->lock.s_lock(SRW_LOCK_CALL); + if (index->online_log && + index->online_status <= ONLINE_INDEX_CREATION && + !index->is_corrupted()) + { + dtuple_t *entry= row_build_index_entry_low(row, ext, index, + heap, ROW_BUILD_NORMAL); + success= row_log_online_op(index, entry, trx_id); + } + + index->lock.s_unlock(); + if (!success) + { + row_log_mark_other_online_index_abort(index->table); + return; + } + index= dict_table_get_next_index(index); + } + } +} + +void UndorecApplier::log_update(const dtuple_t &tuple, + dict_index_t *clust_index) +{ + rec_offs offsets_[REC_OFFS_NORMAL_SIZE]; + rec_offs offsets2_[REC_OFFS_NORMAL_SIZE]; + rec_offs *offsets= offsets_; + rec_offs *prev_offsets= offsets2_; + + rec_offs_init(offsets_); + rec_offs_init(offsets2_); + + dict_table_t *table= clust_index->table; + + clust_index->lock.s_lock(SRW_LOCK_CALL); + bool table_rebuild= + (clust_index->online_log + && !clust_index->online_log_is_dummy() + && clust_index->online_status <= ONLINE_INDEX_CREATION); + clust_index->lock.s_unlock(); + + mtr.start(); + const rec_t *rec; + rec_t *prev_version; + bool is_update= (type == TRX_UNDO_UPD_EXIST_REC); + const rec_t *match_rec= get_old_rec(tuple, clust_index, &rec, &offsets); + if (!match_rec) + { + mtr.commit(); + return; + } + + if (table_rebuild) + { + const rec_t *copy_rec= match_rec; + if (match_rec == rec) + copy_rec= rec_copy(mem_heap_alloc( + heap, rec_offs_size(offsets)), match_rec, offsets); + trx_undo_prev_version_build(rec, &mtr, match_rec, clust_index, + offsets, heap, &prev_version, nullptr, + nullptr, 0, block); + + prev_offsets= rec_get_offsets(prev_version, clust_index, prev_offsets, + clust_index->n_core_fields, + ULINT_UNDEFINED, &heap); + rec_offs_make_valid(copy_rec, clust_index, true, offsets); + mtr.commit(); + + clust_index->lock.s_lock(SRW_LOCK_CALL); + /* Recheck whether clustered index online log has been cleared */ + if (clust_index->online_log) + { + if (is_update) + { + const dtuple_t *rebuilt_old_pk= row_log_table_get_pk( + prev_version, clust_index, prev_offsets, nullptr, &heap); + row_log_table_update(copy_rec, clust_index, offsets, rebuilt_old_pk); + } + else + row_log_table_delete(prev_version, clust_index, prev_offsets, nullptr); + } + clust_index->lock.s_unlock(); + return; + } + + dtuple_t *row= nullptr; + row_ext_t *new_ext; + if (match_rec != rec) + row= row_build(ROW_COPY_POINTERS, clust_index, match_rec, offsets, + clust_index->table, NULL, NULL, &new_ext, heap); + else + row= row_build(ROW_COPY_DATA, clust_index, rec, offsets, + clust_index->table, NULL, NULL, &new_ext, heap); + mtr.commit(); + row_ext_t *old_ext; + dtuple_t *old_row= nullptr; + if (!(this->cmpl_info & UPD_NODE_NO_ORD_CHANGE)) + { + for (ulint i = 0; i < dict_table_get_n_v_cols(table); i++) + dfield_get_type( + dtuple_get_nth_v_field(row, i))->mtype = DATA_MISSING; + } + + if (is_update) + { + old_row= dtuple_copy(row, heap); + row_upd_replace(old_row, &old_ext, clust_index, update, heap); + } + + if (table->n_v_cols) + row_upd_replace_vcol(row, table, update, false, nullptr, + (cmpl_info & UPD_NODE_NO_ORD_CHANGE) + ? nullptr : this->undo_rec); + + bool success= true; + dict_index_t *index= dict_table_get_next_index(clust_index); + while (index) + { + index->lock.s_lock(SRW_LOCK_CALL); + if (index->online_log && + index->online_status <= ONLINE_INDEX_CREATION && + !index->is_corrupted()) + { + if (is_update) + { + dtuple_t *old_entry= row_build_index_entry_low( + old_row, old_ext, index, heap, ROW_BUILD_NORMAL); + + success= row_log_online_op(index, old_entry, 0); + + dtuple_t *new_entry= row_build_index_entry_low( + row, new_ext, index, heap, ROW_BUILD_NORMAL); + + if (success) + success= row_log_online_op(index, new_entry, trx_id); + } + else + { + dtuple_t *old_entry= row_build_index_entry_low( + row, new_ext, index, heap, ROW_BUILD_NORMAL); + + success= row_log_online_op(index, old_entry, 0); + } + } + index->lock.s_unlock(); + if (!success) + { + row_log_mark_other_online_index_abort(index->table); + return; + } + index= dict_table_get_next_index(index); + } +} + diff --git a/storage/innobase/row/row0merge.cc b/storage/innobase/row/row0merge.cc index 1eae0a8e0b6..ce558ddbc29 100644 --- a/storage/innobase/row/row0merge.cc +++ b/storage/innobase/row/row0merge.cc @@ -853,7 +853,7 @@ row_merge_dup_report( row_merge_dup_t* dup, /*!< in/out: for reporting duplicates */ const dfield_t* entry) /*!< in: duplicate index entry */ { - if (!dup->n_dup++) { + if (!dup->n_dup++ && dup->table) { /* Only report the first duplicate record, but count all duplicate records. */ innobase_fields_to_mysql(dup->table, dup->index, entry); @@ -3629,11 +3629,7 @@ row_merge_insert_index_tuples( Any modifications after the row_merge_read_clustered_index() scan - will go through row_log_table_apply(). - Any modifications to off-page columns - will be tracked by - row_log_table_blob_alloc() and - row_log_table_blob_free(). */ + will go through row_log_table_apply(). */ row_merge_copy_blobs( mrec, offsets, old_table->space->zip_size(), dtuple, tuple_heap); @@ -4813,6 +4809,13 @@ func_exit: MONITOR_BACKGROUND_DROP_INDEX); } } + + dict_index_t *clust_index= new_table->indexes.start; + clust_index->lock.x_lock(SRW_LOCK_CALL); + ut_ad(!clust_index->online_log || + clust_index->online_log_is_dummy()); + clust_index->online_log= nullptr; + clust_index->lock.x_unlock(); } DBUG_EXECUTE_IF("ib_index_crash_after_bulk_load", DBUG_SUICIDE();); diff --git a/storage/innobase/row/row0purge.cc b/storage/innobase/row/row0purge.cc index e6267a2023a..3ba2e964689 100644 --- a/storage/innobase/row/row0purge.cc +++ b/storage/innobase/row/row0purge.cc @@ -1,7 +1,7 @@ /***************************************************************************** Copyright (c) 1997, 2017, Oracle and/or its affiliates. All Rights Reserved. -Copyright (c) 2017, 2021, MariaDB Corporation. +Copyright (c) 2017, 2022, MariaDB Corporation. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -41,7 +41,6 @@ Created 3/14/1997 Heikki Tuuri #include "row0upd.h" #include "row0vers.h" #include "row0mysql.h" -#include "row0log.h" #include "log0log.h" #include "srv0mon.h" #include "srv0start.h" @@ -359,27 +358,6 @@ row_purge_remove_sec_if_poss_tree( mtr.start(); index->set_modified(mtr); - if (!index->is_committed()) { - /* The index->online_status may change if the index is - or was being created online, but not committed yet. It - is protected by index->lock. */ - mtr_sx_lock_index(index, &mtr); - - if (dict_index_is_online_ddl(index)) { - /* Online secondary index creation will not - copy any delete-marked records. Therefore - there is nothing to be purged. We must also - skip the purge when a completed index is - dropped by rollback_inplace_alter_table(). */ - goto func_exit_no_pcur; - } - } else { - /* For secondary indexes, - index->online_status==ONLINE_INDEX_COMPLETE if - index->is_committed(). */ - ut_ad(!dict_index_is_online_ddl(index)); - } - search_result = row_search_index_entry( index, entry, BTR_MODIFY_TREE | BTR_LATCH_FOR_DELETE, @@ -452,7 +430,6 @@ row_purge_remove_sec_if_poss_tree( func_exit: btr_pcur_close(&pcur); // FIXME: need this? -func_exit_no_pcur: mtr.commit(); return(success); @@ -483,40 +460,10 @@ row_purge_remove_sec_if_poss_leaf( mtr.start(); index->set_modified(mtr); - if (!index->is_committed()) { - /* For uncommitted spatial index, we also skip the purge. */ - if (dict_index_is_spatial(index)) { - goto func_exit_no_pcur; - } - - /* The index->online_status may change if the the - index is or was being created online, but not - committed yet. It is protected by index->lock. */ - mtr_s_lock_index(index, &mtr); - - if (dict_index_is_online_ddl(index)) { - /* Online secondary index creation will not - copy any delete-marked records. Therefore - there is nothing to be purged. We must also - skip the purge when a completed index is - dropped by rollback_inplace_alter_table(). */ - goto func_exit_no_pcur; - } - - mode = BTR_PURGE_LEAF_ALREADY_S_LATCHED; - } else { - /* For secondary indexes, - index->online_status==ONLINE_INDEX_COMPLETE if - index->is_committed(). */ - ut_ad(!dict_index_is_online_ddl(index)); - - /* Change buffering is disabled for spatial index and - virtual index. */ - mode = (dict_index_is_spatial(index) - || dict_index_has_virtual(index)) - ? BTR_MODIFY_LEAF - : BTR_PURGE_LEAF; - } + /* Change buffering is disabled for spatial index and + virtual index. */ + mode = (index->type & (DICT_SPATIAL | DICT_VIRTUAL)) + ? BTR_MODIFY_LEAF : BTR_PURGE_LEAF; /* Set the purge node for the call to row_purge_poss_sec(). */ pcur.btr_cur.purge_node = node; diff --git a/storage/innobase/row/row0uins.cc b/storage/innobase/row/row0uins.cc index 173ae9a98f9..446d56f8daa 100644 --- a/storage/innobase/row/row0uins.cc +++ b/storage/innobase/row/row0uins.cc @@ -1,7 +1,7 @@ /***************************************************************************** Copyright (c) 1997, 2017, Oracle and/or its affiliates. All Rights Reserved. -Copyright (c) 2017, 2021, MariaDB Corporation. +Copyright (c) 2017, 2022, MariaDB Corporation. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -35,7 +35,6 @@ Created 2/25/1997 Heikki Tuuri #include "mach0data.h" #include "row0undo.h" #include "row0vers.h" -#include "row0log.h" #include "trx0trx.h" #include "trx0rec.h" #include "row0row.h" @@ -70,7 +69,6 @@ row_undo_ins_remove_clust_rec( ulint n_tries = 0; mtr_t mtr; dict_index_t* index = node->pcur.btr_cur.index; - bool online; table_id_t table_id = 0; const bool dict_locked = node->trx->dict_operation_lock_mode; restart: @@ -90,20 +88,10 @@ restart: if (index->table->is_temporary()) { ut_ad(node->rec_type == TRX_UNDO_INSERT_REC); mtr.set_log_mode(MTR_LOG_NO_REDO); - ut_ad(!dict_index_is_online_ddl(index)); ut_ad(index->table->id >= DICT_HDR_FIRST_ID); - online = false; } else { index->set_modified(mtr); ut_ad(lock_table_has_locks(index->table)); - online = dict_index_is_online_ddl(index); - if (online) { - ut_ad(node->rec_type == TRX_UNDO_INSERT_REC); - ut_ad(!node->trx->dict_operation_lock_mode); - ut_ad(node->table->id != DICT_INDEXES_ID); - ut_ad(node->table->id != DICT_COLUMNS_ID); - mtr_s_lock_index(index, &mtr); - } } /* This is similar to row_undo_mod_clust(). The DDL thread may @@ -112,8 +100,7 @@ restart: purged. However, we can log the removal out of sync with the B-tree modification. */ ut_a(node->pcur.restore_position( - online ? BTR_MODIFY_LEAF | BTR_ALREADY_S_LATCHED - : (node->rec_type == TRX_UNDO_INSERT_METADATA) + (node->rec_type == TRX_UNDO_INSERT_METADATA) ? BTR_MODIFY_TREE : BTR_MODIFY_LEAF, &mtr) == btr_pcur_t::SAME_ALL); @@ -126,94 +113,83 @@ restart: ut_ad(rec_is_metadata(rec, index->table->not_redundant()) == (node->rec_type == TRX_UNDO_INSERT_METADATA)); - if (online && dict_index_is_online_ddl(index)) { - mem_heap_t* heap = NULL; - const rec_offs* offsets = rec_get_offsets( - rec, index, NULL, index->n_core_fields, - ULINT_UNDEFINED, &heap); - row_log_table_delete(rec, index, offsets, NULL); - mem_heap_free(heap); - } else { - switch (node->table->id) { - case DICT_COLUMNS_ID: - /* This is rolling back an INSERT into SYS_COLUMNS. - If it was part of an instant ALTER TABLE operation, we - must evict the table definition, so that it can be - reloaded after the dictionary operation has been - completed. At this point, any corresponding operation - to the metadata record will have been rolled back. */ - ut_ad(!online); - ut_ad(node->trx->dict_operation_lock_mode); - ut_ad(node->rec_type == TRX_UNDO_INSERT_REC); - if (rec_get_n_fields_old(rec) - != DICT_NUM_FIELDS__SYS_COLUMNS - || (rec_get_1byte_offs_flag(rec) - ? rec_1_get_field_end_info(rec, 0) != 8 - : rec_2_get_field_end_info(rec, 0) != 8)) { - break; - } - static_assert(!DICT_FLD__SYS_COLUMNS__TABLE_ID, ""); - node->trx->evict_table(mach_read_from_8(rec)); + switch (node->table->id) { + case DICT_COLUMNS_ID: + /* This is rolling back an INSERT into SYS_COLUMNS. + If it was part of an instant ALTER TABLE operation, we + must evict the table definition, so that it can be + reloaded after the dictionary operation has been + completed. At this point, any corresponding operation + to the metadata record will have been rolled back. */ + ut_ad(node->trx->dict_operation_lock_mode); + ut_ad(node->rec_type == TRX_UNDO_INSERT_REC); + if (rec_get_n_fields_old(rec) + != DICT_NUM_FIELDS__SYS_COLUMNS + || (rec_get_1byte_offs_flag(rec) + ? rec_1_get_field_end_info(rec, 0) != 8 + : rec_2_get_field_end_info(rec, 0) != 8)) { break; - case DICT_INDEXES_ID: - ut_ad(!online); - ut_ad(node->trx->dict_operation_lock_mode); - ut_ad(node->rec_type == TRX_UNDO_INSERT_REC); - if (!table_id) { - table_id = mach_read_from_8(rec); - if (table_id) { - mtr.commit(); - goto restart; - } - ut_ad("corrupted SYS_INDEXES record" == 0); + } + static_assert(!DICT_FLD__SYS_COLUMNS__TABLE_ID, ""); + node->trx->evict_table(mach_read_from_8(rec)); + break; + case DICT_INDEXES_ID: + ut_ad(node->trx->dict_operation_lock_mode); + ut_ad(node->rec_type == TRX_UNDO_INSERT_REC); + if (!table_id) { + table_id = mach_read_from_8(rec); + if (table_id) { + mtr.commit(); + goto restart; } + ut_ad("corrupted SYS_INDEXES record" == 0); + } - pfs_os_file_t d = OS_FILE_CLOSED; - - if (const uint32_t space_id = dict_drop_index_tree( - &node->pcur, node->trx, &mtr)) { - if (table) { - lock_release_on_rollback(node->trx, - table); - if (!dict_locked) { - dict_sys.lock(SRW_LOCK_CALL); - } - if (table->release()) { - dict_sys.remove(table); - } else if (table->space_id - == space_id) { - table->space = nullptr; - table->file_unreadable = true; - } - if (!dict_locked) { - dict_sys.unlock(); - } - table = nullptr; - if (!mdl_ticket); - else if (MDL_context* mdl_context = - static_cast<MDL_context*>( - thd_mdl_context( - node->trx-> - mysql_thd))) { - mdl_context->release_lock( - mdl_ticket); - mdl_ticket = nullptr; - } - } + pfs_os_file_t d = OS_FILE_CLOSED; - d = fil_delete_tablespace(space_id); + if (const uint32_t space_id = dict_drop_index_tree( + &node->pcur, node->trx, &mtr)) { + if (table) { + lock_release_on_rollback(node->trx, + table); + if (!dict_locked) { + dict_sys.lock(SRW_LOCK_CALL); + } + if (table->release()) { + dict_sys.remove(table); + } else if (table->space_id + == space_id) { + table->space = nullptr; + table->file_unreadable = true; + } + if (!dict_locked) { + dict_sys.unlock(); + } + table = nullptr; + if (!mdl_ticket); + else if (MDL_context* mdl_context = + static_cast<MDL_context*>( + thd_mdl_context( + node->trx-> + mysql_thd))) { + mdl_context->release_lock( + mdl_ticket); + mdl_ticket = nullptr; + } } - mtr.commit(); + d = fil_delete_tablespace(space_id); + } - if (d != OS_FILE_CLOSED) { - os_file_close(d); - } + mtr.commit(); - mtr.start(); - ut_a(node->pcur.restore_position( - BTR_MODIFY_LEAF, &mtr) == btr_pcur_t::SAME_ALL); + if (d != OS_FILE_CLOSED) { + os_file_close(d); } + + mtr.start(); + ut_a(node->pcur.restore_position( + BTR_MODIFY_LEAF, &mtr) == btr_pcur_t::SAME_ALL); } if (btr_cur_optimistic_delete(&node->pcur.btr_cur, 0, &mtr)) { @@ -299,10 +275,6 @@ row_undo_ins_remove_sec_low( mtr_sx_lock_index(index, &mtr); } - if (row_log_online_op_try(index, entry, 0)) { - goto func_exit_no_pcur; - } - if (dict_index_is_spatial(index)) { if (modify_leaf) { mode |= BTR_RTREE_DELETE_MARK; @@ -346,7 +318,6 @@ row_undo_ins_remove_sec_low( } btr_pcur_close(&pcur); -func_exit_no_pcur: mtr_commit(&mtr); return(err); @@ -546,7 +517,7 @@ row_undo_ins_remove_sec_rec( while (index != NULL) { dtuple_t* entry; - if (index->type & DICT_FTS) { + if (index->type & DICT_FTS || !index->is_committed()) { dict_table_next_uncorrupted_index(index); continue; } diff --git a/storage/innobase/row/row0umod.cc b/storage/innobase/row/row0umod.cc index 494a23eaf0a..f18d7ab1be1 100644 --- a/storage/innobase/row/row0umod.cc +++ b/storage/innobase/row/row0umod.cc @@ -1,7 +1,7 @@ /***************************************************************************** Copyright (c) 1997, 2017, Oracle and/or its affiliates. All Rights Reserved. -Copyright (c) 2017, 2021, MariaDB Corporation. +Copyright (c) 2017, 2022, MariaDB Corporation. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -36,7 +36,6 @@ Created 2/27/1997 Heikki Tuuri #include "ibuf0ibuf.h" #include "row0undo.h" #include "row0vers.h" -#include "row0log.h" #include "trx0trx.h" #include "trx0rec.h" #include "row0row.h" @@ -80,11 +79,6 @@ row_undo_mod_clust_low( mem_heap_t** offsets_heap, /*!< in/out: memory heap that can be emptied */ mem_heap_t* heap, /*!< in/out: memory heap */ - const dtuple_t**rebuilt_old_pk, - /*!< out: row_log_table_get_pk() - before the update, or NULL if - the table is not being rebuilt online or - the PRIMARY KEY definition does not change */ byte* sys, /*!< out: DB_TRX_ID, DB_ROLL_PTR for row_log_table_delete() */ que_thr_t* thr, /*!< in: query thread */ @@ -111,15 +105,6 @@ row_undo_mod_clust_low( || node->update->info_bits == REC_INFO_METADATA_ADD || node->update->info_bits == REC_INFO_METADATA_ALTER); - if (mode != BTR_MODIFY_LEAF - && dict_index_is_online_ddl(btr_cur_get_index(btr_cur))) { - *rebuilt_old_pk = row_log_table_get_pk( - btr_cur_get_rec(btr_cur), - btr_cur_get_index(btr_cur), NULL, sys, &heap); - } else { - *rebuilt_old_pk = NULL; - } - if (mode != BTR_MODIFY_TREE) { ut_ad((mode & ulint(~BTR_ALREADY_S_LATCHED)) == BTR_MODIFY_LEAF); @@ -269,7 +254,6 @@ row_undo_mod_clust( bool have_latch = false; dberr_t err; dict_index_t* index; - bool online; ut_ad(thr_get_trx(thr) == node->trx); ut_ad(node->trx->in_rollback); @@ -287,26 +271,16 @@ row_undo_mod_clust( ut_ad(lock_table_has_locks(index->table)); } - online = dict_index_is_online_ddl(index); - if (online) { - ut_ad(!node->trx->dict_operation_lock_mode); - mtr_s_lock_index(index, &mtr); - } - mem_heap_t* heap = mem_heap_create(1024); mem_heap_t* offsets_heap = NULL; rec_offs* offsets = NULL; - const dtuple_t* rebuilt_old_pk; byte sys[DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN]; /* Try optimistic processing of the record, keeping changes within the index page */ err = row_undo_mod_clust_low(node, &offsets, &offsets_heap, - heap, &rebuilt_old_pk, sys, - thr, &mtr, online - ? BTR_MODIFY_LEAF | BTR_ALREADY_S_LATCHED - : BTR_MODIFY_LEAF); + heap, sys, thr, &mtr, BTR_MODIFY_LEAF); if (err != DB_SUCCESS) { btr_pcur_commit_specify_mtr(pcur, &mtr); @@ -321,34 +295,12 @@ row_undo_mod_clust( index->set_modified(mtr); } - err = row_undo_mod_clust_low( - node, &offsets, &offsets_heap, - heap, &rebuilt_old_pk, sys, - thr, &mtr, BTR_MODIFY_TREE); + err = row_undo_mod_clust_low(node, &offsets, &offsets_heap, + heap, sys, thr, &mtr, + BTR_MODIFY_TREE); ut_ad(err == DB_SUCCESS || err == DB_OUT_OF_FILE_SPACE); } - if (err == DB_SUCCESS && online && dict_index_is_online_ddl(index)) { - switch (node->rec_type) { - case TRX_UNDO_DEL_MARK_REC: - row_log_table_insert( - btr_pcur_get_rec(pcur), index, offsets); - break; - case TRX_UNDO_UPD_EXIST_REC: - row_log_table_update( - btr_pcur_get_rec(pcur), index, offsets, - rebuilt_old_pk); - break; - case TRX_UNDO_UPD_DEL_REC: - row_log_table_delete( - btr_pcur_get_rec(pcur), index, offsets, sys); - break; - default: - ut_ad(0); - break; - } - } - /** * when scrubbing, and records gets cleared, * the transaction id is not present afterwards. @@ -570,10 +522,6 @@ row_undo_mod_del_mark_or_remove_sec_low( ut_ad(mode == (BTR_MODIFY_TREE | BTR_LATCH_FOR_DELETE)); mtr_sx_lock_index(index, &mtr); } - - if (row_log_online_op_try(index, entry, 0)) { - goto func_exit_no_pcur; - } } else { /* For secondary indexes, index->online_status==ONLINE_INDEX_COMPLETE if @@ -669,7 +617,6 @@ row_undo_mod_del_mark_or_remove_sec_low( func_exit: btr_pcur_close(&pcur); -func_exit_no_pcur: mtr_commit(&mtr); return(err); @@ -753,28 +700,6 @@ row_undo_mod_del_unmark_sec_and_undo_update( try_again: row_mtr_start(&mtr, index, !(mode & BTR_MODIFY_LEAF)); - if (!index->is_committed()) { - /* The index->online_status may change if the index is - or was being created online, but not committed yet. It - is protected by index->lock. */ - if (mode == BTR_MODIFY_LEAF) { - mode = BTR_MODIFY_LEAF | BTR_ALREADY_S_LATCHED; - mtr_s_lock_index(index, &mtr); - } else { - ut_ad(mode == BTR_MODIFY_TREE); - mtr_sx_lock_index(index, &mtr); - } - - if (row_log_online_op_try(index, entry, trx->id)) { - goto func_exit_no_pcur; - } - } else { - /* For secondary indexes, - index->online_status==ONLINE_INDEX_COMPLETE if - index->is_committed(). */ - ut_ad(!dict_index_is_online_ddl(index)); - } - btr_cur->thr = thr; search_result = row_search_index_entry(index, entry, mode, @@ -802,34 +727,26 @@ try_again: } } - if (index->is_committed()) { - /* During online secondary index creation, it - is possible that MySQL is waiting for a - meta-data lock upgrade before invoking - ha_innobase::commit_inplace_alter_table() - while this ROLLBACK is executing. InnoDB has - finished building the index, but it does not - yet exist in MySQL. In this case, we suppress - the printout to the error log. */ + if (btr_cur->up_match >= dict_index_get_n_unique(index) + || btr_cur->low_match >= dict_index_get_n_unique(index)) { ib::warn() << "Record in index " << index->name << " of table " << index->table->name - << " was not found on rollback, trying to" - " insert: " << *entry + << " was not found on rollback, and" + " a duplicate exists: " + << *entry << " at: " << rec_index_print( btr_cur_get_rec(btr_cur), index); - } - - if (btr_cur->up_match >= dict_index_get_n_unique(index) - || btr_cur->low_match >= dict_index_get_n_unique(index)) { - if (index->is_committed()) { - ib::warn() << "Record in index " << index->name - << " was not found on rollback, and" - " a duplicate exists"; - } err = DB_DUPLICATE_KEY; break; } + ib::warn() << "Record in index " << index->name + << " of table " << index->table->name + << " was not found on rollback, trying to insert: " + << *entry + << " at: " << rec_index_print( + btr_cur_get_rec(btr_cur), index); + /* Insert the missing record that we were trying to delete-unmark. */ big_rec_t* big_rec; @@ -912,7 +829,6 @@ try_again: } btr_pcur_close(&pcur); -func_exit_no_pcur: mtr_commit(&mtr); return(err); @@ -940,7 +856,7 @@ row_undo_mod_upd_del_sec( dict_index_t* index = node->index; dtuple_t* entry; - if (index->type & DICT_FTS) { + if (index->type & DICT_FTS || !index->is_committed()) { dict_table_next_uncorrupted_index(node->index); continue; } @@ -1006,7 +922,7 @@ row_undo_mod_del_mark_sec( dict_index_t* index = node->index; dtuple_t* entry; - if (index->type == DICT_FTS) { + if (index->type == DICT_FTS || !index->is_committed()) { dict_table_next_uncorrupted_index(node->index); continue; } @@ -1076,6 +992,12 @@ row_undo_mod_upd_exist_sec( while (node->index != NULL) { + + if (!node->index->is_committed()) { + dict_table_next_uncorrupted_index(node->index); + continue; + } + dict_index_t* index = node->index; dtuple_t* entry; diff --git a/storage/innobase/row/row0upd.cc b/storage/innobase/row/row0upd.cc index 4d364c19540..5b89582e23b 100644 --- a/storage/innobase/row/row0upd.cc +++ b/storage/innobase/row/row0upd.cc @@ -1,7 +1,7 @@ /***************************************************************************** Copyright (c) 1996, 2017, Oracle and/or its affiliates. All Rights Reserved. -Copyright (c) 2015, 2021, MariaDB Corporation. +Copyright (c) 2015, 2022, MariaDB Corporation. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -1081,16 +1081,7 @@ row_upd_replace_vcol( /* If there is no index on the column, do not bother for value update */ if (!col->m_col.ord_part) { - dict_index_t* clust_index - = dict_table_get_first_index(table); - - /* Skip the column if there is no online alter - table in progress or it is not being indexed - in new table */ - if (!dict_index_is_online_ddl(clust_index) - || !row_log_col_is_indexed(clust_index, col_no)) { - continue; - } + continue; } dfield = dtuple_get_nth_v_field(row, col_no); @@ -1906,6 +1897,13 @@ row_upd_sec_index_entry( ut_ad(trx->id != 0); index = node->index; + if (!index->is_committed()) { + return DB_SUCCESS; + } + + /* For secondary indexes, index->online_status==ONLINE_INDEX_COMPLETE + if index->is_committed(). */ + ut_ad(!dict_index_is_online_ddl(index)); const bool referenced = row_upd_index_is_referenced(index, trx); #ifdef WITH_WSREP @@ -1929,75 +1927,22 @@ row_upd_sec_index_entry( case SRV_TMP_SPACE_ID: mtr.set_log_mode(MTR_LOG_NO_REDO); flags = BTR_NO_LOCKING_FLAG; + mode = index->is_spatial() + ? ulint(BTR_MODIFY_LEAF | BTR_RTREE_DELETE_MARK) + : ulint(BTR_MODIFY_LEAF); break; default: index->set_modified(mtr); /* fall through */ case IBUF_SPACE_ID: flags = index->table->no_rollback() ? BTR_NO_ROLLBACK : 0; - break; - } - - bool uncommitted = !index->is_committed(); - - if (uncommitted) { - /* The index->online_status may change if the index is - or was being created online, but not committed yet. It - is protected by index->lock. */ - - mtr_s_lock_index(index, &mtr); - - switch (dict_index_get_online_status(index)) { - case ONLINE_INDEX_COMPLETE: - /* This is a normal index. Do not log anything. - Perform the update on the index tree directly. */ - break; - case ONLINE_INDEX_CREATION: - /* Log a DELETE and optionally INSERT. */ - row_log_online_op(index, entry, 0); - - if (!node->is_delete) { - mem_heap_empty(heap); - entry = row_build_index_entry( - node->upd_row, node->upd_ext, - index, heap); - ut_a(entry); - row_log_online_op(index, entry, trx->id); - } - /* fall through */ - case ONLINE_INDEX_ABORTED: - case ONLINE_INDEX_ABORTED_DROPPED: - mtr_commit(&mtr); - goto func_exit; - } - /* We can only buffer delete-mark operations if there - are no foreign key constraints referring to the index. - Change buffering is disabled for temporary tables and - spatial index. */ - mode = (referenced || index->table->is_temporary() - || dict_index_is_spatial(index)) - ? BTR_MODIFY_LEAF_ALREADY_S_LATCHED - : BTR_DELETE_MARK_LEAF_ALREADY_S_LATCHED; - } else { - /* For secondary indexes, - index->online_status==ONLINE_INDEX_COMPLETE if - index->is_committed(). */ - ut_ad(!dict_index_is_online_ddl(index)); - - /* We can only buffer delete-mark operations if there - are no foreign key constraints referring to the index. - Change buffering is disabled for temporary tables and - spatial index. */ - mode = (referenced || index->table->is_temporary() - || dict_index_is_spatial(index)) - ? BTR_MODIFY_LEAF - : BTR_DELETE_MARK_LEAF; - } - - if (dict_index_is_spatial(index)) { - ut_ad(mode & BTR_MODIFY_LEAF); - mode |= BTR_RTREE_DELETE_MARK; + are no foreign key constraints referring to the index. */ + mode = index->is_spatial() + ? ulint(BTR_MODIFY_LEAF | BTR_RTREE_DELETE_MARK) + : referenced + ? ulint(BTR_MODIFY_LEAF) : ulint(BTR_DELETE_MARK_LEAF); + break; } /* Set the query thread, so that ibuf_insert_low() will be @@ -2020,19 +1965,6 @@ row_upd_sec_index_entry( break; case ROW_NOT_FOUND: - if (!index->is_committed()) { - /* When online CREATE INDEX copied the update - that we already made to the clustered index, - and completed the secondary index creation - before we got here, the old secondary index - record would not exist. The CREATE INDEX - should be waiting for a MySQL meta-data lock - upgrade at least until this UPDATE returns. - After that point, set_committed(true) would be - invoked by commit_inplace_alter_table(). */ - break; - } - if (dict_index_is_spatial(index) && btr_cur->rtr_info->fd_del) { /* We found the record, but a delete marked */ break; @@ -2139,35 +2071,11 @@ row_upd_sec_index_entry( DEBUG_SYNC_C_IF_THD(trx->mysql_thd, "before_row_upd_sec_new_index_entry"); - uncommitted = !index->is_committed(); - if (uncommitted) { - mtr.start(); - /* The index->online_status may change if the index is - being rollbacked. It is protected by index->lock. */ - - mtr_s_lock_index(index, &mtr); - - switch (dict_index_get_online_status(index)) { - case ONLINE_INDEX_COMPLETE: - case ONLINE_INDEX_CREATION: - break; - case ONLINE_INDEX_ABORTED: - case ONLINE_INDEX_ABORTED_DROPPED: - mtr_commit(&mtr); - goto func_exit; - } - - } - /* Build a new index entry */ entry = row_build_index_entry(node->upd_row, node->upd_ext, index, heap); ut_a(entry); - if (uncommitted) { - mtr_commit(&mtr); - } - /* Insert new index entry */ err = row_ins_sec_index_entry(index, entry, thr, !node->is_delete); @@ -2488,7 +2396,6 @@ row_upd_clust_rec( btr_pcur_t* pcur; btr_cur_t* btr_cur; dberr_t err; - const dtuple_t* rebuilt_old_pk = NULL; ut_ad(dict_index_is_clust(index)); ut_ad(!thr_get_trx(thr)->in_rollback); @@ -2502,11 +2409,6 @@ row_upd_clust_rec( dict_table_is_comp(index->table))); ut_ad(rec_offs_validate(btr_cur_get_rec(btr_cur), index, offsets)); - if (dict_index_is_online_ddl(index)) { - rebuilt_old_pk = row_log_table_get_pk( - btr_cur_get_rec(btr_cur), index, offsets, NULL, &heap); - } - /* Try optimistic updating of the record, keeping changes within the page; we do not check locks because we assume the x-lock on the record to update */ @@ -2524,7 +2426,7 @@ row_upd_clust_rec( } if (err == DB_SUCCESS) { - goto success; + goto func_exit; } if (buf_pool.running_out()) { @@ -2577,15 +2479,6 @@ row_upd_clust_rec( DEBUG_SYNC_C("after_row_upd_extern"); } - if (err == DB_SUCCESS) { -success: - if (dict_index_is_online_ddl(index)) { - row_log_table_update( - btr_cur_get_rec(btr_cur), - index, offsets, rebuilt_old_pk); - } - } - func_exit: if (heap) { mem_heap_free(heap); @@ -2932,7 +2825,8 @@ row_upd( break; } - if (node->index->type != DICT_FTS) { + if (!(node->index->type & DICT_FTS) + && node->index->is_committed()) { err = row_upd_sec_step(node, thr); if (err != DB_SUCCESS) { diff --git a/storage/innobase/trx/trx0rec.cc b/storage/innobase/trx/trx0rec.cc index a78212d3359..cd4cd278ffd 100644 --- a/storage/innobase/trx/trx0rec.cc +++ b/storage/innobase/trx/trx0rec.cc @@ -2037,6 +2037,10 @@ trx_undo_report_row_operation( auto m = trx->mod_tables.emplace(index->table, trx->undo_no); ut_ad(m.first->second.valid(trx->undo_no)); + if (m.second && index->table->is_active_ddl()) { + trx->apply_online_log= true; + } + bool bulk = !rec; if (!bulk) { @@ -2226,12 +2230,15 @@ err_exit: /** Copy an undo record to heap. @param[in] roll_ptr roll pointer to a record that exists -@param[in,out] heap memory heap where copied */ +@param[in,out] heap memory heap where copied +@param[in] undo_block undo log block which was cached during + DML online log apply */ static trx_undo_rec_t* trx_undo_get_undo_rec_low( - roll_ptr_t roll_ptr, - mem_heap_t* heap) + roll_ptr_t roll_ptr, + mem_heap_t* heap, + const buf_block_t* undo_block= nullptr) { trx_undo_rec_t* undo_rec; ulint rseg_id; @@ -2247,14 +2254,21 @@ trx_undo_get_undo_rec_low( trx_rseg_t* rseg = &trx_sys.rseg_array[rseg_id]; ut_ad(rseg->is_persistent()); - mtr.start(); + if (undo_block + && undo_block->page.id().page_no() == page_no) { + undo_rec = trx_undo_rec_copy( + undo_block->page.frame + offset, heap); + } else { + mtr.start(); - buf_block_t* undo_page = trx_undo_page_get_s_latched( - page_id_t(rseg->space->id, page_no), &mtr); + buf_block_t *undo_page = trx_undo_page_get_s_latched( + page_id_t(rseg->space->id, page_no), &mtr); - undo_rec = trx_undo_rec_copy(undo_page->page.frame + offset, heap); + undo_rec = trx_undo_rec_copy( + undo_page->page.frame + offset, heap); - mtr.commit(); + mtr.commit(); + } return(undo_rec); } @@ -2267,6 +2281,8 @@ trx_undo_get_undo_rec_low( undo log of this transaction @param[in] name table name @param[out] undo_rec own: copy of the record +@param[in] undo_block undo log block which was cached during + DML online log apply @retval true if the undo log has been truncated and we cannot fetch the old version @retval false if the undo log record is available @@ -2278,13 +2294,15 @@ trx_undo_get_undo_rec( mem_heap_t* heap, trx_id_t trx_id, const table_name_t& name, - trx_undo_rec_t** undo_rec) + trx_undo_rec_t** undo_rec, + const buf_block_t* undo_block) { purge_sys.latch.rd_lock(SRW_LOCK_CALL); bool missing_history = purge_sys.changes_visible(trx_id, name); if (!missing_history) { - *undo_rec = trx_undo_get_undo_rec_low(roll_ptr, heap); + *undo_rec = trx_undo_get_undo_rec_low( + roll_ptr, heap, undo_block); } purge_sys.latch.rd_unlock(); @@ -2298,41 +2316,48 @@ trx_undo_get_undo_rec( #define ATTRIB_USED_ONLY_IN_DEBUG MY_ATTRIBUTE((unused)) #endif /* UNIV_DEBUG */ -/*******************************************************************//** -Build a previous version of a clustered index record. The caller must -hold a latch on the index page of the clustered index record. +/** Build a previous version of a clustered index record. The caller +must hold a latch on the index page of the clustered index record. +@param index_rec clustered index record in the index tree +@param index_mtr mtr which contains the latch to index_rec page + and purge_view +@param rec version of a clustered index record +@param index clustered index +@param offsets rec_get_offsets(rec, index) +@param heap memory heap from which the memory needed is + allocated +@param old_vers previous version or NULL if rec is the + first inserted version, or if history data + has been deleted (an error), or if the purge + could have removed the version + though it has not yet done so +@param v_heap memory heap used to create vrow + dtuple if it is not yet created. This heap + diffs from "heap" above in that it could be + prebuilt->old_vers_heap for selection +@param v_row virtual column info, if any +@param v_status status determine if it is going into this + function by purge thread or not. + And if we read "after image" of undo log +@param undo_block undo log block which was cached during + online dml apply or nullptr @retval true if previous version was built, or if it was an insert or the table has been rebuilt @retval false if the previous version is earlier than purge_view, or being purged, which means that it may have been removed */ bool trx_undo_prev_version_build( -/*========================*/ - const rec_t* index_rec ATTRIB_USED_ONLY_IN_DEBUG, - /*!< in: clustered index record in the - index tree */ - mtr_t* index_mtr ATTRIB_USED_ONLY_IN_DEBUG, - /*!< in: mtr which contains the latch to - index_rec page and purge_view */ - const rec_t* rec, /*!< in: version of a clustered index record */ - dict_index_t* index, /*!< in: clustered index */ - rec_offs* offsets,/*!< in/out: rec_get_offsets(rec, index) */ - mem_heap_t* heap, /*!< in: memory heap from which the memory - needed is allocated */ - rec_t** old_vers,/*!< out, own: previous version, or NULL if - rec is the first inserted version, or if - history data has been deleted (an error), - or if the purge COULD have removed the version - though it has not yet done so */ - mem_heap_t* v_heap, /* !< in: memory heap used to create vrow - dtuple if it is not yet created. This heap - diffs from "heap" above in that it could be - prebuilt->old_vers_heap for selection */ - dtuple_t** vrow, /*!< out: virtual column info, if any */ - ulint v_status) - /*!< in: status determine if it is going - into this function by purge thread or not. - And if we read "after image" of undo log */ + const rec_t *index_rec ATTRIB_USED_ONLY_IN_DEBUG, + mtr_t *index_mtr ATTRIB_USED_ONLY_IN_DEBUG, + const rec_t *rec, + dict_index_t *index, + rec_offs *offsets, + mem_heap_t *heap, + rec_t **old_vers, + mem_heap_t *v_heap, + dtuple_t **vrow, + ulint v_status, + const buf_block_t*undo_block) { trx_undo_rec_t* undo_rec = NULL; dtuple_t* entry; @@ -2371,7 +2396,7 @@ trx_undo_prev_version_build( if (trx_undo_get_undo_rec( roll_ptr, heap, rec_trx_id, index->table->name, - &undo_rec)) { + &undo_rec, undo_block)) { if (v_status & TRX_UNDO_PREV_IN_PURGE) { /* We are fetching the record being purged */ undo_rec = trx_undo_get_undo_rec_low(roll_ptr, heap); diff --git a/storage/innobase/trx/trx0roll.cc b/storage/innobase/trx/trx0roll.cc index ddad699ead4..59ea0bdcd8f 100644 --- a/storage/innobase/trx/trx0roll.cc +++ b/storage/innobase/trx/trx0roll.cc @@ -60,7 +60,7 @@ const trx_t* trx_roll_crash_recv_trx; inline bool trx_t::rollback_finish() { mod_tables.clear(); - + apply_online_log= false; if (UNIV_LIKELY(error_state == DB_SUCCESS)) { commit(); @@ -137,13 +137,16 @@ inline void trx_t::rollback_low(trx_savept_t *savept) { ut_a(error_state == DB_SUCCESS); const undo_no_t limit= savept->least_undo_no; + apply_online_log= false; for (trx_mod_tables_t::iterator i= mod_tables.begin(); - i != mod_tables.end(); ) + i != mod_tables.end(); ) { trx_mod_tables_t::iterator j= i++; ut_ad(j->second.valid()); if (j->second.rollback(limit)) mod_tables.erase(j); + else if (!apply_online_log) + apply_online_log= j->first->is_active_ddl(); } MONITOR_INC(MONITOR_TRX_ROLLBACK_SAVEPOINT); } diff --git a/storage/innobase/trx/trx0trx.cc b/storage/innobase/trx/trx0trx.cc index 3c3c4db2b3e..64883fef38d 100644 --- a/storage/innobase/trx/trx0trx.cc +++ b/storage/innobase/trx/trx0trx.cc @@ -133,6 +133,8 @@ trx_init( trx->bulk_insert = false; + trx->apply_online_log = false; + ut_d(trx->start_file = 0); ut_d(trx->start_line = 0); @@ -452,6 +454,7 @@ void trx_t::free() MEM_NOACCESS(&mod_tables, sizeof mod_tables); MEM_NOACCESS(&detailed_error, sizeof detailed_error); MEM_NOACCESS(&magic_n, sizeof magic_n); + MEM_NOACCESS(&apply_online_log, sizeof apply_online_log); trx_pools->mem_free(this); } @@ -512,6 +515,7 @@ TRANSACTIONAL_TARGET void trx_free_at_shutdown(trx_t *trx) && !srv_undo_sources && srv_fast_shutdown)))); ut_a(trx->magic_n == TRX_MAGIC_N); + ut_d(trx->apply_online_log = false); trx->commit_state(); trx->release_locks(); trx->mod_tables.clear(); @@ -1396,7 +1400,7 @@ void trx_t::commit_cleanup() TRANSACTIONAL_TARGET void trx_t::commit_low(mtr_t *mtr) { ut_ad(!mtr || mtr->is_active()); - ut_d(bool aborted = in_rollback && error_state == DB_DEADLOCK); + ut_d(bool aborted= in_rollback && error_state == DB_DEADLOCK); ut_ad(!mtr == (aborted || !has_logged())); ut_ad(!mtr || !aborted); @@ -1415,12 +1419,13 @@ TRANSACTIONAL_TARGET void trx_t::commit_low(mtr_t *mtr) ut_ad(error == DB_DUPLICATE_KEY || error == DB_LOCK_WAIT_TIMEOUT); } -#ifndef DBUG_OFF +#ifdef ENABLED_DEBUG_SYNC const bool debug_sync= mysql_thd && has_logged_persistent(); #endif if (mtr) { + apply_log(); trx_write_serialisation_history(this, mtr); /* The following call commits the mini-transaction, making the @@ -1440,7 +1445,7 @@ TRANSACTIONAL_TARGET void trx_t::commit_low(mtr_t *mtr) mtr->commit(); } -#ifndef DBUG_OFF +#ifdef ENABLED_DEBUG_SYNC if (debug_sync) DEBUG_SYNC_C("before_trx_state_committed_in_memory"); #endif diff --git a/storage/innobase/trx/trx0undo.cc b/storage/innobase/trx/trx0undo.cc index 86f2d467219..1ad7caf6739 100644 --- a/storage/innobase/trx/trx0undo.cc +++ b/storage/innobase/trx/trx0undo.cc @@ -290,6 +290,107 @@ trx_undo_get_first_rec(const fil_space_t &space, uint32_t page_no, mtr); } +void UndorecApplier::assign_rec(trx_undo_rec_t *rec) +{ + this->undo_rec= rec; + this->offset= page_offset(rec); +} + +void UndorecApplier::apply_undo_rec() +{ + bool updated_extern= false; + undo_no_t undo_no= 0; + table_id_t table_id= 0; + undo_rec= trx_undo_rec_get_pars(undo_rec, &type, + &cmpl_info, + &updated_extern, &undo_no, &table_id); + dict_sys.freeze(SRW_LOCK_CALL); + dict_table_t *table= dict_sys.find_table(table_id); + dict_sys.unfreeze(); + + ut_ad(table); + if (!table->is_active_ddl()) + return; + + dict_index_t *index= dict_table_get_first_index(table); + const dtuple_t *undo_tuple; + switch (type) { + default: + ut_ad("invalid type" == 0); + MY_ASSERT_UNREACHABLE(); + case TRX_UNDO_INSERT_REC: + undo_rec= trx_undo_rec_get_row_ref(undo_rec, index, &undo_tuple, heap); + insert: + log_insert(*undo_tuple, index); + break; + case TRX_UNDO_UPD_EXIST_REC: + case TRX_UNDO_UPD_DEL_REC: + case TRX_UNDO_DEL_MARK_REC: + trx_id_t trx_id; + roll_ptr_t roll_ptr; + byte info_bits; + undo_rec= trx_undo_update_rec_get_sys_cols( + undo_rec, &trx_id, &roll_ptr, &info_bits); + + undo_rec= trx_undo_rec_get_row_ref(undo_rec, index, &undo_tuple, heap); + undo_rec= trx_undo_update_rec_get_update(undo_rec, index, type, trx_id, + roll_ptr, info_bits, + heap, &update); + if (type == TRX_UNDO_UPD_DEL_REC) + goto insert; + log_update(*undo_tuple, index); + } + + clear_undo_rec(); +} + +/** Apply any changes to tables for which online DDL is in progress. */ +ATTRIBUTE_COLD void trx_t::apply_log() +{ + if (undo_no == 0 || apply_online_log == false) + return; + const trx_undo_t *undo= rsegs.m_redo.undo; + if (!undo) + return; + page_id_t page_id{rsegs.m_redo.rseg->space->id, undo->hdr_page_no}; + page_id_t next_page_id(page_id); + mtr_t mtr; + mem_heap_t *heap= mem_heap_create(100); + mtr.start(); + buf_block_t *block= buf_page_get(page_id, 0, RW_S_LATCH, &mtr); + ut_ad(block); + + UndorecApplier log_applier(block, id); + + for (;;) + { + trx_undo_rec_t *rec= trx_undo_page_get_first_rec(block, page_id.page_no(), + undo->hdr_offset); + while (rec) + { + log_applier.assign_rec(rec); + log_applier.apply_undo_rec(); + rec= trx_undo_page_get_next_rec(block, page_offset(rec), + page_id.page_no(), undo->hdr_offset); + } + + uint32_t next= mach_read_from_4(TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_NODE + + FLST_NEXT + FIL_ADDR_PAGE + + block->page.frame); + if (next == FIL_NULL) + break; + next_page_id.set_page_no(next); + mtr.commit(); + mtr.start(); + block= buf_page_get(next_page_id, 0, RW_S_LATCH, &mtr); + log_applier.assign_block(block); + ut_ad(block); + } + mtr.commit(); + mem_heap_free(heap); + apply_online_log= false; +} + /*============== UNDO LOG FILE COPY CREATION AND FREEING ==================*/ /** Initialize an undo log page. |