From ce28fa53032dd7c233ed4875514bf85eafec0bc4 Mon Sep 17 00:00:00 2001 From: Teemu Ollakka Date: Thu, 24 Jan 2019 16:01:28 +0200 Subject: [PATCH 01/11] Backported wsrep-recover test from 10.4. Backported wsrep-recover test from 10.4 to test the wsrep recovery after database shutdown or crash. Renamed the test to wsrep-recover-v25 to avoid conflicts with existing test in 10.4 and to provide coverage for wsrep-API v25 compatible behavior. The test case in 10.2 is simpler because out of order prepare step cannot happen, the commit order critical section is grabbed before prepare phase. Test is not recorded since it does not produce expected result. --- .../suite/wsrep/t/wsrep-recover-step.inc | 41 ++++++ .../suite/wsrep/t/wsrep-recover-v25.cnf | 7 ++ .../wsrep/t/wsrep-recover-v25.combinations | 4 + .../suite/wsrep/t/wsrep-recover-v25.test | 119 ++++++++++++++++++ 4 files changed, 171 insertions(+) create mode 100644 mysql-test/suite/wsrep/t/wsrep-recover-step.inc create mode 100644 mysql-test/suite/wsrep/t/wsrep-recover-v25.cnf create mode 100644 mysql-test/suite/wsrep/t/wsrep-recover-v25.combinations create mode 100644 mysql-test/suite/wsrep/t/wsrep-recover-v25.test diff --git a/mysql-test/suite/wsrep/t/wsrep-recover-step.inc b/mysql-test/suite/wsrep/t/wsrep-recover-step.inc new file mode 100644 index 00000000000..22669438fe0 --- /dev/null +++ b/mysql-test/suite/wsrep/t/wsrep-recover-step.inc @@ -0,0 +1,41 @@ +# +# Macro to run wsrep recovery step. This is adapted from +# suite/galera/include/galera_wsrep_recover.inc, with additional +# option to pass binlog argument to recovery command. The macro +# returns recovered position split in uuid and seqno parts. +# +# Arguments: +# +# wsrep_recover_binlog_opt - Binlog options to recovery command +# +# Return: +# +# wsrep_recover_start_position_uuid - UUID corresponding to recovered position +# wsrep_recover_start_position_seqno - seqno corresponding to recovered position +# + +--exec $MYSQLD --defaults-group-suffix=.1 --defaults-file=$MYSQLTEST_VARDIR/my.cnf --log-error=$MYSQL_TMP_DIR/galera_wsrep_recover.log --innodb --wsrep-recover $wsrep_recover_binlog_opt --core-file > $MYSQL_TMP_DIR/galera_wsrep_recover.log 2>&1 + +--perl + use strict; + my $wsrep_start_position = `grep 'WSREP: Recovered position:' $ENV{MYSQL_TMP_DIR}/galera_wsrep_recover.log | sed 's/.*WSREP\:\ Recovered\ position://' | sed 's/^[ \t]*//'`; + chomp($wsrep_start_position); + die if $wsrep_start_position eq ''; + open(FILE, ">", "$ENV{MYSQL_TMP_DIR}/galera_wsrep_start_position.inc") or die; + my ($uuid, $seqno) = split /:/, $wsrep_start_position; + print FILE "--let \$wsrep_recover_start_position_uuid = $uuid\n"; + print FILE "--let \$wsrep_recover_start_position_seqno = $seqno\n"; + close FILE; +EOF + +--source $MYSQL_TMP_DIR/galera_wsrep_start_position.inc + +if ($wsrep_recover_start_position_uuid == '') { + --die "Could not obtain start_position_uuid." +} + +if ($wsrep_recover_start_position_seqno == '') { + --die "Could not obtain start_position_seqno." +} + +--remove_file $MYSQL_TMP_DIR/galera_wsrep_start_position.inc diff --git a/mysql-test/suite/wsrep/t/wsrep-recover-v25.cnf b/mysql-test/suite/wsrep/t/wsrep-recover-v25.cnf new file mode 100644 index 00000000000..b1c96d2614d --- /dev/null +++ b/mysql-test/suite/wsrep/t/wsrep-recover-v25.cnf @@ -0,0 +1,7 @@ +!include ../my.cnf + +[mysqld.1] +wsrep-on=ON +wsrep-cluster-address=gcomm:// +wsrep-provider=@ENV.WSREP_PROVIDER +binlog-format=ROW diff --git a/mysql-test/suite/wsrep/t/wsrep-recover-v25.combinations b/mysql-test/suite/wsrep/t/wsrep-recover-v25.combinations new file mode 100644 index 00000000000..1ce3b45aa1a --- /dev/null +++ b/mysql-test/suite/wsrep/t/wsrep-recover-v25.combinations @@ -0,0 +1,4 @@ +[binlogon] +log-bin + +[binlogoff] diff --git a/mysql-test/suite/wsrep/t/wsrep-recover-v25.test b/mysql-test/suite/wsrep/t/wsrep-recover-v25.test new file mode 100644 index 00000000000..cfd77fbdef4 --- /dev/null +++ b/mysql-test/suite/wsrep/t/wsrep-recover-v25.test @@ -0,0 +1,119 @@ +# +# Verify that the wsrep XID gets updated in InnoDB rollback segment +# properly and can be recovered with --wsrep-recover +# +# The test runs the following scenarios: +# +# 1) The server is started but no SQL is run +# 2) DDL is executed +# 3) INSERT is executed +# 4) Two INSERTs are executed so that the first one in order will be +# blocked after certification and the second one before entering +# commit order critical section. +# 5) Two DMLs are executed so that the prepare step is run out of order. +# Both transactions are blocked before commit order critical section. +# +# After each scenario server is killed and the recovered position +# is validated. +# + +--source include/have_wsrep.inc +--source include/have_innodb.inc +--source include/have_wsrep_provider.inc +--source include/have_debug_sync.inc + +# +# Binlog option for recovery run. This must be set in the test because +# combinations file causes log-bin option to be set from command line, +# not via my.cnf. +# +--let $log_bin = `SELECT @@log_bin` +if ($log_bin) { +--let $wsrep_recover_binlog_opt = --log-bin +} + +# +# Scenario 1 +# The expected recovered seqno is 1 corresponding to initial cluster +# configuration change. +# +--source include/kill_mysqld.inc +--source wsrep-recover-step.inc +--echo Expect seqno 0 +--echo $wsrep_recover_start_position_seqno + +--let $restart_parameters = --wsrep-start-position=$wsrep_recover_start_position_uuid:$wsrep_recover_start_position_seqno +--source include/start_mysqld.inc +--source include/wait_wsrep_ready.inc + +# +# Senario 2 +# The expected recovered seqno is 1 corresponding to CREATE TABLE +# + +CREATE TABLE t1 (f1 INT PRIMARY KEY) ENGINE=InnoDB; +--source include/kill_mysqld.inc +--source wsrep-recover-step.inc +--echo Expect seqno 1 +--echo $wsrep_recover_start_position_seqno + +--let $restart_parameters = --wsrep-start-position=$wsrep_recover_start_position_uuid:$wsrep_recover_start_position_seqno +--source include/start_mysqld.inc +--source include/wait_wsrep_ready.inc + +# +# Scenario 3 +# The expected recovered seqno is 2 corresponding to CREATE TABLE and INSERT. +# +# The expected wsrep_last_committed after the server is restarted is 2. +# + +INSERT INTO t1 VALUES (5); +--source include/kill_mysqld.inc +--source wsrep-recover-step.inc +--echo Expect seqno 2 +--echo $wsrep_recover_start_position_seqno +--let $restart_parameters = --wsrep-start-position=$wsrep_recover_start_position_uuid:$wsrep_recover_start_position_seqno +--source include/start_mysqld.inc +--source include/wait_wsrep_ready.inc + +SELECT VARIABLE_VALUE `expect 2` FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_last_committed'; + +# +# Scenario 4 +# +# The INSERT gets prepared but not committed. +# +# If binlog is off, the expected outcome is that the INSERT gets committed +# since it is already committed in the cluster. If binlog is on, the INSERT +# should be rolled back during recovery phase since it has not yet +# been logged into binlog. +# +--connect con1, localhost, root +SET DEBUG_SYNC = "ha_commit_trans_after_prepare SIGNAL after_prepare_reached WAIT_FOR continue"; +--send INSERT INTO t1 VALUES (7) + +--connection default +SET DEBUG_SYNC = "now WAIT_FOR after_prepare_reached"; +--source include/kill_mysqld.inc +--source wsrep-recover-step.inc +if ($log_bin) { + --echo Expect seqno 2 +} +if (!$log_bin) { + --echo Expect seqno 3 +} +--echo $wsrep_recover_start_position_seqno +--let $restart_parameters = --wsrep-start-position=$wsrep_recover_start_position_uuid:$wsrep_recover_start_position_seqno +--source include/start_mysqld.inc +--source include/wait_wsrep_ready.inc + +if ($log_bin) { + --echo Expect 5 +} +if (!$log_bin) { + --echo Expect 5 7 +} +SELECT * FROM t1; + +DROP TABLE t1; From 040b840de7bcd32b0236531a7aec95b11341cef3 Mon Sep 17 00:00:00 2001 From: Teemu Ollakka Date: Thu, 24 Jan 2019 16:31:33 +0200 Subject: [PATCH 02/11] MDEV-15740 Backport wsrep recovery fixes from 10.4. Clear wsrep XID in innobase_rollback_by_xid() for recovered wsrep transaction in order to avoid resetting XID storage when rolling back wsrep transaction during recovery. Sort wsrep XIDs read from storage engine in ascending order and erify that the range is continuous during crash recovery. If binlog is off, commit all recovered transactions for continuous seqno range. This is safe because all transactions with wsrep XID have been certified and must be committed in the cluster. On the other hand if binlog is on, respect binlog as a transaction coordinator in order to avoid missing transactions in binlog that have been committed into storage engine . --- sql/handler.cc | 51 +++++++++++++++++++++++++-- sql/wsrep_xid.cc | 34 ++++++++++++++++++ sql/wsrep_xid.h | 2 ++ storage/innobase/handler/ha_innodb.cc | 8 +++++ 4 files changed, 92 insertions(+), 3 deletions(-) diff --git a/sql/handler.cc b/sql/handler.cc index da41daf2440..e2a8d16b723 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -1827,6 +1827,35 @@ static char* xid_to_str(char *buf, XID *xid) } #endif +#ifdef WITH_WSREP +static my_xid wsrep_order_and_check_continuity(XID *list, int len) +{ + wsrep_sort_xid_array(list, len); + wsrep_uuid_t uuid; + wsrep_seqno_t seqno; + if (wsrep_get_SE_checkpoint(uuid, seqno)) + { + WSREP_ERROR("Could not read wsrep SE checkpoint for recovery"); + return 0; + } + long long cur_seqno= seqno; + for (int i= 0; i < len; ++i) + { + if (!wsrep_is_wsrep_xid(list + i) || + wsrep_xid_seqno(*(list + i)) != cur_seqno + 1) + { + WSREP_WARN("Discovered discontinuity in recovered wsrep " + "transaction XIDs. Truncating the recovery list to " + "%d entries", i); + break; + } + ++cur_seqno; + } + WSREP_INFO("Last wsrep seqno to be recovered %lld", cur_seqno); + return (cur_seqno < 0 ? 0 : cur_seqno); +} +#endif /* WITH_WSREP */ + /** recover() step of xa. @@ -1864,6 +1893,19 @@ static my_bool xarecover_handlerton(THD *unused, plugin_ref plugin, { sql_print_information("Found %d prepared transaction(s) in %s", got, hton_name(hton)->str); +#ifdef WITH_WSREP + /* If wsrep_on=ON, XIDs are first ordered and then the range of + recovered XIDs is checked for continuity. All the XIDs which + are in continuous range can be safely committed if binlog + is off since they have already ordered and certified in the + cluster. */ + my_xid wsrep_limit= 0; + if (WSREP_ON) + { + wsrep_limit= wsrep_order_and_check_continuity(info->list, got); + } +#endif /* WITH_WSREP */ + for (int i=0; i < got; i ++) { my_xid x= WSREP_ON && wsrep_is_wsrep_xid(&info->list[i]) ? @@ -1885,9 +1927,12 @@ static my_bool xarecover_handlerton(THD *unused, plugin_ref plugin, continue; } // recovery mode - if (info->commit_list ? - my_hash_search(info->commit_list, (uchar *)&x, sizeof(x)) != 0 : - tc_heuristic_recover == TC_HEURISTIC_RECOVER_COMMIT) + if (IF_WSREP((wsrep_emulate_bin_log && + wsrep_is_wsrep_xid(info->list + i) && + x <= wsrep_limit), false) || + (info->commit_list ? + my_hash_search(info->commit_list, (uchar *)&x, sizeof(x)) != 0 : + tc_heuristic_recover == TC_HEURISTIC_RECOVER_COMMIT)) { #ifndef DBUG_OFF int rc= diff --git a/sql/wsrep_xid.cc b/sql/wsrep_xid.cc index c0b7669d8ca..72b96cadfb0 100644 --- a/sql/wsrep_xid.cc +++ b/sql/wsrep_xid.cc @@ -20,6 +20,8 @@ #include "sql_class.h" #include "wsrep_mysqld.h" // for logging macros +#include /* std::sort() */ + /* * WSREPXid */ @@ -154,3 +156,35 @@ bool wsrep_get_SE_checkpoint(wsrep_uuid_t& uuid, wsrep_seqno_t& seqno) return false; } + +/* + Sort order for XIDs. Wsrep XIDs are sorted according to + seqno in ascending order. Non-wsrep XIDs are considered + equal among themselves and greater than with respect + to wsrep XIDs. + */ +struct Wsrep_xid_cmp +{ + bool operator()(const XID& left, const XID& right) const + { + const bool left_is_wsrep= wsrep_is_wsrep_xid(&left); + const bool right_is_wsrep= wsrep_is_wsrep_xid(&right); + if (left_is_wsrep && right_is_wsrep) + { + return (wsrep_xid_seqno(left) < wsrep_xid_seqno(right)); + } + else if (left_is_wsrep) + { + return true; + } + else + { + return false; + } + } +}; + +void wsrep_sort_xid_array(XID *array, int len) +{ + std::sort(array, array + len, Wsrep_xid_cmp()); +} diff --git a/sql/wsrep_xid.h b/sql/wsrep_xid.h index 5b33a904de1..01b18506708 100644 --- a/sql/wsrep_xid.h +++ b/sql/wsrep_xid.h @@ -32,5 +32,7 @@ bool wsrep_get_SE_checkpoint(wsrep_uuid_t&, wsrep_seqno_t&); //void wsrep_set_SE_checkpoint(XID&); /* uncomment if needed */ bool wsrep_set_SE_checkpoint(const wsrep_uuid_t&, wsrep_seqno_t); +void wsrep_sort_xid_array(XID *array, int len); + #endif /* WITH_WSREP */ #endif /* WSREP_UTILS_H */ diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index 3e68e2d1d4a..dde12e5c2d5 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -17260,6 +17260,14 @@ innobase_rollback_by_xid( } if (trx_t* trx = trx_get_trx_by_xid(xid)) { +#ifdef WITH_WSREP + /* If a wsrep transaction is being rolled back during + the recovery, we must clear the xid in order to avoid + writing serialisation history for rolled back transaction. */ + if (wsrep_is_wsrep_xid(trx->xid)) { + trx->xid->null(); + } +#endif /* WITH_WSREP */ int ret = innobase_rollback_trx(trx); trx_deregister_from_2pc(trx); ut_ad(!trx->will_lock); From 4ea128391b964fbad5b58629bdee980bbedbd228 Mon Sep 17 00:00:00 2001 From: Teemu Ollakka Date: Thu, 24 Jan 2019 17:37:45 +0200 Subject: [PATCH 03/11] MDEV-15740 Fix wsrep recovery with wsrep_emulate_bin_log If the TC log did not provide list of XIDs to recover, the commit by XID was skipped during wsrep recovery if binlog emulation was on. However, with wsrep we want to commit every prepared transaction with assigned wsrep XID since the transaction has already been committed in the cluster. Added a special condition to always proceed to commit by XID in xarecover_handlerton() if binlog is off and the recovered transaction has wsrep XID. --- sql/handler.cc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sql/handler.cc b/sql/handler.cc index e2a8d16b723..a9688fb96b4 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -1921,7 +1921,10 @@ static my_bool xarecover_handlerton(THD *unused, plugin_ref plugin, info->found_foreign_xids++; continue; } - if (info->dry_run) + if (IF_WSREP(!(wsrep_emulate_bin_log && + wsrep_is_wsrep_xid(info->list + i) && + x <= wsrep_limit) && info->dry_run, + info->dry_run)) { info->found_my_xids++; continue; From ddfc789098e7f038e1f8f62f79663d9751b957c2 Mon Sep 17 00:00:00 2001 From: Teemu Ollakka Date: Sun, 27 Jan 2019 15:44:35 +0200 Subject: [PATCH 04/11] MDEV-15740 Recorded wsrep-recover-v25 --- .../wsrep/r/wsrep-recover-v25,binlogon.rdiff | 17 +++++++++++ .../suite/wsrep/r/wsrep-recover-v25.result | 28 +++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 mysql-test/suite/wsrep/r/wsrep-recover-v25,binlogon.rdiff create mode 100644 mysql-test/suite/wsrep/r/wsrep-recover-v25.result diff --git a/mysql-test/suite/wsrep/r/wsrep-recover-v25,binlogon.rdiff b/mysql-test/suite/wsrep/r/wsrep-recover-v25,binlogon.rdiff new file mode 100644 index 00000000000..5bf6502d0d8 --- /dev/null +++ b/mysql-test/suite/wsrep/r/wsrep-recover-v25,binlogon.rdiff @@ -0,0 +1,17 @@ +--- r/wsrep-recover-v25.result 2019-01-27 15:38:58.819204748 +0200 ++++ r/wsrep-recover-v25.reject 2019-01-27 15:39:49.967358994 +0200 +@@ -18,11 +18,10 @@ + connection default; + SET DEBUG_SYNC = "now WAIT_FOR after_prepare_reached"; + # Kill the server +-Expect seqno 3 +-3 +-Expect 5 7 ++Expect seqno 2 ++2 ++Expect 5 + SELECT * FROM t1; + f1 + 5 +-7 + DROP TABLE t1; diff --git a/mysql-test/suite/wsrep/r/wsrep-recover-v25.result b/mysql-test/suite/wsrep/r/wsrep-recover-v25.result new file mode 100644 index 00000000000..6d146f67bdf --- /dev/null +++ b/mysql-test/suite/wsrep/r/wsrep-recover-v25.result @@ -0,0 +1,28 @@ +# Kill the server +Expect seqno 0 +0 +CREATE TABLE t1 (f1 INT PRIMARY KEY) ENGINE=InnoDB; +# Kill the server +Expect seqno 1 +1 +INSERT INTO t1 VALUES (5); +# Kill the server +Expect seqno 2 +2 +SELECT VARIABLE_VALUE `expect 2` FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_last_committed'; +expect 2 +2 +connect con1, localhost, root; +SET DEBUG_SYNC = "ha_commit_trans_after_prepare SIGNAL after_prepare_reached WAIT_FOR continue"; +INSERT INTO t1 VALUES (7); +connection default; +SET DEBUG_SYNC = "now WAIT_FOR after_prepare_reached"; +# Kill the server +Expect seqno 3 +3 +Expect 5 7 +SELECT * FROM t1; +f1 +5 +7 +DROP TABLE t1; From 4ef556955f5083ff046a195b3d0a68545224483e Mon Sep 17 00:00:00 2001 From: Teemu Ollakka Date: Thu, 24 Jan 2019 17:42:30 +0200 Subject: [PATCH 05/11] MDEV-15740 Enabled and recorded galera_gcache_recover_manytrx --- mysql-test/suite/galera/disabled.def | 1 - .../r/galera_gcache_recover_manytrx.result | 29 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/mysql-test/suite/galera/disabled.def b/mysql-test/suite/galera/disabled.def index 9a8adba7a03..4dcfdec84ce 100644 --- a/mysql-test/suite/galera/disabled.def +++ b/mysql-test/suite/galera/disabled.def @@ -44,4 +44,3 @@ galera.MW-328C : MDEV-17847 Galera test failure on MW-328[A|B|C] galera.galera_sst_xtrabackup-v2 : MDEV-17848 Galera test failure on galera_sst_xtrabackup-v2[_data_dir] galera.galera_sst_xtrabackup-v2_data_dir : MDEV-17848 Galera test failure on galera_sst_xtrabackup-v2[_data_dir] query_cache : MDEV-18137: Galera test failure on query_cache -galera_gcache_recover_manytrx : MDEV-15740 diff --git a/mysql-test/suite/galera/r/galera_gcache_recover_manytrx.result b/mysql-test/suite/galera/r/galera_gcache_recover_manytrx.result index 868b39bfbd6..604b24a8fab 100644 --- a/mysql-test/suite/galera/r/galera_gcache_recover_manytrx.result +++ b/mysql-test/suite/galera/r/galera_gcache_recover_manytrx.result @@ -70,28 +70,55 @@ WHILE 1 DO INSERT INTO t1 (f2) VALUES (REPEAT('x', 1024 * 1024 * 10)); END WHILE; END| +connect node_1_insert_simple, 127.0.0.1, root, , test, $NODE_MYPORT_1; +connect node_1_insert_multi, 127.0.0.1, root, , test, $NODE_MYPORT_1; +connect node_1_insert_transaction, 127.0.0.1, root, , test, $NODE_MYPORT_1; +connect node_1_update_simple, 127.0.0.1, root, , test, $NODE_MYPORT_1; +connect node_1_insert_1k, 127.0.0.1, root, , test, $NODE_MYPORT_1; +connect node_1_insert_1m, 127.0.0.1, root, , test, $NODE_MYPORT_1; +connect node_1_insert_10m, 127.0.0.1, root, , test, $NODE_MYPORT_1; +connection node_1_insert_simple; CALL insert_simple();; +connection node_1_insert_multi; CALL insert_multi();; +connection node_1_insert_transaction; CALL insert_transaction ();; +connection node_1_update_simple; CALL update_simple ();; +connection node_1_insert_1k; CALL insert_1k ();; +connection node_1_insert_1m; CALL insert_1m ();; +connection node_1_insert_10m; CALL insert_10m ();; +connection node_2; SET SESSION wsrep_sync_wait = 0; Killing server ... +connection node_1; Killing server ... +connection node_1_insert_simple; ERROR HY000: Lost connection to MySQL server during query +connection node_1_insert_multi; ERROR HY000: Lost connection to MySQL server during query +connection node_1_insert_transaction; ERROR HY000: Lost connection to MySQL server during query +connection node_1_update_simple; ERROR HY000: Lost connection to MySQL server during query +connection node_1_insert_1k; ERROR HY000: Lost connection to MySQL server during query +connection node_1_insert_1m; ERROR HY000: Lost connection to MySQL server during query +connection node_1_insert_10m; ERROR HY000: Lost connection to MySQL server during query +connection node_1; Performing --wsrep-recover ... Using --wsrep-start-position when starting mysqld ... +connection node_2; Performing --wsrep-recover ... Using --wsrep-start-position when starting mysqld ... +connection node_1; include/diff_servers.inc [servers=1 2] +connection node_1; DROP TABLE t1; DROP TABLE ten; DROP PROCEDURE insert_simple; @@ -100,8 +127,10 @@ DROP PROCEDURE insert_transaction; DROP PROCEDURE update_simple; DROP PROCEDURE insert_1k; DROP PROCEDURE insert_1m; +connection node_1; CALL mtr.add_suppression("conflict state 7 after post commit"); CALL mtr.add_suppression("Skipped GCache ring buffer recovery"); include/assert_grep.inc [async IST sender starting to serve] +connection node_2; CALL mtr.add_suppression("Skipped GCache ring buffer recovery"); include/assert_grep.inc [Recovering GCache ring buffer: found gapless sequence] From b8aef87221c46c82921377f18a6498a31f7e5367 Mon Sep 17 00:00:00 2001 From: Thirunarayanan Balathandayuthapani Date: Wed, 30 Jan 2019 15:32:51 +0530 Subject: [PATCH 06/11] MDEV-16849 Extending indexed VARCHAR column should be instantaneous Analysis: ======== Increasing the length of the indexed varchar column is not an instant operation for innodb. Fix: === - Introduce the new handler flag 'Alter_inplace_info::ALTER_COLUMN_INDEX_LENGTH' to indicate the index length differs due to change of column length changes. - InnoDB makes the ALTER_COLUMN_INDEX_LENGTH flag as instant operation. This is a port of Mysql fix. commit 913071c0b16cc03e703308250d795bc381627e37 Author: Nisha Gopalakrishnan Date: Wed May 30 14:54:46 2018 +0530 BUG#26848813: INDEXED COLUMN CAN'T BE CHANGED FROM VARCHAR(15) TO VARCHAR(40) INSTANTANEOUSLY --- .../innodb/r/alter_varchar_change.result | 461 ++++++++++++++++++ .../suite/innodb/t/alter_varchar_change.test | 336 +++++++++++++ sql/handler.h | 5 + sql/sql_table.cc | 41 +- storage/innobase/handler/handler0alter.cc | 3 +- 5 files changed, 834 insertions(+), 12 deletions(-) create mode 100644 mysql-test/suite/innodb/r/alter_varchar_change.result create mode 100644 mysql-test/suite/innodb/t/alter_varchar_change.test diff --git a/mysql-test/suite/innodb/r/alter_varchar_change.result b/mysql-test/suite/innodb/r/alter_varchar_change.result new file mode 100644 index 00000000000..0b06fddd35e --- /dev/null +++ b/mysql-test/suite/innodb/r/alter_varchar_change.result @@ -0,0 +1,461 @@ +CREATE PROCEDURE get_index_id(IN tbl_id INT, IN idx_name char(100), OUT idx_id INT) +BEGIN +SELECT index_id into idx_id FROM INFORMATION_SCHEMA.INNODB_SYS_INDEXES WHERE +NAME=idx_name and TABLE_ID=tbl_id; +END| +CREATE PROCEDURE get_table_id(IN tbl_name char(100), OUT tbl_id INT) +BEGIN +SELECT table_id into tbl_id FROM INFORMATION_SCHEMA.INNODB_SYS_TABLES WHERE +NAME = tbl_name; +END| +SET @tbl_id = 0; +SET @tbl1_id = 0; +SET @idx_id = 0; +SET @idx1_id = 0; +CREATE TABLE t1(f1 INT NOT NULL, +f2 VARCHAR(100) PRIMARY KEY)ENGINE=InnoDB; +CALL get_table_id("test/t1", @tbl_id); +ALTER TABLE t1 MODIFY f2 VARCHAR(200); +CALL get_table_id("test/t1", @tbl1_id); +SELECT @tbl1_id = @tbl_id; +@tbl1_id = @tbl_id +1 +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `f1` int(11) NOT NULL, + `f2` varchar(200) NOT NULL, + PRIMARY KEY (`f2`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +DROP TABLE t1; +CREATE TABLE t1(f1 INT NOT NULL, +f2 VARCHAR(100), +INDEX idx(f2))ENGINE=InnoDB; +CALL get_table_id("test/t1", @tbl_id); +ALTER TABLE t1 MODIFY f2 VARCHAR(200); +CALL get_table_id("test/t1", @tbl1_id); +SELECT @tbl1_id = @tbl_id; +@tbl1_id = @tbl_id +1 +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `f1` int(11) NOT NULL, + `f2` varchar(200) DEFAULT NULL, + KEY `idx` (`f2`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +DROP TABLE t1; +CREATE TABLE t1(f1 INT NOT NULL, f2 VARCHAR(100), f3 VARCHAR(100), +INDEX idx(f2, f3), index idx1(f3, f2))ENGINE=InnoDB; +CALL get_table_id("test/t1", @tbl_id); +CALL get_index_id(@tbl_id, "idx", @idx_id); +ALTER TABLE t1 MODIFY f2 VARCHAR(200), MODIFY f3 VARCHAR(150); +CALL get_table_id("test/t1", @tbl1_id); +CALL get_index_id(@tbl1_id, "idx", @idx1_id); +SELECT @tbl1_id = @tbl_id; +@tbl1_id = @tbl_id +1 +SELECT @idx1_id = @idx_id; +@idx1_id = @idx_id +1 +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `f1` int(11) NOT NULL, + `f2` varchar(200) DEFAULT NULL, + `f3` varchar(150) DEFAULT NULL, + KEY `idx` (`f2`,`f3`), + KEY `idx1` (`f3`,`f2`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +DROP TABLE t1; +CREATE TABLE t1(f1 INT NOT NULL, f2 VARCHAR(100), +INDEX idx(f2(40)))ENGINE=InnoDB; +CALL get_table_id("test/t1", @tbl_id); +CALL get_index_id(@tbl_id, "idx", @idx_id); +ALTER TABLE t1 MODIFY f2 VARCHAR(200); +CALL get_table_id("test/t1", @tbl1_id); +CALL get_index_id(@tbl1_id, "idx", @idx1_id); +SELECT @tbl1_id = @tbl_id; +@tbl1_id = @tbl_id +1 +SELECT @idx1_id = @idx_id; +@idx1_id = @idx_id +1 +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `f1` int(11) NOT NULL, + `f2` varchar(200) DEFAULT NULL, + KEY `idx` (`f2`(40)) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +DROP TABLE t1; +CREATE TABLE t1(f1 INT NOT NULL, +f2 VARCHAR(100), FULLTEXT idx(f2))ENGINE=InnoDB; +CALL get_table_id("test/t1", @tbl_id); +CALL get_index_id(@tbl_id, "idx", @idx_id); +ALTER TABLE t1 MODIFY f2 VARCHAR(200); +CALL get_table_id("test/t1", @tbl1_id); +CALL get_index_id(@tbl1_id, "idx", @idx1_id); +SELECT @tbl1_id = @tbl_id; +@tbl1_id = @tbl_id +1 +SELECT @idx1_id = @idx_id; +@idx1_id = @idx_id +1 +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `f1` int(11) NOT NULL, + `f2` varchar(200) DEFAULT NULL, + FULLTEXT KEY `idx` (`f2`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +DROP TABLE t1; +CREATE TABLE t1(f1 INT NOT NULL, +f2 VARCHAR(100), +f3 VARCHAR(50) as (f2) VIRTUAL, +INDEX idx(f3))ENGINE=InnoDB; +INSERT INTO t1(f1, f2) VALUES(1, repeat('a', 40)); +CALL get_table_id("test/t1", @tbl_id); +CALL get_index_id(@tbl_id, "idx", @idx_id); +ALTER TABLE t1 MODIFY f2 VARCHAR(100); +CALL get_table_id("test/t1", @tbl1_id); +CALL get_index_id(@tbl1_id, "idx", @idx1_id); +SELECT @tbl1_id = @tbl_id; +@tbl1_id = @tbl_id +1 +SELECT @idx1_id = @idx_id; +@idx1_id = @idx_id +1 +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `f1` int(11) NOT NULL, + `f2` varchar(100) DEFAULT NULL, + `f3` varchar(50) GENERATED ALWAYS AS (`f2`) VIRTUAL, + KEY `idx` (`f3`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +DROP TABLE t1; +CREATE TABLE t1(f1 INT NOT NULL, +f2 VARCHAR(100), +INDEX idx(f2(10)), +INDEX idx1(f1))ENGINE=InnoDB; +CALL get_table_id("test/t1", @tbl_id); +CALL get_index_id(@tbl_id, "idx", @idx_id); +ALTER TABLE t1 MODIFY f2 VARCHAR(200), DROP INDEX idx1; +CALL get_table_id("test/t1", @tbl1_id); +CALL get_index_id(@tbl1_id, "idx", @idx1_id); +SELECT @tbl1_id = @tbl_id; +@tbl1_id = @tbl_id +1 +SELECT @idx1_id = @idx_id; +@idx1_id = @idx_id +1 +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `f1` int(11) NOT NULL, + `f2` varchar(200) DEFAULT NULL, + KEY `idx` (`f2`(10)) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +DROP TABLE t1; +CREATE TABLE t1(f1 INT NOT NULL, +f2 VARCHAR(100), +INDEX idx(f2(10)))ENGINE=InnoDB; +CALL get_table_id("test/t1", @tbl_id); +CALL get_index_id(@tbl_id, "idx", @idx_id); +ALTER TABLE t1 MODIFY f2 VARCHAR(200), DROP INDEX idx, ADD INDEX idx(f2(10)); +CALL get_table_id("test/t1", @tbl1_id); +CALL get_index_id(@tbl1_id, "idx", @idx1_id); +SELECT @tbl1_id = @tbl_id; +@tbl1_id = @tbl_id +1 +SELECT @idx1_id = @idx_id; +@idx1_id = @idx_id +1 +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `f1` int(11) NOT NULL, + `f2` varchar(200) DEFAULT NULL, + KEY `idx` (`f2`(10)) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +DROP TABLE t1; +CREATE TABLE t1(f1 INT NOT NULL, +f2 VARCHAR(100), +INDEX idx(f2(10)))ENGINE=InnoDB; +CALL get_table_id("test/t1", @tbl_id); +CALL get_index_id(@tbl_id, "idx", @idx_id); +ALTER TABLE t1 MODIFY f2 VARCHAR(200), DROP INDEX idx, ADD INDEX idx(f2(50)); +CALL get_table_id("test/t1", @tbl1_id); +CALL get_index_id(@tbl1_id, "idx", @idx1_id); +SELECT @tbl1_id = @tbl_id; +@tbl1_id = @tbl_id +1 +SELECT @idx1_id = @idx_id; +@idx1_id = @idx_id +0 +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `f1` int(11) NOT NULL, + `f2` varchar(200) DEFAULT NULL, + KEY `idx` (`f2`(50)) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +DROP TABLE t1; +CREATE TABLE t1(f1 INT NOT NULL, +f2 VARCHAR(100), +INDEX idx(f2(100)))ENGINE=InnoDB; +CALL get_table_id("test/t1", @tbl_id); +CALL get_index_id(@tbl_id, "idx", @idx_id); +ALTER TABLE t1 MODIFY f2 VARCHAR(200), ADD INDEX idx1(f1); +CALL get_table_id("test/t1", @tbl1_id); +CALL get_index_id(@tbl1_id, "idx", @idx1_id); +SELECT @tbl1_id = @tbl_id; +@tbl1_id = @tbl_id +1 +SELECT @idx1_id = @idx_id; +@idx1_id = @idx_id +1 +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `f1` int(11) NOT NULL, + `f2` varchar(200) DEFAULT NULL, + KEY `idx` (`f2`), + KEY `idx1` (`f1`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +DROP TABLE t1; +CREATE TABLE t1(f1 INT NOT NULL, +f2 VARCHAR(100), +INDEX idx(f2(10)))ENGINE=InnoDB; +CALL get_table_id("test/t1", @tbl_id); +CALL get_index_id(@tbl_id, "idx", @idx_id); +ALTER TABLE t1 MODIFY f2 VARCHAR(200), DROP INDEX idx, ADD INDEX idx(f2(6)); +CALL get_table_id("test/t1", @tbl1_id); +CALL get_index_id(@tbl1_id, "idx", @idx1_id); +SELECT @tbl1_id = @tbl_id; +@tbl1_id = @tbl_id +1 +SELECT @idx1_id = @idx_id; +@idx1_id = @idx_id +0 +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `f1` int(11) NOT NULL, + `f2` varchar(200) DEFAULT NULL, + KEY `idx` (`f2`(6)) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +DROP TABLE t1; +CREATE TABLE t1(f1 INT NOT NULL, +f2 VARCHAR(100), +INDEX idx(f2))ENGINE=InnoDB; +CALL get_table_id("test/t1", @tbl_id); +ALTER TABLE t1 MODIFY f2 VARCHAR(200), ADD COLUMN f3 INT; +CALL get_table_id("test/t1", @tbl1_id); +SELECT @tbl1_id = @tbl_id; +@tbl1_id = @tbl_id +0 +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `f1` int(11) NOT NULL, + `f2` varchar(200) DEFAULT NULL, + `f3` int(11) DEFAULT NULL, + KEY `idx` (`f2`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +DROP TABLE t1; +CREATE TABLE t1(f1 INT NOT NULL, f2 VARCHAR(100) PRIMARY KEY)ENGINE=InnoDB; +CALL get_table_id("test/t1", @tbl_id); +ALTER TABLE t1 MODIFY f2 VARCHAR(200), ADD COLUMN f3 INT; +CALL get_table_id("test/t1", @tbl1_id); +SELECT @tbl1_id = @tbl_id; +@tbl1_id = @tbl_id +0 +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `f1` int(11) NOT NULL, + `f2` varchar(200) NOT NULL, + `f3` int(11) DEFAULT NULL, + PRIMARY KEY (`f2`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +DROP TABLE t1; +CREATE TABLE t1(f1 INT NOT NULL, f2 VARCHAR(100))ENGINE=INNODB; +CALL get_table_id("test/t1", @tbl_id); +ALTER TABLE t1 MODIFY f2 VARCHAR(200), ADD FULLTEXT idx(f2); +Warnings: +Warning 124 InnoDB rebuilding table to add column FTS_DOC_ID +CALL get_table_id("test/t1", @tbl1_id); +SELECT @tbl1_id = @tbl_id; +@tbl1_id = @tbl_id +0 +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `f1` int(11) NOT NULL, + `f2` varchar(200) DEFAULT NULL, + FULLTEXT KEY `idx` (`f2`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +DROP TABLE t1; +CREATE TABLE t1(f1 INT NOT NULL, +f2 CHAR(100) PRIMARY KEY)ENGINE=InnoDB; +CALL get_table_id("test/t1", @tbl_id); +ALTER TABLE t1 MODIFY f2 CHAR(200); +CALL get_table_id("test/t1", @tbl1_id); +SELECT @tbl1_id = @tbl_id; +@tbl1_id = @tbl_id +0 +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `f1` int(11) NOT NULL, + `f2` char(200) NOT NULL, + PRIMARY KEY (`f2`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +DROP TABLE t1; +CREATE TABLE t1(f1 INT NOT NULL, +f2 VARCHAR(100), +INDEX idx(f2(10)), +INDEX idx1(f1))ENGINE=InnoDB; +CALL get_table_id("test/t1", @tbl_id); +ALTER TABLE t1 MODIFY f2 VARCHAR(50); +CALL get_table_id("test/t1", @tbl1_id); +SELECT @tbl1_id = @tbl_id; +@tbl1_id = @tbl_id +0 +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `f1` int(11) NOT NULL, + `f2` varchar(50) DEFAULT NULL, + KEY `idx` (`f2`(10)), + KEY `idx1` (`f1`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +DROP TABLE t1; +CREATE TABLE t1(f1 INT NOT NULL, +f2 VARCHAR(100), +INDEX idx(f2(10)), +INDEX idx1(f1))ENGINE=InnoDB; +CALL get_table_id("test/t1", @tbl_id); +ALTER TABLE t1 MODIFY f2 VARCHAR(5), DROP INDEX idx1; +CALL get_table_id("test/t1", @tbl1_id); +SELECT @tbl1_id = @tbl_id; +@tbl1_id = @tbl_id +0 +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `f1` int(11) NOT NULL, + `f2` varchar(5) DEFAULT NULL, + KEY `idx` (`f2`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +DROP TABLE t1; +CREATE TABLE t1(f1 INT NOT NULL, +f2 VARCHAR(100), FULLTEXT idx(f2))ENGINE=InnoDB; +CALL get_table_id("test/t1", @tbl_id); +CALL get_index_id(@tbl_id, "idx", @idx_id); +ALTER TABLE t1 MODIFY f2 VARCHAR(50); +CALL get_table_id("test/t1", @tbl1_id); +CALL get_index_id(@tbl1_id, "idx", @idx1_id); +SELECT @tbl1_id = @tbl_id; +@tbl1_id = @tbl_id +0 +SELECT @idx1_id = @idx_id; +@idx1_id = @idx_id +0 +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `f1` int(11) NOT NULL, + `f2` varchar(50) DEFAULT NULL, + FULLTEXT KEY `idx` (`f2`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +DROP TABLE t1; +CREATE TABLE t1(f1 INT NOT NULL, +f2 VARCHAR(100), +INDEX idx(f2))ENGINE=InnoDB; +CALL get_table_id("test/t1", @tbl_id); +ALTER TABLE t1 MODIFY f2 CHAR(200); +CALL get_table_id("test/t1", @tbl1_id); +SELECT @tbl1_id = @tbl_id; +@tbl1_id = @tbl_id +0 +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `f1` int(11) NOT NULL, + `f2` char(200) DEFAULT NULL, + KEY `idx` (`f2`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +DROP TABLE t1; +CREATE TABLE t1(f1 INT NOT NULL, +f2 VARCHAR(100), +INDEX idx(f2(40)))ENGINE=InnoDB; +CALL get_table_id("test/t1", @tbl_id); +ALTER TABLE t1 MODIFY f2 TEXT; +CALL get_table_id("test/t1", @tbl1_id); +SELECT @tbl1_id = @tbl_id; +@tbl1_id = @tbl_id +0 +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `f1` int(11) NOT NULL, + `f2` text DEFAULT NULL, + KEY `idx` (`f2`(40)) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +DROP TABLE t1; +CREATE TABLE t1(f1 INT NOT NULL, +f2 VARCHAR(100), +INDEX idx(f2(40)))ENGINE=InnoDB; +CALL get_table_id("test/t1", @tbl_id); +ALTER TABLE t1 MODIFY f2 VARCHAR(300); +CALL get_table_id("test/t1", @tbl1_id); +SELECT @tbl1_id = @tbl_id; +@tbl1_id = @tbl_id +0 +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `f1` int(11) NOT NULL, + `f2` varchar(300) DEFAULT NULL, + KEY `idx` (`f2`(40)) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +DROP TABLE t1; +CREATE TABLE t1(f1 INT NOT NULL, +f2 VARCHAR(100), +INDEX idx(f2(40)))ENGINE=InnoDB; +CALL get_table_id("test/t1", @tbl_id); +ALTER TABLE t1 MODIFY f2 VARCHAR(200) CHARACTER SET UTF16; +CALL get_table_id("test/t1", @tbl1_id); +SELECT @tbl1_id = @tbl_id; +@tbl1_id = @tbl_id +0 +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `f1` int(11) NOT NULL, + `f2` varchar(200) CHARACTER SET utf16 DEFAULT NULL, + KEY `idx` (`f2`(40)) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +DROP TABLE t1; +CREATE TABLE t1(f1 INT NOT NULL, +f2 VARCHAR(100), +f3 VARCHAR(50) as (f2) VIRTUAL, +INDEX idx(f3))ENGINE=InnoDB; +# If varchar virtual column extension is allowed in the future then +# InnoDB must rebuild the index +ALTER TABLE t1 MODIFY f3 VARCHAR(100); +ERROR HY000: This is not yet supported for generated columns +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `f1` int(11) NOT NULL, + `f2` varchar(100) DEFAULT NULL, + `f3` varchar(50) GENERATED ALWAYS AS (`f2`) VIRTUAL, + KEY `idx` (`f3`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +DROP TABLE t1; +DROP PROCEDURE get_index_id; +DROP PROCEDURE get_table_id; diff --git a/mysql-test/suite/innodb/t/alter_varchar_change.test b/mysql-test/suite/innodb/t/alter_varchar_change.test new file mode 100644 index 00000000000..f435125e581 --- /dev/null +++ b/mysql-test/suite/innodb/t/alter_varchar_change.test @@ -0,0 +1,336 @@ +--source include/have_innodb.inc + +DELIMITER |; +CREATE PROCEDURE get_index_id(IN tbl_id INT, IN idx_name char(100), OUT idx_id INT) +BEGIN +SELECT index_id into idx_id FROM INFORMATION_SCHEMA.INNODB_SYS_INDEXES WHERE + NAME=idx_name and TABLE_ID=tbl_id; +END| + +CREATE PROCEDURE get_table_id(IN tbl_name char(100), OUT tbl_id INT) +BEGIN +SELECT table_id into tbl_id FROM INFORMATION_SCHEMA.INNODB_SYS_TABLES WHERE + NAME = tbl_name; +END| + +DELIMITER ;| + +SET @tbl_id = 0; +SET @tbl1_id = 0; +SET @idx_id = 0; +SET @idx1_id = 0; + +# Table should avoid rebuild for the following varchar change. + +CREATE TABLE t1(f1 INT NOT NULL, + f2 VARCHAR(100) PRIMARY KEY)ENGINE=InnoDB; + +CALL get_table_id("test/t1", @tbl_id); +ALTER TABLE t1 MODIFY f2 VARCHAR(200); +CALL get_table_id("test/t1", @tbl1_id); + +SELECT @tbl1_id = @tbl_id; +SHOW CREATE TABLE t1; +DROP TABLE t1; + +# Index should avoid rebuild +CREATE TABLE t1(f1 INT NOT NULL, + f2 VARCHAR(100), + INDEX idx(f2))ENGINE=InnoDB; + +CALL get_table_id("test/t1", @tbl_id); +ALTER TABLE t1 MODIFY f2 VARCHAR(200); +CALL get_table_id("test/t1", @tbl1_id); + +SELECT @tbl1_id = @tbl_id; +SHOW CREATE TABLE t1; +DROP TABLE t1; + +CREATE TABLE t1(f1 INT NOT NULL, f2 VARCHAR(100), f3 VARCHAR(100), + INDEX idx(f2, f3), index idx1(f3, f2))ENGINE=InnoDB; + +CALL get_table_id("test/t1", @tbl_id); +CALL get_index_id(@tbl_id, "idx", @idx_id); +ALTER TABLE t1 MODIFY f2 VARCHAR(200), MODIFY f3 VARCHAR(150); +CALL get_table_id("test/t1", @tbl1_id); +CALL get_index_id(@tbl1_id, "idx", @idx1_id); + +SELECT @tbl1_id = @tbl_id; +SELECT @idx1_id = @idx_id; +SHOW CREATE TABLE t1; +DROP TABLE t1; + +CREATE TABLE t1(f1 INT NOT NULL, f2 VARCHAR(100), + INDEX idx(f2(40)))ENGINE=InnoDB; + +CALL get_table_id("test/t1", @tbl_id); +CALL get_index_id(@tbl_id, "idx", @idx_id); +ALTER TABLE t1 MODIFY f2 VARCHAR(200); +CALL get_table_id("test/t1", @tbl1_id); +CALL get_index_id(@tbl1_id, "idx", @idx1_id); + +SELECT @tbl1_id = @tbl_id; +SELECT @idx1_id = @idx_id; + +SHOW CREATE TABLE t1; +DROP TABLE t1; + +CREATE TABLE t1(f1 INT NOT NULL, + f2 VARCHAR(100), FULLTEXT idx(f2))ENGINE=InnoDB; + +CALL get_table_id("test/t1", @tbl_id); +CALL get_index_id(@tbl_id, "idx", @idx_id); +ALTER TABLE t1 MODIFY f2 VARCHAR(200); +CALL get_table_id("test/t1", @tbl1_id); +CALL get_index_id(@tbl1_id, "idx", @idx1_id); + +SELECT @tbl1_id = @tbl_id; +SELECT @idx1_id = @idx_id; +SHOW CREATE TABLE t1; +DROP TABLE t1; + +CREATE TABLE t1(f1 INT NOT NULL, + f2 VARCHAR(100), + f3 VARCHAR(50) as (f2) VIRTUAL, + INDEX idx(f3))ENGINE=InnoDB; + +INSERT INTO t1(f1, f2) VALUES(1, repeat('a', 40)); + +CALL get_table_id("test/t1", @tbl_id); +CALL get_index_id(@tbl_id, "idx", @idx_id); +ALTER TABLE t1 MODIFY f2 VARCHAR(100); +CALL get_table_id("test/t1", @tbl1_id); +CALL get_index_id(@tbl1_id, "idx", @idx1_id); + +SELECT @tbl1_id = @tbl_id; +SELECT @idx1_id = @idx_id; +SHOW CREATE TABLE t1; +DROP TABLE t1; + +CREATE TABLE t1(f1 INT NOT NULL, + f2 VARCHAR(100), + INDEX idx(f2(10)), + INDEX idx1(f1))ENGINE=InnoDB; + +CALL get_table_id("test/t1", @tbl_id); +CALL get_index_id(@tbl_id, "idx", @idx_id); +ALTER TABLE t1 MODIFY f2 VARCHAR(200), DROP INDEX idx1; +CALL get_table_id("test/t1", @tbl1_id); +CALL get_index_id(@tbl1_id, "idx", @idx1_id); + +SELECT @tbl1_id = @tbl_id; +SELECT @idx1_id = @idx_id; +SHOW CREATE TABLE t1; +DROP TABLE t1; + +CREATE TABLE t1(f1 INT NOT NULL, + f2 VARCHAR(100), + INDEX idx(f2(10)))ENGINE=InnoDB; + +CALL get_table_id("test/t1", @tbl_id); +CALL get_index_id(@tbl_id, "idx", @idx_id); +ALTER TABLE t1 MODIFY f2 VARCHAR(200), DROP INDEX idx, ADD INDEX idx(f2(10)); +CALL get_table_id("test/t1", @tbl1_id); +CALL get_index_id(@tbl1_id, "idx", @idx1_id); + +SELECT @tbl1_id = @tbl_id; +SELECT @idx1_id = @idx_id; +SHOW CREATE TABLE t1; +DROP TABLE t1; + +CREATE TABLE t1(f1 INT NOT NULL, + f2 VARCHAR(100), + INDEX idx(f2(10)))ENGINE=InnoDB; + +CALL get_table_id("test/t1", @tbl_id); +CALL get_index_id(@tbl_id, "idx", @idx_id); +ALTER TABLE t1 MODIFY f2 VARCHAR(200), DROP INDEX idx, ADD INDEX idx(f2(50)); +CALL get_table_id("test/t1", @tbl1_id); +CALL get_index_id(@tbl1_id, "idx", @idx1_id); + +SELECT @tbl1_id = @tbl_id; +SELECT @idx1_id = @idx_id; +SHOW CREATE TABLE t1; +DROP TABLE t1; + +# Newly added index should built + +CREATE TABLE t1(f1 INT NOT NULL, + f2 VARCHAR(100), + INDEX idx(f2(100)))ENGINE=InnoDB; + +CALL get_table_id("test/t1", @tbl_id); +CALL get_index_id(@tbl_id, "idx", @idx_id); +ALTER TABLE t1 MODIFY f2 VARCHAR(200), ADD INDEX idx1(f1); +CALL get_table_id("test/t1", @tbl1_id); +CALL get_index_id(@tbl1_id, "idx", @idx1_id); + +SELECT @tbl1_id = @tbl_id; +SELECT @idx1_id = @idx_id; +SHOW CREATE TABLE t1; +DROP TABLE t1; + +CREATE TABLE t1(f1 INT NOT NULL, + f2 VARCHAR(100), + INDEX idx(f2(10)))ENGINE=InnoDB; + +CALL get_table_id("test/t1", @tbl_id); +CALL get_index_id(@tbl_id, "idx", @idx_id); +ALTER TABLE t1 MODIFY f2 VARCHAR(200), DROP INDEX idx, ADD INDEX idx(f2(6)); +CALL get_table_id("test/t1", @tbl1_id); +CALL get_index_id(@tbl1_id, "idx", @idx1_id); + +SELECT @tbl1_id = @tbl_id; +SELECT @idx1_id = @idx_id; +SHOW CREATE TABLE t1; +DROP TABLE t1; + +# Table should rebuild + +CREATE TABLE t1(f1 INT NOT NULL, + f2 VARCHAR(100), + INDEX idx(f2))ENGINE=InnoDB; + +CALL get_table_id("test/t1", @tbl_id); +ALTER TABLE t1 MODIFY f2 VARCHAR(200), ADD COLUMN f3 INT; +CALL get_table_id("test/t1", @tbl1_id); + +SELECT @tbl1_id = @tbl_id; +SHOW CREATE TABLE t1; +DROP TABLE t1; + +CREATE TABLE t1(f1 INT NOT NULL, f2 VARCHAR(100) PRIMARY KEY)ENGINE=InnoDB; + +CALL get_table_id("test/t1", @tbl_id); +ALTER TABLE t1 MODIFY f2 VARCHAR(200), ADD COLUMN f3 INT; +CALL get_table_id("test/t1", @tbl1_id); + +SELECT @tbl1_id = @tbl_id; +SHOW CREATE TABLE t1; +DROP TABLE t1; + +CREATE TABLE t1(f1 INT NOT NULL, f2 VARCHAR(100))ENGINE=INNODB; + +CALL get_table_id("test/t1", @tbl_id); +ALTER TABLE t1 MODIFY f2 VARCHAR(200), ADD FULLTEXT idx(f2); +CALL get_table_id("test/t1", @tbl1_id); + +SELECT @tbl1_id = @tbl_id; +SHOW CREATE TABLE t1; +DROP TABLE t1; + +CREATE TABLE t1(f1 INT NOT NULL, + f2 CHAR(100) PRIMARY KEY)ENGINE=InnoDB; + +CALL get_table_id("test/t1", @tbl_id); +ALTER TABLE t1 MODIFY f2 CHAR(200); +CALL get_table_id("test/t1", @tbl1_id); + +SELECT @tbl1_id = @tbl_id; +SHOW CREATE TABLE t1; +DROP TABLE t1; + +CREATE TABLE t1(f1 INT NOT NULL, + f2 VARCHAR(100), + INDEX idx(f2(10)), + INDEX idx1(f1))ENGINE=InnoDB; + +CALL get_table_id("test/t1", @tbl_id); +ALTER TABLE t1 MODIFY f2 VARCHAR(50); +CALL get_table_id("test/t1", @tbl1_id); + +SELECT @tbl1_id = @tbl_id; +SHOW CREATE TABLE t1; +DROP TABLE t1; + +CREATE TABLE t1(f1 INT NOT NULL, + f2 VARCHAR(100), + INDEX idx(f2(10)), + INDEX idx1(f1))ENGINE=InnoDB; + +CALL get_table_id("test/t1", @tbl_id); +ALTER TABLE t1 MODIFY f2 VARCHAR(5), DROP INDEX idx1; +CALL get_table_id("test/t1", @tbl1_id); + +SELECT @tbl1_id = @tbl_id; +SHOW CREATE TABLE t1; +DROP TABLE t1; + +CREATE TABLE t1(f1 INT NOT NULL, + f2 VARCHAR(100), FULLTEXT idx(f2))ENGINE=InnoDB; + +CALL get_table_id("test/t1", @tbl_id); +CALL get_index_id(@tbl_id, "idx", @idx_id); +ALTER TABLE t1 MODIFY f2 VARCHAR(50); +CALL get_table_id("test/t1", @tbl1_id); +CALL get_index_id(@tbl1_id, "idx", @idx1_id); + +SELECT @tbl1_id = @tbl_id; +SELECT @idx1_id = @idx_id; +SHOW CREATE TABLE t1; +DROP TABLE t1; + +CREATE TABLE t1(f1 INT NOT NULL, + f2 VARCHAR(100), + INDEX idx(f2))ENGINE=InnoDB; + +CALL get_table_id("test/t1", @tbl_id); +ALTER TABLE t1 MODIFY f2 CHAR(200); +CALL get_table_id("test/t1", @tbl1_id); + +SELECT @tbl1_id = @tbl_id; +SHOW CREATE TABLE t1; +DROP TABLE t1; + +CREATE TABLE t1(f1 INT NOT NULL, + f2 VARCHAR(100), + INDEX idx(f2(40)))ENGINE=InnoDB; + +CALL get_table_id("test/t1", @tbl_id); +ALTER TABLE t1 MODIFY f2 TEXT; +CALL get_table_id("test/t1", @tbl1_id); + +SELECT @tbl1_id = @tbl_id; +SHOW CREATE TABLE t1; +DROP TABLE t1; + +CREATE TABLE t1(f1 INT NOT NULL, + f2 VARCHAR(100), + INDEX idx(f2(40)))ENGINE=InnoDB; + +CALL get_table_id("test/t1", @tbl_id); +ALTER TABLE t1 MODIFY f2 VARCHAR(300); +CALL get_table_id("test/t1", @tbl1_id); + +SELECT @tbl1_id = @tbl_id; +SHOW CREATE TABLE t1; +DROP TABLE t1; + +CREATE TABLE t1(f1 INT NOT NULL, + f2 VARCHAR(100), + INDEX idx(f2(40)))ENGINE=InnoDB; + +CALL get_table_id("test/t1", @tbl_id); +ALTER TABLE t1 MODIFY f2 VARCHAR(200) CHARACTER SET UTF16; +CALL get_table_id("test/t1", @tbl1_id); + +SELECT @tbl1_id = @tbl_id; +SHOW CREATE TABLE t1; +DROP TABLE t1; +# Show error when virtual varchar column got changed + +CREATE TABLE t1(f1 INT NOT NULL, + f2 VARCHAR(100), + f3 VARCHAR(50) as (f2) VIRTUAL, + INDEX idx(f3))ENGINE=InnoDB; + +--echo # If varchar virtual column extension is allowed in the future then +--echo # InnoDB must rebuild the index + +--error ER_UNSUPPORTED_ACTION_ON_GENERATED_COLUMN +ALTER TABLE t1 MODIFY f3 VARCHAR(100); +SHOW CREATE TABLE t1; +DROP TABLE t1; + +DROP PROCEDURE get_index_id; +DROP PROCEDURE get_table_id; diff --git a/sql/handler.h b/sql/handler.h index 284fed7cd7e..229197a2c78 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -2001,6 +2001,11 @@ public: static const HA_ALTER_FLAGS ALTER_DROP_CHECK_CONSTRAINT= 1ULL << 40; + /** + Change in index length such that it doesn't require index rebuild. + */ + static const HA_ALTER_FLAGS ALTER_COLUMN_INDEX_LENGTH= 1ULL << 41; + /** Create options (like MAX_ROWS) for the new version of table. diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 1b426f80a88..19100aafd51 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -6303,7 +6303,7 @@ static bool fill_alter_inplace_info(THD *thd, bool varchar, Alter_inplace_info *ha_alter_info) { - Field **f_ptr, *field; + Field **f_ptr, *field, *old_field; List_iterator_fast new_field_it; Create_field *new_field; KEY_PART_INFO *key_part, *new_part; @@ -6629,6 +6629,7 @@ static bool fill_alter_inplace_info(THD *thd, Go through keys and check if the original ones are compatible with new table. */ + uint old_field_len= 0; KEY *table_key; KEY *table_key_end= table->key_info + table->s->keys; KEY *new_key; @@ -6693,17 +6694,35 @@ static bool fill_alter_inplace_info(THD *thd, key_part < end; key_part++, new_part++) { - /* - Key definition has changed if we are using a different field or - if the used key part length is different. It makes sense to - check lengths first as in case when fields differ it is likely - that lengths differ too and checking fields is more expensive - in general case. - */ - if (key_part->length != new_part->length) - goto index_changed; - new_field= get_field_by_index(alter_info, new_part->fieldnr); + old_field= table->field[key_part->fieldnr - 1]; + /* + If there is a change in index length due to column expansion + like varchar(X) changed to varchar(X + N) and has a compatible + packed data representation, we mark it for fast/INPLACE change + in index definition. InnoDB supports INPLACE for this cases + + Key definition has changed if we are using a different field or + if the user key part length is different. + */ + old_field_len= old_field->pack_length(); + + if (old_field->type() == MYSQL_TYPE_VARCHAR) + { + old_field_len= (old_field->pack_length() + - ((Field_varstring*) old_field)->length_bytes); + } + + if (key_part->length == old_field_len && + key_part->length < new_part->length && + (key_part->field->is_equal((Create_field*) new_field) + == IS_EQUAL_PACK_LENGTH)) + { + ha_alter_info->handler_flags |= + Alter_inplace_info::ALTER_COLUMN_INDEX_LENGTH; + } + else if (key_part->length != new_part->length) + goto index_changed; /* For prefix keys KEY_PART_INFO::field points to cloned Field diff --git a/storage/innobase/handler/handler0alter.cc b/storage/innobase/handler/handler0alter.cc index 0f896e6138f..13d78d636d8 100644 --- a/storage/innobase/handler/handler0alter.cc +++ b/storage/innobase/handler/handler0alter.cc @@ -90,7 +90,8 @@ static const Alter_inplace_info::HA_ALTER_FLAGS INNOBASE_INPLACE_IGNORE | Alter_inplace_info::ALTER_COLUMN_COLUMN_FORMAT | Alter_inplace_info::ALTER_COLUMN_STORAGE_TYPE | Alter_inplace_info::ALTER_VIRTUAL_GCOL_EXPR - | Alter_inplace_info::ALTER_RENAME; + | Alter_inplace_info::ALTER_RENAME + | Alter_inplace_info::ALTER_COLUMN_INDEX_LENGTH; /** Operations on foreign key definitions (changing the schema only) */ static const Alter_inplace_info::HA_ALTER_FLAGS INNOBASE_FOREIGN_OPERATIONS From 20e19f69750466ac2c9bbf82ef5ba396fc842a08 Mon Sep 17 00:00:00 2001 From: Vladislav Vaintroub Date: Thu, 31 Jan 2019 22:08:45 +0100 Subject: [PATCH 07/11] MDEV-17479 Assertion `mysql_socket.fd != -1' failed in inline_mysql_socket_send on server shutdown Do not try to write ER_SHUTDOWN error message to socket, when it is forcefully closed by the shutdown. This will avoid the race condition (attempt to write to closed socket, if connection shuts down by itself). --- sql/mysqld.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 77736607e22..9240622cadf 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -1791,7 +1791,7 @@ static void close_connections(void) */ THD* save_thd= current_thd; set_current_thd(tmp); - close_connection(tmp,ER_SERVER_SHUTDOWN); + close_connection(tmp); set_current_thd(save_thd); } #endif From f669cecbe30da820b93ebff0d3ed27d578627e47 Mon Sep 17 00:00:00 2001 From: Thirunarayanan Balathandayuthapani Date: Thu, 31 Jan 2019 02:20:51 +0530 Subject: [PATCH 08/11] MDEV-18415 mariabackup.mdev-14447 test case fails with Table 'test.t' doesn't exist in engine - Added retry logic if validation of first page fails with checksum mismatch. --- extra/mariabackup/xtrabackup.cc | 15 +++++++++++---- .../suite/mariabackup/mdev-14447.result | 1 + mysql-test/suite/mariabackup/mdev-14447.test | 14 ++++++++++++-- storage/innobase/buf/buf0buf.cc | 10 ++++++++++ storage/innobase/include/fsp0file.h | 19 +++++++++++++++++++ 5 files changed, 53 insertions(+), 6 deletions(-) diff --git a/extra/mariabackup/xtrabackup.cc b/extra/mariabackup/xtrabackup.cc index 87f8822c38b..6ba5d950104 100644 --- a/extra/mariabackup/xtrabackup.cc +++ b/extra/mariabackup/xtrabackup.cc @@ -3092,7 +3092,16 @@ xb_load_single_table_tablespace( die("Can't open datafile %s", name); } - err = file->validate_first_page(&flush_lsn); + for (int i = 0; i < 10; i++) { + err = file->validate_first_page(&flush_lsn); + if (err != DB_CORRUPTION) { + break; + } + + my_sleep(1000); + } + + bool is_empty_file = file->exists() && file->is_empty_file(); if (err == DB_SUCCESS && file->space_id() != SRV_TMP_SPACE_ID) { os_offset_t node_size = os_file_get_size(file->handle()); @@ -3124,9 +3133,7 @@ xb_load_single_table_tablespace( delete file; - if (err != DB_SUCCESS && err != DB_CORRUPTION && xtrabackup_backup) { - /* allow corrupted first page for xtrabackup, it could be just - zero-filled page, which we restore from redo log later */ + if (err != DB_SUCCESS && xtrabackup_backup && !is_empty_file) { die("Failed to not validate first page of the file %s, error %d",name, (int)err); } } diff --git a/mysql-test/suite/mariabackup/mdev-14447.result b/mysql-test/suite/mariabackup/mdev-14447.result index 3bca7eb5701..6600c13ed74 100644 --- a/mysql-test/suite/mariabackup/mdev-14447.result +++ b/mysql-test/suite/mariabackup/mdev-14447.result @@ -7,6 +7,7 @@ COMMIT; SELECT count(*) FROM t; count(*) 100000 +FOUND 1 /Checksum mismatch in datafile/ in backup.log # Prepare full backup, apply incremental one # Restore and check results # shutdown server diff --git a/mysql-test/suite/mariabackup/mdev-14447.test b/mysql-test/suite/mariabackup/mdev-14447.test index 689b578f2ab..96d12368547 100644 --- a/mysql-test/suite/mariabackup/mdev-14447.test +++ b/mysql-test/suite/mariabackup/mdev-14447.test @@ -1,3 +1,4 @@ +--source include/have_debug.inc call mtr.add_suppression("InnoDB: New log files created"); let $basedir=$MYSQLTEST_VARDIR/tmp/backup; @@ -9,16 +10,26 @@ echo # Create full backup , modify table, then create incremental/differential b --disable_result_log exec $XTRABACKUP --defaults-file=$MYSQLTEST_VARDIR/my.cnf --backup --target-dir=$basedir; --enable_result_log + BEGIN; INSERT INTO t select uuid(), uuid(), uuid(), uuid() from seq_1_to_100000; COMMIT; SELECT count(*) FROM t; -exec $XTRABACKUP --defaults-file=$MYSQLTEST_VARDIR/my.cnf --backup --target-dir=$incremental_dir --incremental-basedir=$basedir; +let $backuplog=$MYSQLTEST_VARDIR/tmp/backup.log; + +exec $XTRABACKUP --defaults-file=$MYSQLTEST_VARDIR/my.cnf --backup --target-dir=$incremental_dir --incremental-basedir=$basedir --dbug=+d,page_intermittent_checksum_mismatch 2> $backuplog; + +--let SEARCH_RANGE = 10000000 +--let SEARCH_PATTERN=Checksum mismatch in datafile +--let SEARCH_FILE=$backuplog +--source include/search_pattern_in_file.inc +remove_file $backuplog; --disable_result_log echo # Prepare full backup, apply incremental one; exec $XTRABACKUP --prepare --verbose --apply-log-only --target-dir=$basedir; + exec $XTRABACKUP --prepare --verbose --apply-log-only --target-dir=$basedir --incremental-dir=$incremental_dir ; echo # Restore and check results; @@ -36,7 +47,6 @@ exec $XTRABACKUP --defaults-file=$MYSQLTEST_VARDIR/my.cnf --copy-back --datadir= echo # restart server; --source include/start_mysqld.inc - --enable_result_log SELECT count(*) FROM t; DROP TABLE t; diff --git a/storage/innobase/buf/buf0buf.cc b/storage/innobase/buf/buf0buf.cc index 8dc2fd20dbe..e97a36efa56 100644 --- a/storage/innobase/buf/buf0buf.cc +++ b/storage/innobase/buf/buf0buf.cc @@ -1103,6 +1103,16 @@ buf_page_is_corrupted( if (srv_checksum_algorithm == SRV_CHECKSUM_ALGORITHM_CRC32) { + + DBUG_EXECUTE_IF( + "page_intermittent_checksum_mismatch", { + static int page_counter; + if (page_counter++ == 2) { + checksum_field2++; + } + }); + + crc32 = buf_page_check_crc32(read_buf, checksum_field2); crc32_inited = true; diff --git a/storage/innobase/include/fsp0file.h b/storage/innobase/include/fsp0file.h index 9bc7b4bf9c9..0cb6ccaf86c 100644 --- a/storage/innobase/include/fsp0file.h +++ b/storage/innobase/include/fsp0file.h @@ -314,6 +314,25 @@ public: return(m_last_os_error); } + /** Check whether the file is empty. + @return true if file is empty */ + bool is_empty_file() const + { +#ifdef _WIN32 + os_offset_t offset = + (os_offset_t) m_file_info.nFileSizeLow + | ((os_offset_t) m_file_info.nFileSizeHigh << 32); + + return (offset == 0); +#else + return (m_file_info.st_size == 0); +#endif + } + + /** Check if the file exist. + @return true if file exists. */ + bool exists() const { return m_exists; } + /** Test if the filepath provided looks the same as this filepath by string comparison. If they are two different paths to the same file, same_as() will be used to show that after the files are opened. From a2641b26111116adabe1e1c06b768d8c3e06e129 Mon Sep 17 00:00:00 2001 From: Vladislav Vaintroub Date: Tue, 29 Jan 2019 13:15:59 +0100 Subject: [PATCH 09/11] MDEV-18380 : adjust max_statement_time in mariabackup --- extra/mariabackup/backup_mysql.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extra/mariabackup/backup_mysql.cc b/extra/mariabackup/backup_mysql.cc index 8936b577a9e..a691cb664f1 100644 --- a/extra/mariabackup/backup_mysql.cc +++ b/extra/mariabackup/backup_mysql.cc @@ -152,7 +152,7 @@ xb_mysql_connect() return(NULL); } - xb_mysql_query(connection, "SET SESSION wait_timeout=2147483", + xb_mysql_query(connection, "SET SESSION wait_timeout=2147483, max_statement_time=0", false, true); return(connection); From 7c7161a1bd150e61f530933f096a8035932071af Mon Sep 17 00:00:00 2001 From: Thirunarayanan Balathandayuthapani Date: Mon, 28 Jan 2019 14:38:39 +0530 Subject: [PATCH 10/11] MDEV-18194 Incremental prepare tries to access page which is out of tablespace bounds Problem: ======= Mariabackup incremental prepare creates new tablespace when it encounter new tablespace. It sets the intial size as FIL_IBD_FILE_INITIAL_SIZE (4). But while applying redo log, it tries to access 5th page and then it leads to out of tablespace error. Fix: === While parsing the redo log record, track FSP_SIZE in recv_spaces for the respective space id. Assign the recv_size for the tablespace when it is loaded. Extend the tablespace depends on recv_size while applying the redo log record. --- .../incremental_ddl_before_backup.result | 32 ++++++++++++ .../incremental_ddl_before_backup.test | 50 +++++++++++++++++++ storage/innobase/fil/fil0fil.cc | 2 +- storage/innobase/log/log0recv.cc | 30 +++++++++-- 4 files changed, 109 insertions(+), 5 deletions(-) create mode 100644 mysql-test/suite/mariabackup/incremental_ddl_before_backup.result create mode 100644 mysql-test/suite/mariabackup/incremental_ddl_before_backup.test diff --git a/mysql-test/suite/mariabackup/incremental_ddl_before_backup.result b/mysql-test/suite/mariabackup/incremental_ddl_before_backup.result new file mode 100644 index 00000000000..a6273a20ff5 --- /dev/null +++ b/mysql-test/suite/mariabackup/incremental_ddl_before_backup.result @@ -0,0 +1,32 @@ +call mtr.add_suppression("InnoDB: New log files created"); +CREATE TABLE t1(i INT) ENGINE INNODB; +CREATE TABLE t2(i INT PRIMARY KEY) ENGINE INNODB; +CREATE TABLE t3(i INT) ENGINE INNODB; +# Create full backup , modify table, then create incremental/differential backup +create table t4(f1 int not null, f2 int not null)engine=innodb; +insert into t4 values(1, 2), (2, 2), (3, 3), (5, 5), (6, 6), (4, 4), (9, 9); +insert into t4 select * from t4; +insert into t4 select * from t4; +insert into t4 select * from t4; +insert into t4 select * from t4; +insert into t4 select * from t4; +insert into t4 select * from t4; +insert into t4 select * from t4; +insert into t4 select * from t4; +insert into t4 select * from t4; +insert into t4 select * from t4; +rename table t4 to t7; +select count(*) from t7; +count(*) +7168 +# XTRABACKUP INCREMENTAL +# XTRABACKUP PREPARE +# XTRABACKUP INCREMENTAL PREPARE +# shutdown server +# remove datadir +# xtrabackup move back +# restart server +select count(*) from t7; +count(*) +7168 +drop table t1, t2, t7, t3; diff --git a/mysql-test/suite/mariabackup/incremental_ddl_before_backup.test b/mysql-test/suite/mariabackup/incremental_ddl_before_backup.test new file mode 100644 index 00000000000..2136771b97e --- /dev/null +++ b/mysql-test/suite/mariabackup/incremental_ddl_before_backup.test @@ -0,0 +1,50 @@ +--source include/have_debug.inc + +call mtr.add_suppression("InnoDB: New log files created"); + +let $basedir=$MYSQLTEST_VARDIR/tmp/backup; +let $incremental_dir=$MYSQLTEST_VARDIR/tmp/backup_inc1; + +CREATE TABLE t1(i INT) ENGINE INNODB; +CREATE TABLE t2(i INT PRIMARY KEY) ENGINE INNODB; +CREATE TABLE t3(i INT) ENGINE INNODB; + +echo # Create full backup , modify table, then create incremental/differential backup; +--disable_result_log +exec $XTRABACKUP --defaults-file=$MYSQLTEST_VARDIR/my.cnf --backup --target-dir=$basedir; +--enable_result_log + +create table t4(f1 int not null, f2 int not null)engine=innodb; +insert into t4 values(1, 2), (2, 2), (3, 3), (5, 5), (6, 6), (4, 4), (9, 9); +insert into t4 select * from t4; +insert into t4 select * from t4; +insert into t4 select * from t4; +insert into t4 select * from t4; +insert into t4 select * from t4; +insert into t4 select * from t4; +insert into t4 select * from t4; +insert into t4 select * from t4; +insert into t4 select * from t4; +insert into t4 select * from t4; +rename table t4 to t7; +select count(*) from t7; + +--echo # XTRABACKUP INCREMENTAL +exec $XTRABACKUP --defaults-file=$MYSQLTEST_VARDIR/my.cnf --backup --target-dir=$incremental_dir --incremental-basedir=$basedir; + +--echo # XTRABACKUP PREPARE +exec $XTRABACKUP --apply-log-only --prepare --target-dir=$basedir; + +--echo # XTRABACKUP INCREMENTAL PREPARE +exec $XTRABACKUP --prepare --target-dir=$basedir --incremental-dir=$incremental_dir; + +let $targetdir=$basedir; +-- source include/restart_and_restore.inc +--enable_result_log + +select count(*) from t7; +drop table t1, t2, t7, t3; + +# Cleanup +rmdir $basedir; +rmdir $incremental_dir; diff --git a/storage/innobase/fil/fil0fil.cc b/storage/innobase/fil/fil0fil.cc index 604f43d7639..623aa38be38 100644 --- a/storage/innobase/fil/fil0fil.cc +++ b/storage/innobase/fil/fil0fil.cc @@ -963,7 +963,7 @@ fil_space_extend_must_retry( we have set the node->being_extended flag. */ mutex_exit(&fil_system->mutex); - ut_ad(size > space->size); + ut_ad(size >= space->size); ulint last_page_no = space->size; const ulint file_start_page_no = last_page_no - node->size; diff --git a/storage/innobase/log/log0recv.cc b/storage/innobase/log/log0recv.cc index f71067fddf2..bcd6254ebd6 100644 --- a/storage/innobase/log/log0recv.cc +++ b/storage/innobase/log/log0recv.cc @@ -152,9 +152,13 @@ struct file_name_t { /** Status of the tablespace */ fil_status status; + /** FSP_SIZE of tablespace */ + ulint size; + /** Constructor */ file_name_t(std::string name_, bool deleted) : - name(name_), space(NULL), status(deleted ? DELETED: NORMAL) {} + name(name_), space(NULL), status(deleted ? DELETED: NORMAL), + size(0) {} }; /** Map of dirty tablespaces during recovery */ @@ -326,6 +330,11 @@ fil_name_process( ut_ad(space != NULL); if (f.space == NULL || f.space == space) { + + if (f.size && f.space == NULL) { + fil_space_set_recv_size(space->id, f.size); + } + f.name = fname.name; f.space = space; f.status = file_name_t::NORMAL; @@ -2394,11 +2403,24 @@ recv_parse_log_rec( } if (*page_no == 0 && *type == MLOG_4BYTES + && apply && mach_read_from_2(old_ptr) == FSP_HEADER_OFFSET + FSP_SIZE) { old_ptr += 2; - fil_space_set_recv_size(*space, - mach_parse_compressed(&old_ptr, - end_ptr)); + + ulint size = mach_parse_compressed(&old_ptr, end_ptr); + + recv_spaces_t::iterator it = recv_spaces.find(*space); + + ut_ad(!recv_sys->mlog_checkpoint_lsn + || *space == TRX_SYS_SPACE + || srv_is_undo_tablespace(*space) + || it != recv_spaces.end()); + + if (it != recv_spaces.end() && !it->second.space) { + it->second.size = size; + } + + fil_space_set_recv_size(*space, size); } return(new_ptr - ptr); From 09cea8703f3ec4e4f9e23855a339c9e3d5e84d3b Mon Sep 17 00:00:00 2001 From: Alexey Botchkov Date: Fri, 1 Feb 2019 17:10:27 +0400 Subject: [PATCH 11/11] MDEV-17148 DROP DATABASE throw "Directory not empty" after changed lower_case_table_names. No need to lowercase table names on case-sensitive file systems, as the cache won't contain the 'lowercased' table anyway. And it prevents the UPPERCASE.frm from being deleted. --- mysql-test/r/lowercase_table.result | 4 ++++ mysql-test/t/lowercase_table.test | 12 ++++++++++++ sql/sql_db.cc | 8 ++++++-- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/mysql-test/r/lowercase_table.result b/mysql-test/r/lowercase_table.result index ac7d3e6bf7b..823ffa7696f 100644 --- a/mysql-test/r/lowercase_table.result +++ b/mysql-test/r/lowercase_table.result @@ -127,3 +127,7 @@ Database (mysql_TE%) mysql_test drop database mysql_TEST; End of 10.0 tests +create database db1; +create table t1 (a int); +drop database db1; +drop table t1; diff --git a/mysql-test/t/lowercase_table.test b/mysql-test/t/lowercase_table.test index c339105aae4..e0dcb6c36dd 100644 --- a/mysql-test/t/lowercase_table.test +++ b/mysql-test/t/lowercase_table.test @@ -118,3 +118,15 @@ show databases like "mysql_TE%"; drop database mysql_TEST; --echo End of 10.0 tests + +# +# MDEV-17148 DROP DATABASE throw "Directory not empty" after changed lower_case_table_names. +# + +let $datadir=`select @@datadir`; +create database db1; +create table t1 (a int); +copy_file $datadir/test/t1.frm $datadir/db1/T1.frm; +drop database db1; +drop table t1; + diff --git a/sql/sql_db.cc b/sql/sql_db.cc index e94303f7e62..0e554e29380 100644 --- a/sql/sql_db.cc +++ b/sql/sql_db.cc @@ -1107,8 +1107,12 @@ static bool find_db_tables_and_rm_known_files(THD *thd, MY_DIR *dirp, table_list->table_name_length= table->length; table_list->open_type= OT_BASE_ONLY; - /* To be able to correctly look up the table in the table cache. */ - if (lower_case_table_names) + /* + On the case-insensitive file systems table is opened + with the lowercased file name. So we should lowercase + as well to look up the cache properly. + */ + if (lower_case_file_system) table_list->table_name_length= my_casedn_str(files_charset_info, table_list->table_name);