From a876121d24a19340db508f74b1582955c2a168b0 Mon Sep 17 00:00:00 2001 From: Monty Date: Fri, 30 Oct 2020 15:33:18 +0200 Subject: MDEV-23824 SIGSEGV in end_io_cache on REPAIR LOCAL TABLE for Aria table Bugs fixed: - prepare_for_repair() didn't close all open files if table opened failed because of out-of-memory - If dd_recreate_table() failed, the data file was not properly restored from it's temporary name - Aria repair initializing code didn't properly clear all used structs before calling error, which caused crashed in memory-free calls. - maria_delete_table() didn't register if table open failed. This could calls my_error() to be called without returning 1 to the caller, which cased failures in my_ok() Note when merging to 10.5: - Remove the #if MYSQL_VERSION from sql_admin.cc --- mysql-test/suite/maria/repair.result | 16 +++++++++++++ mysql-test/suite/maria/repair.test | 11 +++++++++ sql/sql_admin.cc | 45 +++++++++++++++++++++++++----------- storage/maria/ma_check.c | 34 +++++++++++++++++++-------- storage/maria/ma_delete_table.c | 9 +++++++- storage/maria/ma_open.c | 2 +- 6 files changed, 91 insertions(+), 26 deletions(-) diff --git a/mysql-test/suite/maria/repair.result b/mysql-test/suite/maria/repair.result index 6bb9e1b5b9e..296f251aa36 100644 --- a/mysql-test/suite/maria/repair.result +++ b/mysql-test/suite/maria/repair.result @@ -22,3 +22,19 @@ i 1 UNLOCK TABLES; DROP TABLE t1; +# +# MDEV-23824 SIGSEGV in end_io_cache on REPAIR LOCAL TABLE for Aria table +# +CREATE TABLE t1 (i INT) ENGINE=Aria; +INSERT INTO t1 VALUES (1); +SET max_session_mem_used=50000; +REPAIR LOCAL TABLE t1 USE_FRM; +Table Op Msg_type Msg_text +t1 repair error Failed to open partially repaired table +Warnings: +Error 1290 The MariaDB server is running with the --max-thread-mem-used=50000 option so it cannot execute this statement +REPAIR LOCAL TABLE t1; +Table Op Msg_type Msg_text +test.t1 repair Error The MariaDB server is running with the --max-thread-mem-used=50000 option so it cannot execute this statement +test.t1 repair error Corrupt +DROP TABLE t1; diff --git a/mysql-test/suite/maria/repair.test b/mysql-test/suite/maria/repair.test index 2f713950d3e..9603a949f9b 100644 --- a/mysql-test/suite/maria/repair.test +++ b/mysql-test/suite/maria/repair.test @@ -22,3 +22,14 @@ SELECT * FROM INFORMATION_SCHEMA.TABLES; SELECT * FROM t1; UNLOCK TABLES; DROP TABLE t1; + +--echo # +--echo # MDEV-23824 SIGSEGV in end_io_cache on REPAIR LOCAL TABLE for Aria table +--echo # + +CREATE TABLE t1 (i INT) ENGINE=Aria; +INSERT INTO t1 VALUES (1); +SET max_session_mem_used=50000; +REPAIR LOCAL TABLE t1 USE_FRM; +REPAIR LOCAL TABLE t1; +DROP TABLE t1; diff --git a/sql/sql_admin.cc b/sql/sql_admin.cc index 89ef6d7793f..8e956eb0f8c 100644 --- a/sql/sql_admin.cc +++ b/sql/sql_admin.cc @@ -91,10 +91,10 @@ static int send_check_errmsg(THD *thd, TABLE_LIST* table, static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, HA_CHECK_OPT *check_opt) { - int error= 0; + int error= 0, create_error= 0; TABLE tmp_table, *table; TABLE_LIST *pos_in_locked_tables= 0; - TABLE_SHARE *share; + TABLE_SHARE *share= 0; bool has_mdl_lock= FALSE; char from[FN_REFLEN],tmp[FN_REFLEN+32]; const char **ext; @@ -207,6 +207,23 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, HA_EXTRA_NOT_USED, NULL); table_list->table= 0; } + else + { + /* + Table open failed, maybe because we run out of memory. + Close all open tables and relaese all MDL locks + */ +#if MYSQL_VERSION < 100500 + tdc_remove_table(thd, TDC_RT_REMOVE_UNUSED, + table->s->db.str, table->s->table_name.str, + TRUE); +#else + tdc_release_share(share); + share->tdc->flush(thd, true); + share= 0; +#endif + } + /* After this point we have an exclusive metadata lock on our table in both cases when table was successfully open in mysql_admin_table() @@ -220,11 +237,8 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, goto end; } if (dd_recreate_table(thd, table_list->db.str, table_list->table_name.str)) - { - error= send_check_errmsg(thd, table_list, "repair", - "Failed generating table from .frm file"); - goto end; - } + create_error= send_check_errmsg(thd, table_list, "repair", + "Failed generating table from .frm file"); /* 'FALSE' for 'using_transactions' means don't postpone invalidation till the end of a transaction, but do it @@ -237,6 +251,8 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, "Failed restoring .MYD file"); goto end; } + if (create_error) + goto end; if (thd->locked_tables_list.locked_tables()) { @@ -264,7 +280,8 @@ end: if (table == &tmp_table) { closefrm(table); - tdc_release_share(table->s); + if (share) + tdc_release_share(share); } /* In case of a temporary table there will be no metadata lock. */ if (unlikely(error) && has_mdl_lock) @@ -592,6 +609,12 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, #endif DBUG_PRINT("admin", ("table: %p", table->table)); + if (table->schema_table) + { + result_code= HA_ADMIN_NOT_IMPLEMENTED; + goto send_result; + } + if (prepare_func) { DBUG_PRINT("admin", ("calling prepare_func")); @@ -650,12 +673,6 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, goto send_result; } - if (table->schema_table) - { - result_code= HA_ADMIN_NOT_IMPLEMENTED; - goto send_result; - } - if ((table->table->db_stat & HA_READ_ONLY) && open_for_modify) { /* purecov: begin inspected */ diff --git a/storage/maria/ma_check.c b/storage/maria/ma_check.c index 20f8eca2828..0f4d79032f8 100644 --- a/storage/maria/ma_check.c +++ b/storage/maria/ma_check.c @@ -2344,6 +2344,14 @@ static int initialize_variables_for_repair(HA_CHECK *param, { MARIA_SHARE *share= info->s; + /* + We have to clear these variables first, as the cleanup-in-case-of-error + handling may touch these. + */ + bzero((char*) sort_info, sizeof(*sort_info)); + bzero((char*) sort_param, sizeof(*sort_param)); + bzero(&info->rec_cache, sizeof(info->rec_cache)); + if (share->data_file_type == NO_RECORD) { _ma_check_print_error(param, @@ -2358,9 +2366,6 @@ static int initialize_variables_for_repair(HA_CHECK *param, if (share->lock.update_status) (*share->lock.update_status)(info); - bzero((char*) sort_info, sizeof(*sort_info)); - bzero((char*) sort_param, sizeof(*sort_param)); - param->testflag|= T_REP; /* for easy checking */ if (share->options & (HA_OPTION_CHECKSUM | HA_OPTION_COMPRESS_RECORD)) param->testflag|= T_CALC_CHECKSUM; @@ -2388,7 +2393,6 @@ static int initialize_variables_for_repair(HA_CHECK *param, set_data_file_type(sort_info, info->s); sort_info->org_data_file_type= share->data_file_type; - bzero(&info->rec_cache, sizeof(info->rec_cache)); info->rec_cache.file= info->dfile.file; info->update= (short) (HA_STATE_CHANGED | HA_STATE_ROW_CHANGED); @@ -2869,9 +2873,13 @@ err: _ma_reset_state(info); end_io_cache(¶m->read_cache); - end_io_cache(&sort_info.new_info->rec_cache); + if (sort_info.new_info) + { + end_io_cache(&sort_info.new_info->rec_cache); + sort_info.new_info->opt_flag&= ~(READ_CACHE_USED | WRITE_CACHE_USED); + } info->opt_flag&= ~(READ_CACHE_USED | WRITE_CACHE_USED); - sort_info.new_info->opt_flag&= ~(READ_CACHE_USED | WRITE_CACHE_USED); + sort_param.sort_info->info->in_check_table= 0; /* this below could fail, shouldn't we detect error? */ if (got_error) @@ -4086,10 +4094,13 @@ err: maria_scan_end(sort_info.info); _ma_reset_state(info); - end_io_cache(&sort_info.new_info->rec_cache); + if (sort_info.new_info) + { + end_io_cache(&sort_info.new_info->rec_cache); + sort_info.new_info->opt_flag&= ~(READ_CACHE_USED | WRITE_CACHE_USED); + } end_io_cache(¶m->read_cache); info->opt_flag&= ~(READ_CACHE_USED | WRITE_CACHE_USED); - sort_info.new_info->opt_flag&= ~(READ_CACHE_USED | WRITE_CACHE_USED); if (got_error) { if (! param->error_printed) @@ -4618,10 +4629,13 @@ err: the share by remove_io_thread() or it was not yet started (if the error happend before creating the thread). */ - end_io_cache(&sort_info.new_info->rec_cache); + if (sort_info.new_info) + { + end_io_cache(&sort_info.new_info->rec_cache); + sort_info.new_info->opt_flag&= ~(READ_CACHE_USED | WRITE_CACHE_USED); + } end_io_cache(¶m->read_cache); info->opt_flag&= ~(READ_CACHE_USED | WRITE_CACHE_USED); - sort_info.new_info->opt_flag&= ~(READ_CACHE_USED | WRITE_CACHE_USED); /* Destroy the new data cache in case of non-quick repair. All slave threads did either detach from the share by remove_io_thread() diff --git a/storage/maria/ma_delete_table.c b/storage/maria/ma_delete_table.c index fee001df1e1..0c78476ad44 100644 --- a/storage/maria/ma_delete_table.c +++ b/storage/maria/ma_delete_table.c @@ -30,6 +30,7 @@ int maria_delete_table(const char *name) { MARIA_HA *info; myf sync_dir; + int got_error= 0, error; DBUG_ENTER("maria_delete_table"); #ifdef EXTRA_DEBUG @@ -41,9 +42,13 @@ int maria_delete_table(const char *name) Unfortunately it is necessary to open the table just to check this. We use 'open_for_repair' to be able to open even a crashed table. */ + my_errno= 0; if (!(info= maria_open(name, O_RDONLY, HA_OPEN_FOR_REPAIR))) { sync_dir= 0; + /* Ignore not found errors and wrong symlink errors */ + if (my_errno != ENOENT && my_errno != HA_WRONG_CREATE_OPTION) + got_error= my_errno;; } else { @@ -78,7 +83,9 @@ int maria_delete_table(const char *name) DBUG_RETURN(1); } - DBUG_RETURN(maria_delete_table_files(name, 0, sync_dir)); + if (!(error= maria_delete_table_files(name, 0, sync_dir))) + error= got_error; + DBUG_RETURN(error); } diff --git a/storage/maria/ma_open.c b/storage/maria/ma_open.c index 226ebe8c1dd..3fa0e4293a6 100644 --- a/storage/maria/ma_open.c +++ b/storage/maria/ma_open.c @@ -1334,7 +1334,7 @@ static void setup_key_functions(register MARIA_KEYDEF *keyinfo) /** - @brief Function to save and store the header in the index file (.MYI) + @brief Function to save and store the header in the index file (.MAI) Operates under MARIA_SHARE::intern_lock if requested. Sets MARIA_SHARE::MARIA_STATE_INFO::is_of_horizon if transactional table. -- cgit v1.2.1 From 94859d985e22ff8a62300635502afa6ed2186905 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Lindstr=C3=B6m?= Date: Tue, 3 Nov 2020 08:49:10 +0200 Subject: Clean up wsrep.variables --- mysql-test/suite/wsrep/r/variables.result | 60 ------------------------------- mysql-test/suite/wsrep/t/variables.test | 6 ---- 2 files changed, 66 deletions(-) diff --git a/mysql-test/suite/wsrep/r/variables.result b/mysql-test/suite/wsrep/r/variables.result index c9c0c5af9c5..1c427b34d2b 100644 --- a/mysql-test/suite/wsrep/r/variables.result +++ b/mysql-test/suite/wsrep/r/variables.result @@ -20,66 +20,6 @@ SET GLOBAL wsrep_provider=none; # variables when using "_" # CALL mtr.add_suppression("WSREP: Could not open saved state file for reading.*"); -# wsrep -SHOW GLOBAL STATUS LIKE 'wsrep%'; -Variable_name Value -wsrep_applier_thread_count # -wsrep_apply_oooe # -wsrep_apply_oool # -wsrep_apply_window # -wsrep_causal_reads # -wsrep_cert_deps_distance # -wsrep_cert_index_size # -wsrep_cert_interval # -wsrep_cluster_conf_id # -wsrep_cluster_size # -wsrep_cluster_state_uuid # -wsrep_cluster_status # -wsrep_commit_oooe # -wsrep_commit_oool # -wsrep_commit_window # -wsrep_connected # -wsrep_debug_sync_waiters # -wsrep_flow_control_paused # -wsrep_flow_control_paused_ns # -wsrep_flow_control_recv # -wsrep_flow_control_sent # -wsrep_incoming_addresses # -wsrep_last_committed # -wsrep_local_bf_aborts # -wsrep_local_cached_downto # -wsrep_local_cert_failures # -wsrep_local_commits # -wsrep_local_index # -wsrep_local_recv_queue # -wsrep_local_recv_queue_avg # -wsrep_local_recv_queue_max # -wsrep_local_recv_queue_min # -wsrep_local_replays # -wsrep_local_send_queue # -wsrep_local_send_queue_avg # -wsrep_local_send_queue_max # -wsrep_local_send_queue_min # -wsrep_local_state # -wsrep_local_state_comment # -wsrep_local_state_uuid # -wsrep_open_connections # -wsrep_open_transactions # -wsrep_protocol_version # -wsrep_provider_name # -wsrep_provider_vendor # -wsrep_provider_version # -wsrep_ready # -wsrep_received # -wsrep_received_bytes # -wsrep_repl_data_bytes # -wsrep_repl_keys # -wsrep_repl_keys_bytes # -wsrep_repl_other_bytes # -wsrep_replicated # -wsrep_replicated_bytes # -wsrep_rollbacker_thread_count # -wsrep_thread_count # SHOW GLOBAL STATUS LIKE 'wsrep_local_state_comment'; Variable_name Value wsrep_local_state_comment # diff --git a/mysql-test/suite/wsrep/t/variables.test b/mysql-test/suite/wsrep/t/variables.test index 2556d8ede4f..5ab0eb68505 100644 --- a/mysql-test/suite/wsrep/t/variables.test +++ b/mysql-test/suite/wsrep/t/variables.test @@ -1,7 +1,6 @@ --source include/have_wsrep.inc --source include/force_restart.inc --source include/have_innodb.inc ---source include/galera_have_debug_sync.inc call mtr.add_suppression("WSREP: Initial position was provided by configuration or SST, avoiding override"); @@ -40,11 +39,6 @@ source include/check_galera_version.inc; --enable_result_log --enable_query_log ---echo # wsrep ---sorted_result ---replace_column 2 # -SHOW GLOBAL STATUS LIKE 'wsrep%'; - --replace_column 2 # SHOW GLOBAL STATUS LIKE 'wsrep_local_state_comment'; -- cgit v1.2.1 From 97f3207cf3d1a119fe3d6a56b204e5bf30cec109 Mon Sep 17 00:00:00 2001 From: Daniele Sciascia Date: Mon, 2 Nov 2020 14:18:49 +0100 Subject: Fix MTR test galera.galera_trigger MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changed the test so that it does not rely on specific auto increment ids. With Galera's default wsrep_auto_increment_control setting it is not guaranteed that auto increments always start from 1. The test was occasionally failing due to result content mismatch. Reviewed-by: Jan Lindström --- mysql-test/suite/galera/r/galera_trigger.result | 44 +++++++++++++++++++++++++ mysql-test/suite/galera/t/galera_trigger.test | 36 ++++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/mysql-test/suite/galera/r/galera_trigger.result b/mysql-test/suite/galera/r/galera_trigger.result index 112d3f39aa0..059f8ef13f9 100644 --- a/mysql-test/suite/galera/r/galera_trigger.result +++ b/mysql-test/suite/galera/r/galera_trigger.result @@ -30,3 +30,47 @@ id 200 DROP TRIGGER tr1; DROP TABLE t1; +connection node_1; +CREATE TABLE t1(id int not null auto_increment, value int not null, primary key (id)) engine=innodb; +CREATE TABLE t2(id int not null auto_increment, tbl varchar(64) not null, action varchar(64) not null, primary key (id)); +create trigger log_insert after insert on t1 +for each row begin +insert into t2(tbl, action) values ('t1', 'INSERT'); +end| +insert into t1(value) values (1); +insert into t1(value) values (2); +connection node_2; +set session wsrep_sync_wait=15; +insert into t1(value) values (3); +insert into t1(value) values (4); +select tbl, action from t2; +tbl action +t1 INSERT +t1 INSERT +t1 INSERT +t1 INSERT +connection node_1; +drop trigger if exists log_insert; +insert into t1(value) values (5); +select tbl, action from t2; +tbl action +t1 INSERT +t1 INSERT +t1 INSERT +t1 INSERT +connection node_2; +insert into t1(value) values (6); +select tbl, action from t2; +tbl action +t1 INSERT +t1 INSERT +t1 INSERT +t1 INSERT +connection node_1; +select tbl, action from t2; +tbl action +t1 INSERT +t1 INSERT +t1 INSERT +t1 INSERT +drop table t1, t2; diff --git a/mysql-test/suite/galera/t/galera_trigger.test b/mysql-test/suite/galera/t/galera_trigger.test index 54508583f4b..3c66b941e32 100644 --- a/mysql-test/suite/galera/t/galera_trigger.test +++ b/mysql-test/suite/galera/t/galera_trigger.test @@ -33,4 +33,40 @@ SELECT * FROM t1; DROP TRIGGER tr1; DROP TABLE t1; +# +# MDEV-23638 : DROP TRIGGER in Galera Cluster not replicating +# +--connection node_1 +CREATE TABLE t1(id int not null auto_increment, value int not null, primary key (id)) engine=innodb; + +CREATE TABLE t2(id int not null auto_increment, tbl varchar(64) not null, action varchar(64) not null, primary key (id)); + +--delimiter | +create trigger log_insert after insert on t1 +for each row begin + insert into t2(tbl, action) values ('t1', 'INSERT'); +end| +--delimiter ; + +insert into t1(value) values (1); +insert into t1(value) values (2); + +--connection node_2 +set session wsrep_sync_wait=15; +insert into t1(value) values (3); +insert into t1(value) values (4); +select tbl, action from t2; + +--connection node_1 +drop trigger if exists log_insert; +insert into t1(value) values (5); +select tbl, action from t2; + +--connection node_2 +insert into t1(value) values (6); +select tbl, action from t2; + +--connection node_1 +select tbl, action from t2; +drop table t1, t2; -- cgit v1.2.1 From 8ba641a6760075199f97d565f6634f4273091070 Mon Sep 17 00:00:00 2001 From: Daniel Bartholomew Date: Tue, 3 Nov 2020 10:56:50 -0500 Subject: bump the VERSION --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index a1f0ad08a61..b451aa8a789 100644 --- a/VERSION +++ b/VERSION @@ -1,3 +1,3 @@ MYSQL_VERSION_MAJOR=10 MYSQL_VERSION_MINOR=2 -MYSQL_VERSION_PATCH=35 +MYSQL_VERSION_PATCH=36 -- cgit v1.2.1 From 4548e74bcca1c97def4fbe44a28940d465f6555b Mon Sep 17 00:00:00 2001 From: Daniel Bartholomew Date: Tue, 3 Nov 2020 10:58:05 -0500 Subject: bump the VERSION --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 7f3afc3090f..1eee99cfc9f 100644 --- a/VERSION +++ b/VERSION @@ -1,4 +1,4 @@ MYSQL_VERSION_MAJOR=10 MYSQL_VERSION_MINOR=3 -MYSQL_VERSION_PATCH=26 +MYSQL_VERSION_PATCH=27 SERVER_MATURITY=stable -- cgit v1.2.1 From 5739c7702d83e83ecff5cdd84e0fab899101f9f5 Mon Sep 17 00:00:00 2001 From: Daniel Bartholomew Date: Tue, 3 Nov 2020 10:59:26 -0500 Subject: bump the VERSION --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index d4740047d0c..9de6839c534 100644 --- a/VERSION +++ b/VERSION @@ -1,4 +1,4 @@ MYSQL_VERSION_MAJOR=10 MYSQL_VERSION_MINOR=4 -MYSQL_VERSION_PATCH=16 +MYSQL_VERSION_PATCH=17 SERVER_MATURITY=stable -- cgit v1.2.1 From 4d6c6611443f1e0e1cdab34ac6e320031e7f980b Mon Sep 17 00:00:00 2001 From: sjaakola Date: Tue, 27 Oct 2020 12:45:42 +0200 Subject: MDEV-21577 MDL BF-BF conflict MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some DDL statements appear to acquire MDL locks for a table referenced by foreign key constraint from the actual affected table of the DDL statement. OPTIMIZE, REPAIR and ALTER TABLE belong to this class of DDL statements. Earlier MariaDB version did not take this in consideration, and appended only affected table in the certification key list in write set. Because of missing certification information, it could happen that e.g. OPTIMIZE table for FK child table could be allowed to apply in parallel with DML operating on the foreign key parent table, and this could lead to unhandled MDL lock conflicts between two high priority appliers (BF). The fix in this patch, changes the TOI replication for OPTIMIZE, REPAIR and ALTER TABLE statements so that before the execution of respective DDL statement, there is foreign key parent search round. This FK parent search contains following steps: * open and lock the affected table (with permissive shared locks) * iterate over foreign key contstraints and collect and array of Fk parent table names * close all tables open for the THD and release MDL locks * do the actual TOI replication with the affected table and FK parent table names as key values The patch contains also new mtr test for verifying that the above mentioned DDL statements replicate without problems when operating on FK child table. The mtr test scenario #1, which can be used to check if some other DDL (on top of OPTIMIZE, REPAIR and ALTER) could cause similar excessive FK parent table locking. Reviewed-by: Aleksey Midenkov Reviewed-by: Jan Lindström --- include/wsrep.h | 9 +- .../suite/galera/r/galera_ddl_fk_conflict.result | 216 +++++++++++++++++++++ .../suite/galera/t/galera_ddl_fk_conflict.inc | 114 +++++++++++ .../suite/galera/t/galera_ddl_fk_conflict.test | 25 +++ sql/sql_admin.cc | 65 +++++-- sql/sql_alter.cc | 30 +-- sql/wsrep_mysqld.cc | 64 +++++- sql/wsrep_mysqld.h | 6 +- 8 files changed, 495 insertions(+), 34 deletions(-) create mode 100644 mysql-test/suite/galera/r/galera_ddl_fk_conflict.result create mode 100644 mysql-test/suite/galera/t/galera_ddl_fk_conflict.inc create mode 100644 mysql-test/suite/galera/t/galera_ddl_fk_conflict.test diff --git a/include/wsrep.h b/include/wsrep.h index fde5c5226e7..65cceba1065 100644 --- a/include/wsrep.h +++ b/include/wsrep.h @@ -27,10 +27,10 @@ if (WSREP_ON && WSREP(thd) && wsrep_to_isolation_begin(thd, db_, table_, table_list_)) \ goto wsrep_error_label; -#define WSREP_TO_ISOLATION_BEGIN_ALTER(db_, table_, table_list_, alter_info_) \ +#define WSREP_TO_ISOLATION_BEGIN_ALTER(db_, table_, table_list_, alter_info_, fk_tables_) \ if (WSREP(thd) && wsrep_thd_is_local(thd) && \ wsrep_to_isolation_begin(thd, db_, table_, \ - table_list_, alter_info_)) \ + table_list_, alter_info_, fk_tables_)) \ goto wsrep_error_label; #define WSREP_TO_ISOLATION_END \ @@ -46,6 +46,10 @@ if (WSREP(thd) && !thd->lex->no_write_to_binlog \ && wsrep_to_isolation_begin(thd, db_, table_, table_list_)) goto wsrep_error_label; +#define WSREP_TO_ISOLATION_BEGIN_FK_TABLES(db_, table_, table_list_, fk_tables) \ + if (WSREP(thd) && !thd->lex->no_write_to_binlog \ + && wsrep_to_isolation_begin(thd, db_, table_, table_list_, NULL, fk_tables)) goto wsrep_error_label; + #define WSREP_DEBUG(...) \ if (wsrep_debug) WSREP_LOG(sql_print_information, ##__VA_ARGS__) #define WSREP_INFO(...) WSREP_LOG(sql_print_information, ##__VA_ARGS__) @@ -69,6 +73,7 @@ #define WSREP_ERROR(...) #define WSREP_TO_ISOLATION_BEGIN(db_, table_, table_list_) do { } while(0) #define WSREP_TO_ISOLATION_BEGIN_ALTER(db_, table_, table_list_, alter_info_) +#define WSREP_TO_ISOLATION_BEGIN_FK_TABLES(db_, table_, table_list_, fk_tables_) #define WSREP_TO_ISOLATION_END #define WSREP_TO_ISOLATION_BEGIN_WRTCHK(db_, table_, table_list_) #define WSREP_SYNC_WAIT(thd_, before_) diff --git a/mysql-test/suite/galera/r/galera_ddl_fk_conflict.result b/mysql-test/suite/galera/r/galera_ddl_fk_conflict.result new file mode 100644 index 00000000000..a39fa6a6219 --- /dev/null +++ b/mysql-test/suite/galera/r/galera_ddl_fk_conflict.result @@ -0,0 +1,216 @@ +connection node_2; +connection node_1; +connect node_1a, 127.0.0.1, root, , test, $NODE_MYPORT_1; +connection node_1a; +SET SESSION wsrep_sync_wait=0; +###################################################################### +# Test for OPTIMIZE +###################################################################### +###################################################################### +# +# Scenario #1: DML working on FK parent table BF aborted by DDL +# over child table +# +###################################################################### +connection node_1; +SET SESSION wsrep_sync_wait=0; +CREATE TABLE p (pk INTEGER PRIMARY KEY, f2 CHAR(30)); +INSERT INTO p VALUES (1, 'INITIAL VALUE'); +INSERT INTO p VALUES (2, 'INITIAL VALUE'); +CREATE TABLE c (pk INTEGER PRIMARY KEY, fk INTEGER, FOREIGN KEY (fk) REFERENCES p(pk)); +INSERT INTO c VALUES (1,1), (2,2); +connection node_1; +SET AUTOCOMMIT=ON; +START TRANSACTION; +UPDATE p SET f2 = 'TO DEADLOCK' WHERE pk = 1; +connection node_2; +SET SESSION wsrep_sync_wait=0; +OPTIMIZE TABLE c ; +Table Op Msg_type Msg_text +test.c optimize note Table does not support optimize, doing recreate + analyze instead +test.c optimize status OK +connection node_1; +COMMIT; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +SELECT COUNT(*) AS EXPECT_2 FROM p WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +connection node_2; +SELECT COUNT(*) AS EXPECT_2 FROM p WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +###################################################################### +# +# Scenario #2: DML working on FK parent table tries to replicate, but +# fails in certification for earlier DDL on child table +# +###################################################################### +connection node_1; +BEGIN; +SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; +connection node_2; +OPTIMIZE TABLE c ; +Table Op Msg_type Msg_text +test.c optimize note Table does not support optimize, doing recreate + analyze instead +test.c optimize status OK +connection node_1a; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +connection node_1; +UPDATE p SET f2 = 'TO DEADLOCK' WHERE pk = 1; +COMMIT; +connection node_1a; +SET GLOBAL wsrep_provider_options = 'signal=apply_monitor_slave_enter_sync'; +connection node_1; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +SELECT 'I deadlocked'; +I deadlocked +I deadlocked +SELECT COUNT(*) AS EXPECT_2 FROM p WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +connection node_2; +SELECT COUNT(*) AS EXPECT_2 FROM p WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +DROP TABLE c; +DROP TABLE p; +###################################################################### +# Test for REPAIR +###################################################################### +###################################################################### +# +# Scenario #1: DML working on FK parent table BF aborted by DDL +# over child table +# +###################################################################### +connection node_1; +SET SESSION wsrep_sync_wait=0; +CREATE TABLE p (pk INTEGER PRIMARY KEY, f2 CHAR(30)); +INSERT INTO p VALUES (1, 'INITIAL VALUE'); +INSERT INTO p VALUES (2, 'INITIAL VALUE'); +CREATE TABLE c (pk INTEGER PRIMARY KEY, fk INTEGER, FOREIGN KEY (fk) REFERENCES p(pk)); +INSERT INTO c VALUES (1,1), (2,2); +connection node_1; +SET AUTOCOMMIT=ON; +START TRANSACTION; +UPDATE p SET f2 = 'TO DEADLOCK' WHERE pk = 1; +connection node_2; +SET SESSION wsrep_sync_wait=0; +REPAIR TABLE c ; +Table Op Msg_type Msg_text +test.c repair note The storage engine for the table doesn't support repair +connection node_1; +COMMIT; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +SELECT COUNT(*) AS EXPECT_2 FROM p WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +connection node_2; +SELECT COUNT(*) AS EXPECT_2 FROM p WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +###################################################################### +# +# Scenario #2: DML working on FK parent table tries to replicate, but +# fails in certification for earlier DDL on child table +# +###################################################################### +connection node_1; +BEGIN; +SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; +connection node_2; +REPAIR TABLE c ; +Table Op Msg_type Msg_text +test.c repair note The storage engine for the table doesn't support repair +connection node_1a; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +connection node_1; +UPDATE p SET f2 = 'TO DEADLOCK' WHERE pk = 1; +COMMIT; +connection node_1a; +SET GLOBAL wsrep_provider_options = 'signal=apply_monitor_slave_enter_sync'; +connection node_1; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +SELECT 'I deadlocked'; +I deadlocked +I deadlocked +SELECT COUNT(*) AS EXPECT_2 FROM p WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +connection node_2; +SELECT COUNT(*) AS EXPECT_2 FROM p WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +DROP TABLE c; +DROP TABLE p; +###################################################################### +# Test for ALTER ENGINE=INNODB +###################################################################### +###################################################################### +# +# Scenario #1: DML working on FK parent table BF aborted by DDL +# over child table +# +###################################################################### +connection node_1; +SET SESSION wsrep_sync_wait=0; +CREATE TABLE p (pk INTEGER PRIMARY KEY, f2 CHAR(30)); +INSERT INTO p VALUES (1, 'INITIAL VALUE'); +INSERT INTO p VALUES (2, 'INITIAL VALUE'); +CREATE TABLE c (pk INTEGER PRIMARY KEY, fk INTEGER, FOREIGN KEY (fk) REFERENCES p(pk)); +INSERT INTO c VALUES (1,1), (2,2); +connection node_1; +SET AUTOCOMMIT=ON; +START TRANSACTION; +UPDATE p SET f2 = 'TO DEADLOCK' WHERE pk = 1; +connection node_2; +SET SESSION wsrep_sync_wait=0; +ALTER TABLE c ENGINE=INNODB; +connection node_1; +COMMIT; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +SELECT COUNT(*) AS EXPECT_2 FROM p WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +connection node_2; +SELECT COUNT(*) AS EXPECT_2 FROM p WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +###################################################################### +# +# Scenario #2: DML working on FK parent table tries to replicate, but +# fails in certification for earlier DDL on child table +# +###################################################################### +connection node_1; +BEGIN; +SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; +connection node_2; +ALTER TABLE c ENGINE=INNODB; +connection node_1a; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +connection node_1; +UPDATE p SET f2 = 'TO DEADLOCK' WHERE pk = 1; +COMMIT; +connection node_1a; +SET GLOBAL wsrep_provider_options = 'signal=apply_monitor_slave_enter_sync'; +connection node_1; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +SELECT 'I deadlocked'; +I deadlocked +I deadlocked +SELECT COUNT(*) AS EXPECT_2 FROM p WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +connection node_2; +SELECT COUNT(*) AS EXPECT_2 FROM p WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +DROP TABLE c; +DROP TABLE p; diff --git a/mysql-test/suite/galera/t/galera_ddl_fk_conflict.inc b/mysql-test/suite/galera/t/galera_ddl_fk_conflict.inc new file mode 100644 index 00000000000..18271d0b641 --- /dev/null +++ b/mysql-test/suite/galera/t/galera_ddl_fk_conflict.inc @@ -0,0 +1,114 @@ +# +# Test for MDL BF-BF lock conflict +# There are some DDL statements, which take extensive MDL lock for +# a table referenced by foreign key constraint from the actual affetec table. +# This extensive MDL lock may cause MDL BF-BF confclict situations, if the +# FK parent table is not listed as certification key in the replication write set. +# i.e. if replication allows such DDL to apply in parallel with regular DML operating +# on the FK parent table. +# +# This test has two scenarios, where DML modifies FK parent table in node 1, +# and offending DDL for FK child table is sent from node 2. +# +# param: $table_admin_command +# DDL table command to test, script will build full SQL statement: +# $table_admin_command TABLE c; +# +# param: $table_admin_command_end +# Optional additional SQL syntax to end the SQL statement, if any +# $table_admin_command TABLE c $table_admin_command_end; +# +# scenario 1, can be used to test if a DDL statement causes such MDL locking vulnerability. +# call this test script with some table DDL command in $table_admin_command +# if scenario 1 passes (especially COMMIT does fail for ER_LOCK_DEADLOCK), +# then this particular DDL is vulnerable. scenraio 2 should fail for this DDL +# unless code has not been fixed to append parent table certification keys for it. +# + +--echo ###################################################################### +--echo # Test for $table_admin_command $table_admin_command_end +--echo ###################################################################### + + +--echo ###################################################################### +--echo # +--echo # Scenario #1: DML working on FK parent table BF aborted by DDL +--echo # over child table +--echo # +--echo ###################################################################### + +--connection node_1 +SET SESSION wsrep_sync_wait=0; + +CREATE TABLE p (pk INTEGER PRIMARY KEY, f2 CHAR(30)); +INSERT INTO p VALUES (1, 'INITIAL VALUE'); +INSERT INTO p VALUES (2, 'INITIAL VALUE'); + +CREATE TABLE c (pk INTEGER PRIMARY KEY, fk INTEGER, FOREIGN KEY (fk) REFERENCES p(pk)); +INSERT INTO c VALUES (1,1), (2,2); + +--connection node_1 +SET AUTOCOMMIT=ON; +START TRANSACTION; + +UPDATE p SET f2 = 'TO DEADLOCK' WHERE pk = 1; + +--connection node_2 +SET SESSION wsrep_sync_wait=0; +--eval $table_admin_command TABLE c $table_admin_command_end + +--connection node_1 +--error ER_LOCK_DEADLOCK +COMMIT; + +SELECT COUNT(*) AS EXPECT_2 FROM p WHERE f2 = 'INITIAL VALUE'; + +--connection node_2 +SELECT COUNT(*) AS EXPECT_2 FROM p WHERE f2 = 'INITIAL VALUE'; + +--echo ###################################################################### +--echo # +--echo # Scenario #2: DML working on FK parent table tries to replicate, but +--echo # fails in certification for earlier DDL on child table +--echo # +--echo ###################################################################### + +--connection node_1 +BEGIN; + +# Block the applier on node #1 and issue DDL on node 2 +--let $galera_sync_point = apply_monitor_slave_enter_sync +--source include/galera_set_sync_point.inc + +--connection node_2 +--eval $table_admin_command TABLE c $table_admin_command_end + +--connection node_1a +--source include/galera_wait_sync_point.inc +--source include/galera_clear_sync_point.inc +--let $expected_cert_failures = `SELECT VARIABLE_VALUE+1 FROM information_schema.global_status WHERE VARIABLE_NAME = 'wsrep_local_cert_failures'` + +--connection node_1 +UPDATE p SET f2 = 'TO DEADLOCK' WHERE pk = 1; +--send COMMIT + +--connection node_1a +--let $galera_sync_point = apply_monitor_slave_enter_sync +--source include/galera_signal_sync_point.inc + +--let $wait_condition = SELECT VARIABLE_VALUE = $expected_cert_failures FROM information_schema.global_status WHERE VARIABLE_NAME = 'wsrep_local_cert_failures' +--source include/wait_condition.inc + +--connection node_1 +--error ER_LOCK_DEADLOCK +--reap + +SELECT 'I deadlocked'; + +SELECT COUNT(*) AS EXPECT_2 FROM p WHERE f2 = 'INITIAL VALUE'; + +--connection node_2 +SELECT COUNT(*) AS EXPECT_2 FROM p WHERE f2 = 'INITIAL VALUE'; + +DROP TABLE c; +DROP TABLE p; diff --git a/mysql-test/suite/galera/t/galera_ddl_fk_conflict.test b/mysql-test/suite/galera/t/galera_ddl_fk_conflict.test new file mode 100644 index 00000000000..a07006a8e49 --- /dev/null +++ b/mysql-test/suite/galera/t/galera_ddl_fk_conflict.test @@ -0,0 +1,25 @@ +# +# MDL BF-BF lock conflict +# + +--source include/galera_cluster.inc +--source include/have_innodb.inc +--source include/have_debug_sync.inc +--source include/galera_have_debug_sync.inc + +--connect node_1a, 127.0.0.1, root, , test, $NODE_MYPORT_1 +--connection node_1a +SET SESSION wsrep_sync_wait=0; + +--let $table_admin_command = OPTIMIZE +--source galera_ddl_fk_conflict.inc + +--let $table_admin_command = REPAIR +--source galera_ddl_fk_conflict.inc + +--let $table_admin_command = ALTER +--let $table_admin_command_end = ENGINE=INNODB +--source galera_ddl_fk_conflict.inc + +# CHECK and ANALYZE are not affected + diff --git a/sql/sql_admin.cc b/sql/sql_admin.cc index 6201411d4aa..ebd0e9b39aa 100644 --- a/sql/sql_admin.cc +++ b/sql/sql_admin.cc @@ -32,7 +32,7 @@ #include "strfunc.h" #include "sql_admin.h" #include "sql_statistics.h" - +#include "wsrep_mysqld.h" /* Prepare, run and cleanup for mysql_recreate_table() */ static bool admin_recreate_table(THD *thd, TABLE_LIST *table_list) @@ -419,6 +419,48 @@ dbug_err: return open_error; } +#ifdef WITH_WSREP + /* + OPTIMIZE, REPAIR and ALTER may take MDL locks not only for the affected table, but + also for the table referenced by foreign key constraint. + This wsrep_toi_replication() function handles TOI replication for OPTIMIZE and REPAIR + so that certification keys for potential FK parent tables are also appended in the + write set. + ALTER TABLE case is handled elsewhere. + */ +static bool wsrep_toi_replication(THD *thd, TABLE_LIST *tables) +{ + if (!WSREP(thd) || !WSREP_CLIENT(thd)) return false; + + LEX *lex= thd->lex; + /* only handle OPTIMIZE and REPAIR here */ + switch (lex->sql_command) + { + case SQLCOM_OPTIMIZE: + case SQLCOM_REPAIR: + break; + default: + return false; + } + + close_thread_tables(thd); + wsrep::key_array keys; + + wsrep_append_fk_parent_table(thd, tables, &keys); + + /* now TOI replication, with no locks held */ + if (keys.empty()) + { + WSREP_TO_ISOLATION_BEGIN_WRTCHK(NULL, NULL, tables); + } else { + WSREP_TO_ISOLATION_BEGIN_FK_TABLES(NULL, NULL, tables, &keys); + } + return false; + + wsrep_error_label: + return true; +} +#endif /* WITH_WSREP */ /* RETURN VALUES @@ -487,6 +529,13 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, close_thread_tables(thd); for (table= tables; table; table= table->next_local) table->table= NULL; +#ifdef WITH_WSREP + if (wsrep_toi_replication(thd, tables)) + { + WSREP_INFO("wsrep TOI replication of has failed, skipping OPTIMIZE"); + goto err; + } +#endif /* WITH_WSREP */ for (table= tables; table; table= table->next_local) { @@ -1333,10 +1382,10 @@ bool Sql_cmd_analyze_table::execute(THD *thd) m_lex->first_select_lex()->table_list.first= first_table; m_lex->query_tables= first_table; -error: #ifdef WITH_WSREP -wsrep_error_label: -#endif + wsrep_error_label: +#endif /* WITH_WSREP */ +error: DBUG_RETURN(res); } @@ -1375,7 +1424,6 @@ bool Sql_cmd_optimize_table::execute(THD *thd) if (check_table_access(thd, SELECT_ACL | INSERT_ACL, first_table, FALSE, UINT_MAX, FALSE)) goto error; /* purecov: inspected */ - WSREP_TO_ISOLATION_BEGIN_WRTCHK(NULL, NULL, first_table); res= (specialflag & SPECIAL_NO_NEW_FUNC) ? mysql_recreate_table(thd, first_table, true) : @@ -1394,9 +1442,6 @@ bool Sql_cmd_optimize_table::execute(THD *thd) m_lex->query_tables= first_table; error: -#ifdef WITH_WSREP -wsrep_error_label: -#endif DBUG_RETURN(res); } @@ -1411,7 +1456,6 @@ bool Sql_cmd_repair_table::execute(THD *thd) if (check_table_access(thd, SELECT_ACL | INSERT_ACL, first_table, FALSE, UINT_MAX, FALSE)) goto error; /* purecov: inspected */ - WSREP_TO_ISOLATION_BEGIN_WRTCHK(NULL, NULL, first_table); res= mysql_admin_table(thd, first_table, &m_lex->check_opt, "repair", TL_WRITE, 1, MY_TEST(m_lex->check_opt.sql_flags & TT_USEFRM), @@ -1430,8 +1474,5 @@ bool Sql_cmd_repair_table::execute(THD *thd) m_lex->query_tables= first_table; error: -#ifdef WITH_WSREP -wsrep_error_label: -#endif DBUG_RETURN(res); } diff --git a/sql/sql_alter.cc b/sql/sql_alter.cc index 21c79a046a5..cefd37811a2 100644 --- a/sql/sql_alter.cc +++ b/sql/sql_alter.cc @@ -470,6 +470,22 @@ bool Sql_cmd_alter_table::execute(THD *thd) if (check_grant(thd, priv_needed, first_table, FALSE, UINT_MAX, FALSE)) DBUG_RETURN(TRUE); /* purecov: inspected */ +#ifdef WITH_WSREP + if (WSREP(thd) && WSREP_CLIENT(thd) && + (!thd->is_current_stmt_binlog_format_row() || + !thd->find_temporary_table(first_table))) + { + wsrep::key_array keys; + wsrep_append_fk_parent_table(thd, first_table, &keys); + + WSREP_TO_ISOLATION_BEGIN_ALTER((lex->name.str ? select_lex->db.str : NULL), + (lex->name.str ? lex->name.str : NULL), + first_table, &alter_info, &keys); + + thd->variables.auto_increment_offset = 1; + thd->variables.auto_increment_increment = 1; + } +#endif if (lex->name.str && !test_all_bits(priv, INSERT_ACL | CREATE_ACL)) { @@ -497,20 +513,6 @@ bool Sql_cmd_alter_table::execute(THD *thd) thd->work_part_info= 0; #endif -#ifdef WITH_WSREP - if (WSREP(thd) && - (!thd->is_current_stmt_binlog_format_row() || - !thd->find_temporary_table(first_table))) - { - WSREP_TO_ISOLATION_BEGIN_ALTER((lex->name.str ? select_lex->db.str : NULL), - (lex->name.str ? lex->name.str : NULL), - first_table, &alter_info); - - thd->variables.auto_increment_offset = 1; - thd->variables.auto_increment_increment = 1; - } -#endif - result= mysql_alter_table(thd, &select_lex->db, &lex->name, &create_info, first_table, diff --git a/sql/wsrep_mysqld.cc b/sql/wsrep_mysqld.cc index aaf17012118..304da7ec979 100644 --- a/sql/wsrep_mysqld.cc +++ b/sql/wsrep_mysqld.cc @@ -1181,6 +1181,54 @@ void wsrep_keys_free(wsrep_key_arr_t* key_arr) key_arr->keys_len= 0; } +void +wsrep_append_fk_parent_table(THD* thd, TABLE_LIST* tables, wsrep::key_array* keys) +{ + if (!WSREP(thd) || !WSREP_CLIENT(thd)) return; + TABLE_LIST *table; + + thd->mdl_context.release_transactional_locks(); + uint counter; + MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint(); + + bool open_error= + open_tables(thd, &tables, &counter, MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL); + if (unlikely(open_error && (thd->killed || thd->is_error()))) + { + WSREP_WARN("unable to open table for FK checks in OPTIMIZE/REPAIR/ALTER processing"); + } + else + { + for (table= tables; table; table= table->next_local) + { + if (table->table) + { + FOREIGN_KEY_INFO *f_key_info; + List f_key_list; + + table->table->file->get_foreign_key_list(thd, &f_key_list); + List_iterator_fast it(f_key_list); + while ((f_key_info=it++)) + { + WSREP_DEBUG("appended fkey %s", f_key_info->referenced_table->str); + keys->push_back(wsrep_prepare_key_for_toi(f_key_info->referenced_db->str, + f_key_info->referenced_table->str, + wsrep::key::shared)); + } + } + } + } + + /* close the table and release MDL locks */ + close_thread_tables(thd); + thd->mdl_context.rollback_to_savepoint(mdl_savepoint); + for (table= tables; table; table= table->next_local) + { + table->table= NULL; + table->mdl_request.ticket= NULL; + } +} + /*! * @param db Database string * @param table Table string @@ -1434,7 +1482,8 @@ wsrep_prepare_keys_for_alter_add_fk(const char* child_table_db, wsrep::key_array wsrep_prepare_keys_for_toi(const char* db, const char* table, const TABLE_LIST* table_list, - Alter_info* alter_info) + Alter_info* alter_info, + wsrep::key_array* fk_tables) { wsrep::key_array ret; if (db || table) @@ -1454,8 +1503,13 @@ wsrep::key_array wsrep_prepare_keys_for_toi(const char* db, ret.insert(ret.end(), fk.begin(), fk.end()); } } + if (fk_tables && !fk_tables->empty()) + { + ret.insert(ret.end(), fk_tables->begin(), fk_tables->end()); + } return ret; } + /* * Construct Query_log_Event from thd query and serialize it * into buffer. @@ -1907,7 +1961,7 @@ fail: */ static int wsrep_TOI_begin(THD *thd, const char *db, const char *table, const TABLE_LIST* table_list, - Alter_info* alter_info) + Alter_info* alter_info, wsrep::key_array* fk_tables) { DBUG_ASSERT(thd->variables.wsrep_OSU_method == WSREP_OSU_TOI); @@ -1935,7 +1989,7 @@ static int wsrep_TOI_begin(THD *thd, const char *db, const char *table, struct wsrep_buf buff= { buf, buf_len }; wsrep::key_array key_array= - wsrep_prepare_keys_for_toi(db, table, table_list, alter_info); + wsrep_prepare_keys_for_toi(db, table, table_list, alter_info, fk_tables); if (thd->has_read_only_protection()) { @@ -2048,7 +2102,7 @@ static void wsrep_RSU_end(THD *thd) int wsrep_to_isolation_begin(THD *thd, const char *db_, const char *table_, const TABLE_LIST* table_list, - Alter_info* alter_info) + Alter_info* alter_info, wsrep::key_array* fk_tables) { /* No isolation for applier or replaying threads. @@ -2100,7 +2154,7 @@ int wsrep_to_isolation_begin(THD *thd, const char *db_, const char *table_, { switch (thd->variables.wsrep_OSU_method) { case WSREP_OSU_TOI: - ret= wsrep_TOI_begin(thd, db_, table_, table_list, alter_info); + ret= wsrep_TOI_begin(thd, db_, table_, table_list, alter_info, fk_tables); break; case WSREP_OSU_RSU: ret= wsrep_RSU_begin(thd, db_, table_); diff --git a/sql/wsrep_mysqld.h b/sql/wsrep_mysqld.h index a717caa836e..b0050a2ebae 100644 --- a/sql/wsrep_mysqld.h +++ b/sql/wsrep_mysqld.h @@ -212,6 +212,7 @@ wsrep_sync_wait_upto (THD* thd, wsrep_gtid_t* upto, int timeout); extern void wsrep_last_committed_id (wsrep_gtid_t* gtid); extern int wsrep_check_opts(); extern void wsrep_prepend_PATH (const char* path); +void wsrep_append_fk_parent_table(THD* thd, TABLE_LIST* table, wsrep::key_array* keys); /* Other global variables */ extern wsrep_seqno_t wsrep_locked_seqno; @@ -357,7 +358,7 @@ struct TABLE_LIST; class Alter_info; int wsrep_to_isolation_begin(THD *thd, const char *db_, const char *table_, const TABLE_LIST* table_list, - Alter_info* alter_info= NULL); + Alter_info* alter_info= NULL, wsrep::key_array *fk_tables=NULL); void wsrep_to_isolation_end(THD *thd); @@ -476,6 +477,9 @@ void wsrep_deinit_server(); */ enum wsrep::streaming_context::fragment_unit wsrep_fragment_unit(ulong unit); +wsrep::key wsrep_prepare_key_for_toi(const char* db, const char* table, + enum wsrep::key::type type); + #else /* !WITH_WSREP */ /* These macros are needed to compile MariaDB without WSREP support -- cgit v1.2.1 From 1f1fa07cd5de1026205b797f205b913eb13e6862 Mon Sep 17 00:00:00 2001 From: Daniele Sciascia Date: Fri, 30 Oct 2020 13:45:27 +0100 Subject: MDEV-24063 Assertion during graceful shutdown with wsrep_on=OFF MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit During graceful shutdowns, client connections are closed and eventually and THD::awake() acquires LOCK_thd_data mutex which is required later on in wsrep_thd_is_aborting(). Make sure LOCK_thd_data is acquired, even if global wsrep_on is disabled. Reviewed-by: Jan Lindström --- mysql-test/suite/galera/r/MDEV-24063.result | 8 ++++++++ mysql-test/suite/galera/t/MDEV-24063.test | 20 ++++++++++++++++++++ sql/sql_class.h | 2 +- 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 mysql-test/suite/galera/r/MDEV-24063.result create mode 100644 mysql-test/suite/galera/t/MDEV-24063.test diff --git a/mysql-test/suite/galera/r/MDEV-24063.result b/mysql-test/suite/galera/r/MDEV-24063.result new file mode 100644 index 00000000000..757cc07a642 --- /dev/null +++ b/mysql-test/suite/galera/r/MDEV-24063.result @@ -0,0 +1,8 @@ +connection node_2; +connection node_1; +connect node_2a, 127.0.0.1, root, , test, $NODE_MYPORT_2; +connection node_2a; +CREATE TABLE t1 (f1 INTEGER PRIMARY KEY); +connection node_2; +SET GLOBAL wsrep_on=OFF; +DROP TABLE t1; diff --git a/mysql-test/suite/galera/t/MDEV-24063.test b/mysql-test/suite/galera/t/MDEV-24063.test new file mode 100644 index 00000000000..24c5071cb15 --- /dev/null +++ b/mysql-test/suite/galera/t/MDEV-24063.test @@ -0,0 +1,20 @@ +# +# MDEV-24063 +# +# my_bool wsrep_thd_is_aborting(const THD*): +# Assertion `((&(&thd->LOCK_thd_data)->m_mutex)->count > 0 && +# pthread_equal(pthread_self(), (&(&thd->LOCK_thd_data)->m_mutex)->thread))' failed. +# + +--source include/galera_cluster.inc + +--connect node_2a, 127.0.0.1, root, , test, $NODE_MYPORT_2 +--connection node_2a +CREATE TABLE t1 (f1 INTEGER PRIMARY KEY); + +--connection node_2 +SET GLOBAL wsrep_on=OFF; +--source include/shutdown_mysqld.inc +--source include/start_mysqld.inc + +DROP TABLE t1; diff --git a/sql/sql_class.h b/sql/sql_class.h index 8c479db8936..41b69742834 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -3308,7 +3308,7 @@ public: void awake_no_mutex(killed_state state_to_set); void awake(killed_state state_to_set) { - bool wsrep_on_local= WSREP_NNULL(this); + bool wsrep_on_local= variables.wsrep_on; /* mutex locking order (LOCK_thd_data - LOCK_thd_kill)) requires to grab LOCK_thd_data here -- cgit v1.2.1 From 0f04f61395b642d429aaa6a0372061b0d996a6b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Lindstr=C3=B6m?= Date: Wed, 4 Nov 2020 09:28:02 +0200 Subject: Need more information about mysql-wsrep#198 sporadic test failure. --- mysql-test/suite/galera/t/mysql-wsrep#198-master.opt | 1 - mysql-test/suite/galera/t/mysql-wsrep#198.cnf | 9 +++++++++ mysql-test/suite/galera/t/mysql-wsrep#198.test | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) delete mode 100644 mysql-test/suite/galera/t/mysql-wsrep#198-master.opt create mode 100644 mysql-test/suite/galera/t/mysql-wsrep#198.cnf diff --git a/mysql-test/suite/galera/t/mysql-wsrep#198-master.opt b/mysql-test/suite/galera/t/mysql-wsrep#198-master.opt deleted file mode 100644 index beae84b3862..00000000000 --- a/mysql-test/suite/galera/t/mysql-wsrep#198-master.opt +++ /dev/null @@ -1 +0,0 @@ ---log-bin diff --git a/mysql-test/suite/galera/t/mysql-wsrep#198.cnf b/mysql-test/suite/galera/t/mysql-wsrep#198.cnf new file mode 100644 index 00000000000..d599f3940e4 --- /dev/null +++ b/mysql-test/suite/galera/t/mysql-wsrep#198.cnf @@ -0,0 +1,9 @@ +!include ../galera_2nodes.cnf + +[mysqld.1] +log-bin +wsrep-debug=1 + +[mysqld.1] +log-bin +wsrep-debug=1 diff --git a/mysql-test/suite/galera/t/mysql-wsrep#198.test b/mysql-test/suite/galera/t/mysql-wsrep#198.test index a80d030a8b0..a8190f2ae61 100644 --- a/mysql-test/suite/galera/t/mysql-wsrep#198.test +++ b/mysql-test/suite/galera/t/mysql-wsrep#198.test @@ -1,5 +1,6 @@ --source include/galera_cluster.inc --source include/have_innodb.inc +--source include/force_restart.inc CREATE TABLE t1 (id INT PRIMARY KEY) ENGINE=InnoDB; CREATE TABLE t2 (id INT PRIMARY KEY) ENGINE=InnoDB; -- cgit v1.2.1 From b13fe8e51bd356299f58a07fecc589b3b3c4937b Mon Sep 17 00:00:00 2001 From: Nayuta Yanagisawa Date: Fri, 16 Oct 2020 14:33:22 +0000 Subject: MDEV-18842: Unfortunate error message when the same column is used for application period start and end An application-time period must be composed of two different columns. We added a check that ensures that the above condition is met. --- mysql-test/suite/period/r/create.result | 4 ++++ mysql-test/suite/period/t/create.test | 6 ++++++ sql/sql_lex.h | 6 ++++++ 3 files changed, 16 insertions(+) diff --git a/mysql-test/suite/period/r/create.result b/mysql-test/suite/period/r/create.result index 8cedb23465d..da4cf998918 100644 --- a/mysql-test/suite/period/r/create.result +++ b/mysql-test/suite/period/r/create.result @@ -45,6 +45,10 @@ ERROR 42000: Incorrect column specifier for column 's' create or replace table t (id int primary key, s date, e date, period for mytime(s,x)); ERROR 42S22: Unknown column 'x' in 'mytime' +# MDEV-18842: Unfortunate error message when the same column is used +# for application period start and end +create or replace table t (s date, t date, period for apt(s,s)); +ERROR 42000: Column 's' specified twice create or replace table t (id int primary key, s date, e date, period for mytime(s,e), period for mytime2(s,e)); diff --git a/mysql-test/suite/period/t/create.test b/mysql-test/suite/period/t/create.test index 2e3de795698..49dcc6ad3c7 100644 --- a/mysql-test/suite/period/t/create.test +++ b/mysql-test/suite/period/t/create.test @@ -31,6 +31,12 @@ create or replace table t (id int primary key, s time, e time, --error ER_BAD_FIELD_ERROR create or replace table t (id int primary key, s date, e date, period for mytime(s,x)); + +--echo # MDEV-18842: Unfortunate error message when the same column is used +--echo # for application period start and end +--error ER_FIELD_SPECIFIED_TWICE +create or replace table t (s date, t date, period for apt(s,s)); + --error ER_MORE_THAN_ONE_PERIOD create or replace table t (id int primary key, s date, e date, period for mytime(s,e), diff --git a/sql/sql_lex.h b/sql/sql_lex.h index c8fca748b77..66c44f2d901 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -4361,6 +4361,12 @@ public: int add_period(Lex_ident name, Lex_ident_sys_st start, Lex_ident_sys_st end) { + if (lex_string_cmp(system_charset_info, &start, &end) == 0) + { + my_error(ER_FIELD_SPECIFIED_TWICE, MYF(0), start.str); + return 1; + } + Table_period_info &info= create_info.period_info; if (check_exists && info.name.streq(name)) -- cgit v1.2.1 From fd7569ea6b3ca283ac1afc7da4170ac8c2411da9 Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Wed, 4 Nov 2020 13:39:38 +1100 Subject: MDEV-24098: SHOW CREATE USER invalid for both PASSWORD and LOCKED The parser of CREATE USER accepts ACCOUNT LOCK before PASSWORD EXPIRE but not the other way around. This just changes the SHOW CREATE USER to output a sql syntax that is valid. Thanks to Robert Bindar for analysis. --- mysql-test/main/lock_user.result | 8 ++++++++ mysql-test/main/lock_user.test | 7 +++++++ sql/sql_acl.cc | 6 +++--- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/mysql-test/main/lock_user.result b/mysql-test/main/lock_user.result index 5048c5a9bee..a0d2b40572c 100644 --- a/mysql-test/main/lock_user.result +++ b/mysql-test/main/lock_user.result @@ -130,5 +130,13 @@ connection default; # alter user user1@localhost account lock; ERROR HY000: Access denied, this account is locked +# +# MDEV-24098 SHOW CREATE USER invalid for both PASSWORD EXPIRE and +# and LOCKED +# +alter user user1@localhost PASSWORD EXPIRE; +show create user user1@localhost; +CREATE USER for user1@localhost +CREATE USER `user1`@`localhost` ACCOUNT LOCK PASSWORD EXPIRE drop user user1@localhost; drop user user2@localhost; diff --git a/mysql-test/main/lock_user.test b/mysql-test/main/lock_user.test index 17ce1cc79da..504c6c5ac10 100644 --- a/mysql-test/main/lock_user.test +++ b/mysql-test/main/lock_user.test @@ -137,6 +137,13 @@ alter user user1@localhost account lock; --error ER_ACCOUNT_HAS_BEEN_LOCKED --change_user user1 +--echo # +--echo # MDEV-24098 SHOW CREATE USER invalid for both PASSWORD EXPIRE and +--echo # and LOCKED +--echo # +alter user user1@localhost PASSWORD EXPIRE; +show create user user1@localhost; + drop user user1@localhost; drop user user2@localhost; diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index 86cea04f91e..57e8411b084 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -8950,6 +8950,9 @@ bool mysql_show_create_user(THD *thd, LEX_USER *lex_user) append_identifier(thd, &result, username, strlen(username)); add_user_parameters(thd, &result, acl_user, false); + if (acl_user->account_locked) + result.append(STRING_WITH_LEN(" ACCOUNT LOCK")); + if (acl_user->password_expired) result.append(STRING_WITH_LEN(" PASSWORD EXPIRE")); else if (!acl_user->password_lifetime) @@ -8961,9 +8964,6 @@ bool mysql_show_create_user(THD *thd, LEX_USER *lex_user) result.append(STRING_WITH_LEN(" DAY")); } - if (acl_user->account_locked) - result.append(STRING_WITH_LEN(" ACCOUNT LOCK")); - protocol->prepare_for_resend(); protocol->store(result.ptr(), result.length(), result.charset()); if (protocol->write()) -- cgit v1.2.1 From add67826361738688bc08dc3582eac8a0be5e92e Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Wed, 4 Nov 2020 15:25:00 +1100 Subject: MDEV-24098 CREATE USER/ALTER USER PASSWORD EXPIRE/LOCK in either order Reviewed-by: vicentiu@mariadb.org --- mysql-test/main/lock_user.result | 29 +++++++++++++++++++++++++++++ mysql-test/main/lock_user.test | 19 +++++++++++++++++++ sql/sql_yacc.yy | 35 +++++++++++++++++++++-------------- 3 files changed, 69 insertions(+), 14 deletions(-) diff --git a/mysql-test/main/lock_user.result b/mysql-test/main/lock_user.result index a0d2b40572c..7d9aeebb7aa 100644 --- a/mysql-test/main/lock_user.result +++ b/mysql-test/main/lock_user.result @@ -139,4 +139,33 @@ show create user user1@localhost; CREATE USER for user1@localhost CREATE USER `user1`@`localhost` ACCOUNT LOCK PASSWORD EXPIRE drop user user1@localhost; +# +# MDEV-24098 CREATE USER/ALTER USER PASSWORD EXPIRE/LOCK in +# either order. +# +create user user1@localhost PASSWORD EXPIRE ACCOUNT LOCK; +show create user user1@localhost; +CREATE USER for user1@localhost +CREATE USER `user1`@`localhost` ACCOUNT LOCK PASSWORD EXPIRE +drop user user1@localhost; +create user user1@localhost ACCOUNT LOCK PASSWORD EXPIRE; +show create user user1@localhost; +CREATE USER for user1@localhost +CREATE USER `user1`@`localhost` ACCOUNT LOCK PASSWORD EXPIRE +alter user user1@localhost PASSWORD EXPIRE NEVER ACCOUNT UNLOCK ; +show create user user1@localhost; +CREATE USER for user1@localhost +CREATE USER `user1`@`localhost` PASSWORD EXPIRE +alter user user1@localhost ACCOUNT LOCK PASSWORD EXPIRE DEFAULT; +show create user user1@localhost; +CREATE USER for user1@localhost +CREATE USER `user1`@`localhost` ACCOUNT LOCK PASSWORD EXPIRE +alter user user1@localhost PASSWORD EXPIRE INTERVAL 60 DAY ACCOUNT UNLOCK; +select * from mysql.global_priv where user='user1'; +Host User Priv +localhost user1 {"access":0,"plugin":"mysql_native_password","authentication_string":"","account_locked":false,"password_last_changed":0,"password_lifetime":60} +show create user user1@localhost; +CREATE USER for user1@localhost +CREATE USER `user1`@`localhost` PASSWORD EXPIRE +drop user user1@localhost; drop user user2@localhost; diff --git a/mysql-test/main/lock_user.test b/mysql-test/main/lock_user.test index 504c6c5ac10..530883f33ef 100644 --- a/mysql-test/main/lock_user.test +++ b/mysql-test/main/lock_user.test @@ -143,6 +143,25 @@ alter user user1@localhost account lock; --echo # alter user user1@localhost PASSWORD EXPIRE; show create user user1@localhost; +drop user user1@localhost; + +--echo # +--echo # MDEV-24098 CREATE USER/ALTER USER PASSWORD EXPIRE/LOCK in +--echo # either order. +--echo # +create user user1@localhost PASSWORD EXPIRE ACCOUNT LOCK; +show create user user1@localhost; +drop user user1@localhost; +create user user1@localhost ACCOUNT LOCK PASSWORD EXPIRE; +show create user user1@localhost; +alter user user1@localhost PASSWORD EXPIRE NEVER ACCOUNT UNLOCK ; +show create user user1@localhost; +alter user user1@localhost ACCOUNT LOCK PASSWORD EXPIRE DEFAULT; +show create user user1@localhost; +# note output needs to be corrected by MDEV-24114: password expire users cannot be unexpired +alter user user1@localhost PASSWORD EXPIRE INTERVAL 60 DAY ACCOUNT UNLOCK; +select * from mysql.global_priv where user='user1'; +show create user user1@localhost; drop user user1@localhost; drop user user2@localhost; diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 3e3674d03ea..47102c3c6aa 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -2902,7 +2902,7 @@ create: MYSQL_YYABORT; } | create_or_replace USER_SYM opt_if_not_exists clear_privileges - grant_list opt_require_clause opt_resource_options opt_account_locking opt_password_expiration + grant_list opt_require_clause opt_resource_options opt_account_locking_and_opt_password_expiration { if (unlikely(Lex->set_command_with_check(SQLCOM_CREATE_USER, $1 | $3))) @@ -8032,7 +8032,7 @@ alter: } OPTIONS_SYM '(' server_options_list ')' { } /* ALTER USER foo is allowed for MySQL compatibility. */ | ALTER USER_SYM opt_if_exists clear_privileges grant_list - opt_require_clause opt_resource_options opt_account_locking opt_password_expiration + opt_require_clause opt_resource_options opt_account_locking_and_opt_password_expiration { Lex->create_info.set($3); Lex->sql_command= SQLCOM_ALTER_USER; @@ -8071,39 +8071,46 @@ alter: } ; -opt_account_locking: - /* Nothing */ {} - | ACCOUNT_SYM LOCK_SYM +account_locking_option: + LOCK_SYM { Lex->account_options.account_locked= ACCOUNTLOCK_LOCKED; } - | ACCOUNT_SYM UNLOCK_SYM + | UNLOCK_SYM { Lex->account_options.account_locked= ACCOUNTLOCK_UNLOCKED; } ; -opt_password_expiration: - /* Nothing */ {} - | PASSWORD_SYM EXPIRE_SYM + +opt_password_expire_option: + /* empty */ { Lex->account_options.password_expire= PASSWORD_EXPIRE_NOW; } - | PASSWORD_SYM EXPIRE_SYM NEVER_SYM + | NEVER_SYM { Lex->account_options.password_expire= PASSWORD_EXPIRE_NEVER; } - | PASSWORD_SYM EXPIRE_SYM DEFAULT + | DEFAULT { Lex->account_options.password_expire= PASSWORD_EXPIRE_DEFAULT; } - | PASSWORD_SYM EXPIRE_SYM INTERVAL_SYM NUM DAY_SYM + | INTERVAL_SYM NUM DAY_SYM { Lex->account_options.password_expire= PASSWORD_EXPIRE_INTERVAL; - if (!(Lex->account_options.num_expiration_days= atoi($4.str))) - my_yyabort_error((ER_WRONG_VALUE, MYF(0), "DAY", $4.str)); + if (!(Lex->account_options.num_expiration_days= atoi($2.str))) + my_yyabort_error((ER_WRONG_VALUE, MYF(0), "DAY", $2.str)); } ; +opt_account_locking_and_opt_password_expiration: + /* empty */ + | ACCOUNT_SYM account_locking_option + | PASSWORD_SYM EXPIRE_SYM opt_password_expire_option + | ACCOUNT_SYM account_locking_option PASSWORD_SYM EXPIRE_SYM opt_password_expire_option + | PASSWORD_SYM EXPIRE_SYM opt_password_expire_option ACCOUNT_SYM account_locking_option + ; + ev_alter_on_schedule_completion: /* empty */ { $$= 0;} | ON SCHEDULE_SYM ev_schedule_time { $$= 1; } -- cgit v1.2.1 From c048053c8af5083d35f764c5707c0b4ddb549846 Mon Sep 17 00:00:00 2001 From: Alice Sherepa Date: Thu, 29 Oct 2020 15:20:48 +0100 Subject: MDEV-23103 rpl.rpl_gtid_delete_domain failed in buildbot --- mysql-test/suite/rpl/r/rpl_gtid_delete_domain.result | 2 +- mysql-test/suite/rpl/t/rpl_gtid_delete_domain.test | 12 +++--------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/mysql-test/suite/rpl/r/rpl_gtid_delete_domain.result b/mysql-test/suite/rpl/r/rpl_gtid_delete_domain.result index 74648501fbe..9c36973427a 100644 --- a/mysql-test/suite/rpl/r/rpl_gtid_delete_domain.result +++ b/mysql-test/suite/rpl/r/rpl_gtid_delete_domain.result @@ -22,7 +22,7 @@ START SLAVE IO_THREAD; include/wait_for_slave_io_error.inc [errno=1236] connection master; FLUSH BINARY LOGS; -PURGE BINARY LOGS TO 'master-bin.000002';; +include/wait_for_purge.inc "master-bin.000002" FLUSH BINARY LOGS DELETE_DOMAIN_ID=(11); SELECT @@global.gtid_binlog_pos, @@global.gtid_binlog_state; @@global.gtid_binlog_pos @@global.gtid_binlog_state diff --git a/mysql-test/suite/rpl/t/rpl_gtid_delete_domain.test b/mysql-test/suite/rpl/t/rpl_gtid_delete_domain.test index 0262998798a..5537b6fbf86 100644 --- a/mysql-test/suite/rpl/t/rpl_gtid_delete_domain.test +++ b/mysql-test/suite/rpl/t/rpl_gtid_delete_domain.test @@ -53,17 +53,11 @@ START SLAVE IO_THREAD; # adjust the master binlog state FLUSH BINARY LOGS; --let $purge_to_binlog= query_get_value(SHOW MASTER STATUS, File, 1) ---eval PURGE BINARY LOGS TO '$purge_to_binlog'; +--let $purge_binlogs_to=$purge_to_binlog +--source include/wait_for_purge.inc + # with final removal of the extra domain -###adding to debug info to catch the failure (1076): ---error 0,1076 --eval FLUSH BINARY LOGS DELETE_DOMAIN_ID=($extra_domain_id) - -if ($mysql_errno == 1076) { - --echo ### Failure "Could not delete gtid domain" - --source include/show_rpl_debug_info.inc - } - SELECT @@global.gtid_binlog_pos, @@global.gtid_binlog_state; --connection slave -- cgit v1.2.1 From f5d2d455a6fa9f55f65238f27a625efe1d8ba5d9 Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Sat, 7 Nov 2020 10:13:17 +1100 Subject: MDEV-24098 (Oracle mode) CREATE USER/ALTER USER PASSWORD EXPIRE/LOCK in either order tested with: $ mysql-test/mtr --mem --mysqld=--sql-mode=ORACLE main.lock_user Observed Oracle syntax quoting difference in `SHOW CREATE USER` output only. Omission in syntax noticed by Robert Bindar. Identical to previous patch except in sql_yacc_ora.yy --- sql/sql_yacc_ora.yy | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/sql/sql_yacc_ora.yy b/sql/sql_yacc_ora.yy index a7b1b880943..ac4929468b1 100644 --- a/sql/sql_yacc_ora.yy +++ b/sql/sql_yacc_ora.yy @@ -2434,7 +2434,7 @@ create: MYSQL_YYABORT; } | create_or_replace USER_SYM opt_if_not_exists clear_privileges - grant_list opt_require_clause opt_resource_options opt_account_locking opt_password_expiration + grant_list opt_require_clause opt_resource_options opt_account_locking_and_opt_password_expiration { if (unlikely(Lex->set_command_with_check(SQLCOM_CREATE_USER, $1 | $3))) @@ -8128,7 +8128,7 @@ alter: } OPTIONS_SYM '(' server_options_list ')' { } /* ALTER USER foo is allowed for MySQL compatibility. */ | ALTER USER_SYM opt_if_exists clear_privileges grant_list - opt_require_clause opt_resource_options opt_account_locking opt_password_expiration + opt_require_clause opt_resource_options opt_account_locking_and_opt_password_expiration { Lex->create_info.set($3); Lex->sql_command= SQLCOM_ALTER_USER; @@ -8167,39 +8167,46 @@ alter: } ; -opt_account_locking: - /* Nothing */ {} - | ACCOUNT_SYM LOCK_SYM +account_locking_option: + LOCK_SYM { Lex->account_options.account_locked= ACCOUNTLOCK_LOCKED; } - | ACCOUNT_SYM UNLOCK_SYM + | UNLOCK_SYM { Lex->account_options.account_locked= ACCOUNTLOCK_UNLOCKED; } ; -opt_password_expiration: - /* Nothing */ {} - | PASSWORD_SYM EXPIRE_SYM + +opt_password_expire_option: + /* empty */ { Lex->account_options.password_expire= PASSWORD_EXPIRE_NOW; } - | PASSWORD_SYM EXPIRE_SYM NEVER_SYM + | NEVER_SYM { Lex->account_options.password_expire= PASSWORD_EXPIRE_NEVER; } - | PASSWORD_SYM EXPIRE_SYM DEFAULT + | DEFAULT { Lex->account_options.password_expire= PASSWORD_EXPIRE_DEFAULT; } - | PASSWORD_SYM EXPIRE_SYM INTERVAL_SYM NUM DAY_SYM + | INTERVAL_SYM NUM DAY_SYM { Lex->account_options.password_expire= PASSWORD_EXPIRE_INTERVAL; - if (!(Lex->account_options.num_expiration_days= atoi($4.str))) - my_yyabort_error((ER_WRONG_VALUE, MYF(0), "DAY", $4.str)); + if (!(Lex->account_options.num_expiration_days= atoi($2.str))) + my_yyabort_error((ER_WRONG_VALUE, MYF(0), "DAY", $2.str)); } ; +opt_account_locking_and_opt_password_expiration: + /* empty */ + | ACCOUNT_SYM account_locking_option + | PASSWORD_SYM EXPIRE_SYM opt_password_expire_option + | ACCOUNT_SYM account_locking_option PASSWORD_SYM EXPIRE_SYM opt_password_expire_option + | PASSWORD_SYM EXPIRE_SYM opt_password_expire_option ACCOUNT_SYM account_locking_option + ; + ev_alter_on_schedule_completion: /* empty */ { $$= 0;} | ON SCHEDULE_SYM ev_schedule_time { $$= 1; } -- cgit v1.2.1 From d01a034ac604a5f960b5f4d16e24e8951b74419f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Mon, 9 Nov 2020 15:50:37 +0200 Subject: MDEV-7620: Remove the data structures The instrumentation that was added in commit 90635c6fb5e51aa878b81b1285c758afc361c7dc (MDEV-7620) was effectively reverted in MariaDB Server 10.2.2, in commit 2e814d4702d71a04388386a9f591d14a35980bfe (which stopped reporting the statistics) and commit fec844aca88e1c6b9c36bb0b811e92d9d023ffb9 (which stopped updating the statistics). Let us remove the orphan data members to reduce the memory footprint. --- storage/innobase/include/trx0trx.h | 14 -------------- storage/innobase/trx/trx0trx.cc | 4 ---- 2 files changed, 18 deletions(-) diff --git a/storage/innobase/include/trx0trx.h b/storage/innobase/include/trx0trx.h index 4161d4d8563..048a050a28d 100644 --- a/storage/innobase/include/trx0trx.h +++ b/storage/innobase/include/trx0trx.h @@ -1113,20 +1113,6 @@ private: during bulk create index */ FlushObserver* flush_observer; public: - /* Lock wait statistics */ - ulint n_rec_lock_waits; - /*!< Number of record lock waits, - might not be exactly correct. */ - ulint n_table_lock_waits; - /*!< Number of table lock waits, - might not be exactly correct. */ - ulint total_rec_lock_wait_time; - /*!< Total rec lock wait time up - to this moment. */ - ulint total_table_lock_wait_time; - /*!< Total table lock wait time - up to this moment. */ - #ifdef WITH_WSREP os_event_t wsrep_event; /* event waited for in srv_conc_slot */ #endif /* WITH_WSREP */ diff --git a/storage/innobase/trx/trx0trx.cc b/storage/innobase/trx/trx0trx.cc index 13b4efb973b..245758cecd5 100644 --- a/storage/innobase/trx/trx0trx.cc +++ b/storage/innobase/trx/trx0trx.cc @@ -464,10 +464,6 @@ inline void trx_t::free() MEM_NOACCESS(&mod_tables, sizeof mod_tables); MEM_NOACCESS(&detailed_error, sizeof detailed_error); MEM_NOACCESS(&flush_observer, sizeof flush_observer); - MEM_NOACCESS(&n_rec_lock_waits, sizeof n_rec_lock_waits); - MEM_NOACCESS(&n_table_lock_waits, sizeof n_table_lock_waits); - MEM_NOACCESS(&total_rec_lock_wait_time, sizeof total_rec_lock_wait_time); - MEM_NOACCESS(&total_table_lock_wait_time, sizeof total_table_lock_wait_time); #ifdef WITH_WSREP MEM_NOACCESS(&wsrep_event, sizeof wsrep_event); #endif /* WITH_WSREP */ -- cgit v1.2.1 From 5171ab808ce7d42c51188d695608c849272a90dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Tue, 10 Nov 2020 07:56:04 +0200 Subject: MDEV-24171 index_online_log is instrumented as rw-lock, not mutex The row_log_t::mutex is a mutex, yet it was instrumented as rw-lock in PERFORMANCE_SCHEMA. --- storage/innobase/handler/ha_innodb.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index 0617005a213..04683a6843b 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -614,6 +614,7 @@ static PSI_mutex_info all_innodb_mutexes[] = { PSI_KEY(ibuf_bitmap_mutex), PSI_KEY(ibuf_mutex), PSI_KEY(ibuf_pessimistic_insert_mutex), + PSI_KEY(index_online_log), PSI_KEY(log_sys_mutex), PSI_KEY(log_sys_write_mutex), PSI_KEY(mutex_list_mutex), @@ -671,7 +672,6 @@ static PSI_rwlock_info all_innodb_rwlocks[] = { PSI_RWLOCK_KEY(trx_i_s_cache_lock), PSI_RWLOCK_KEY(trx_purge_latch), PSI_RWLOCK_KEY(index_tree_rw_lock), - PSI_RWLOCK_KEY(index_online_log), PSI_RWLOCK_KEY(hash_table_locks) }; # endif /* UNIV_PFS_RWLOCK */ -- cgit v1.2.1 From 4e24b3187cd5ffbbfce1890e1befe5095ab0fb1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Tue, 10 Nov 2020 14:19:01 +0200 Subject: MDEV-19951 followup: Remove unused st_handler_tablename, tablename_compare --- storage/innobase/handler/ha_innodb.cc | 18 ------------------ storage/innobase/handler/ha_innodb.h | 7 +------ 2 files changed, 1 insertion(+), 24 deletions(-) diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index 0585aaacb46..057b233a5fc 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -15189,24 +15189,6 @@ struct table_list_item { const char* name; }; -/** Structure to compare two st_tablename objects using their -db and tablename. It is used in the ordering of cascade_fk_set. -It returns true if the first argument precedes the second argument -and false otherwise. */ -struct tablename_compare { - - bool operator()(const st_handler_tablename lhs, - const st_handler_tablename rhs) const - { - int cmp = strcmp(lhs.db, rhs.db); - if (cmp == 0) { - cmp = strcmp(lhs.tablename, rhs.tablename); - } - - return(cmp < 0); - } -}; - /*****************************************************************//** Checks if ALTER TABLE may change the storage engine of the table. Changing storage engines is not allowed for tables for which there diff --git a/storage/innobase/handler/ha_innodb.h b/storage/innobase/handler/ha_innodb.h index d684516e641..eb2afb93595 100644 --- a/storage/innobase/handler/ha_innodb.h +++ b/storage/innobase/handler/ha_innodb.h @@ -51,12 +51,7 @@ struct ha_table_option_struct uint encryption; /*!< DEFAULT, ON, OFF */ ulonglong encryption_key_id; /*!< encryption key id */ }; -/* JAN: TODO: MySQL 5.7 handler.h */ -struct st_handler_tablename -{ - const char *db; - const char *tablename; -}; + /** The class defining a handle to an Innodb table */ class ha_innobase final: public handler { -- cgit v1.2.1 From 3a5cf14defd64003e403b677a6901a1d8a447703 Mon Sep 17 00:00:00 2001 From: Vladislav Vaintroub Date: Mon, 9 Nov 2020 19:47:44 +0100 Subject: MDEV-24175 Windows - fix detection of SSD --- storage/innobase/os/os0file.cc | 242 ++++++++++++++++++++++++----------------- 1 file changed, 143 insertions(+), 99 deletions(-) diff --git a/storage/innobase/os/os0file.cc b/storage/innobase/os/os0file.cc index 6a1dce703cb..27b26d4f5b1 100644 --- a/storage/innobase/os/os0file.cc +++ b/storage/innobase/os/os0file.cc @@ -7556,72 +7556,152 @@ os_file_set_umask(ulint umask) } #ifdef _WIN32 -static int win32_get_block_size(HANDLE volume_handle, const char *volume_name) -{ - STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR disk_alignment; - STORAGE_PROPERTY_QUERY storage_query; - DWORD tmp; - - memset(&storage_query, 0, sizeof(storage_query)); - storage_query.PropertyId = StorageAccessAlignmentProperty; - storage_query.QueryType = PropertyStandardQuery; - - if (os_win32_device_io_control(volume_handle, - IOCTL_STORAGE_QUERY_PROPERTY, - &storage_query, - sizeof storage_query, - &disk_alignment, - sizeof disk_alignment, - &tmp) && tmp == sizeof disk_alignment) { - return disk_alignment.BytesPerPhysicalSector; - } - switch (GetLastError()) { - case ERROR_INVALID_FUNCTION: - case ERROR_NOT_SUPPORTED: - break; - default: - os_file_handle_error_no_exit( - volume_name, - "DeviceIoControl(IOCTL_STORAGE_QUERY_PROPERTY / StorageAccessAlignmentProperty)", - FALSE); - } - return 512; +/* Checks whether physical drive is on SSD.*/ +static bool is_drive_on_ssd(DWORD nr) +{ + char physical_drive_path[32]; + snprintf(physical_drive_path, sizeof(physical_drive_path), + "\\\\.\\PhysicalDrive%lu", nr); + + HANDLE h= CreateFile(physical_drive_path, 0, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); + if (h == INVALID_HANDLE_VALUE) + return false; + + DEVICE_SEEK_PENALTY_DESCRIPTOR seek_penalty; + STORAGE_PROPERTY_QUERY storage_query{}; + storage_query.PropertyId= StorageDeviceSeekPenaltyProperty; + storage_query.QueryType= PropertyStandardQuery; + + bool on_ssd= false; + DWORD bytes_written; + if (DeviceIoControl(h, IOCTL_STORAGE_QUERY_PROPERTY, &storage_query, + sizeof storage_query, &seek_penalty, sizeof seek_penalty, + &bytes_written, nullptr)) + { + on_ssd= seek_penalty.IncursSeekPenalty; + } + else + { + on_ssd= false; + } + CloseHandle(h); + return on_ssd; } -static bool win32_is_ssd(HANDLE volume_handle) +/* + Checks whether volume is on SSD, by checking all physical drives + in that volume. +*/ +static bool is_volume_on_ssd(const char *volume_mount_point) { - DWORD tmp; - DEVICE_SEEK_PENALTY_DESCRIPTOR seek_penalty; - STORAGE_PROPERTY_QUERY storage_query; - memset(&storage_query, 0, sizeof(storage_query)); - - storage_query.PropertyId = StorageDeviceSeekPenaltyProperty; - storage_query.QueryType = PropertyStandardQuery; - - if (os_win32_device_io_control(volume_handle, - IOCTL_STORAGE_QUERY_PROPERTY, - &storage_query, - sizeof storage_query, - &seek_penalty, - sizeof seek_penalty, - &tmp) && tmp == sizeof(seek_penalty)){ - return !seek_penalty.IncursSeekPenalty; + char volume_name[MAX_PATH]; + + if (!GetVolumeNameForVolumeMountPoint(volume_mount_point, volume_name, + array_elements(volume_name))) + { + /* This can fail, e.g if file is on network share */ + return false; } - DEVICE_TRIM_DESCRIPTOR trim; - storage_query.PropertyId = StorageDeviceTrimProperty; - if (os_win32_device_io_control(volume_handle, - IOCTL_STORAGE_QUERY_PROPERTY, - &storage_query, - sizeof storage_query, - &trim, - sizeof trim, - &tmp) && tmp == sizeof trim) { - return trim.TrimEnabled; + /* Chomp last backslash, this is needed to open volume.*/ + size_t length= strlen(volume_name); + if (length && volume_name[length - 1] == '\\') + volume_name[length - 1]= 0; + + /* Open volume handle */ + HANDLE volume_handle= CreateFile( + volume_name, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); + + if (volume_handle == INVALID_HANDLE_VALUE) + return false; + + /* + Enumerate all volume extends, check whether all of them are on SSD + */ + + /* Anticipate common case where there is only one extent.*/ + VOLUME_DISK_EXTENTS single_extent; + + /* But also have a place to manage allocated data.*/ + std::unique_ptr lifetime; + + DWORD bytes_written; + VOLUME_DISK_EXTENTS *extents= nullptr; + if (DeviceIoControl(volume_handle, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, + nullptr, 0, &single_extent, sizeof(single_extent), + &bytes_written, nullptr)) + { + /* Worked on the first try. Use the preallocated buffer.*/ + extents= &single_extent; + } + else + { + VOLUME_DISK_EXTENTS *last_query= &single_extent; + while (GetLastError() == ERROR_MORE_DATA) + { + DWORD extentCount= last_query->NumberOfDiskExtents; + DWORD allocatedSize= + FIELD_OFFSET(VOLUME_DISK_EXTENTS, Extents[extentCount]); + lifetime.reset(new BYTE[allocatedSize]); + last_query= (VOLUME_DISK_EXTENTS *) lifetime.get(); + if (DeviceIoControl(volume_handle, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, + nullptr, 0, last_query, allocatedSize, + &bytes_written, nullptr)) + { + extents= last_query; + break; + } + } } - return false; + CloseHandle(volume_handle); + if (!extents) + return false; + + for (DWORD i= 0; i < extents->NumberOfDiskExtents; i++) + if (!is_drive_on_ssd(extents->Extents[i].DiskNumber)) + return false; + + return true; } + +#include +static bool is_file_on_ssd(char *file_path) +{ + /* Cache of volume_path => volume_info, protected by rwlock.*/ + static std::unordered_map cache; + static SRWLOCK lock= SRWLOCK_INIT; + + /* Preset result, in case something fails, e.g we're on network drive.*/ + char volume_path[MAX_PATH]; + if (!GetVolumePathName(file_path, volume_path, array_elements(volume_path))) + return false; + + /* Try cached volume info first.*/ + std::string volume_path_str(volume_path); + bool found; + bool result; + AcquireSRWLockShared(&lock); + auto e= cache.find(volume_path_str); + if ((found= e != cache.end())) + result= e->second; + ReleaseSRWLockShared(&lock); + + if (found) + return result; + + result= is_volume_on_ssd(volume_path); + + /* Update cache */ + AcquireSRWLockExclusive(&lock); + cache[volume_path_str]= result; + ReleaseSRWLockExclusive(&lock); + return result; +} + #endif /** Determine some file metadata when creating or reading the file. @@ -7658,48 +7738,12 @@ void fil_node_t::find_metadata(os_file_t file space->atomic_write_supported = space->purpose == FIL_TYPE_TEMPORARY || space->purpose == FIL_TYPE_IMPORT; #ifdef _WIN32 - block_size = 512; - on_ssd = false; - // Open volume for this file, find out it "physical bytes per sector" - char volume[MAX_PATH + 4]; - if (!GetVolumePathName(name, volume + 4, MAX_PATH)) { - os_file_handle_error_no_exit(name, - "GetVolumePathName()", FALSE); - return; - } - // Special prefix required for volume names. - memcpy(volume, "\\\\.\\", 4); - - size_t len = strlen(volume); - if (volume[len - 1] == '\\') { - // Trim trailing backslash from volume name. - volume[len - 1] = 0; - } - - HANDLE volume_handle = CreateFile(volume, FILE_READ_ATTRIBUTES, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - 0, OPEN_EXISTING, 0, 0); - - if (volume_handle != INVALID_HANDLE_VALUE) { - block_size = win32_get_block_size(volume_handle, volume); - on_ssd = win32_is_ssd(volume_handle); - CloseHandle(volume_handle); + on_ssd = is_file_on_ssd(name); + FILE_STORAGE_INFO info; + if (GetFileInformationByHandleEx( + file, FileStorageInfo, &info, sizeof(info))) { + block_size = info.PhysicalBytesPerSectorForAtomicity; } else { - /* - Report error, unless it is expected, e.g - missing permissions, or error when trying to - open volume for UNC share. - */ - if (GetLastError() != ERROR_ACCESS_DENIED - && GetDriveType(volume) == DRIVE_FIXED) { - os_file_handle_error_no_exit(volume, "CreateFile()", FALSE); - } - } - - /* Currently we support file block size up to 4KiB */ - if (block_size > 4096) { - block_size = 4096; - } else if (block_size < 512) { block_size = 512; } #else -- cgit v1.2.1 From cd927dd34555a34e733a01bf5af52470f57ce906 Mon Sep 17 00:00:00 2001 From: Anel Husakovic Date: Mon, 9 Nov 2020 11:58:49 +0100 Subject: MDEV-23769: MTR can abort before it prints the test result summary - Patch is solving generating report on warning To repeat the error run single worker: ``` ./mtr --mysqld=--lock-wait-timeout=-xx 1st 1st --force --parallel 1 ``` or `N` workers with `N+1` tests with failures and `force` ``` ./mtr --mysqld=--lock-wait-timeout=-xx 1st 1st grant5 --force --parallel 2 ``` - Patch is doing cosmetic fix of `current_test` log file which holds the old log value of test `CURRENT TEST:..` in `mark_log()` in case of `unknown option` and as such the logic which is using it's content doesn't output valid log content and doesn't generate valid `$test->{'comment'}` message.asdf - Closing the socket/handler after the removing the handler from IO for consistency Reviewed by: serg@mariadb.com --- mysql-test/mysql-test-run.pl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mysql-test/mysql-test-run.pl b/mysql-test/mysql-test-run.pl index 900ef736a45..8093e7ecb6f 100755 --- a/mysql-test/mysql-test-run.pl +++ b/mysql-test/mysql-test-run.pl @@ -802,6 +802,7 @@ sub run_test_server ($$$) { # Client disconnected mtr_verbose("Child closed socket"); $s->remove($sock); + $sock->close; if (--$childs == 0){ return ("Completed", $test_failure, $completed, $extra_warnings); } @@ -971,6 +972,7 @@ sub run_test_server ($$$) { # Test failure due to warnings, force is off return ("Warnings in log", 1, $completed, $extra_warnings); } + next; } elsif ($line =~ /^SPENT/) { add_total_times($line); @@ -4184,6 +4186,7 @@ sub run_testcase ($$) { if (start_servers($tinfo)) { report_failure_and_restart($tinfo); + unlink $path_current_testlog; return 1; } } -- cgit v1.2.1 From 3bf726f5ffc568b15e9788eddcfe87d81a5f6ed5 Mon Sep 17 00:00:00 2001 From: Anel Husakovic Date: Thu, 5 Nov 2020 16:12:22 +0100 Subject: MDEV-24130: Cannot launch mariadbd via mysqld_safe - Patch 95bb3cb886cb64be3ee5ace660b used `my_which` function in `10.2` - Based on patch `355ee6877bec` from 10.3+ `command -v` is used instead of `my_which` so we are changing in this patch also - `ldconfig` is usually found in `/sbin` so make sure it is added in `$PATH` variable --- scripts/mysqld_safe.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/mysqld_safe.sh b/scripts/mysqld_safe.sh index 98398e5421f..8e8575f4c24 100644 --- a/scripts/mysqld_safe.sh +++ b/scripts/mysqld_safe.sh @@ -440,7 +440,8 @@ mysqld_ld_preload_text() { set_malloc_lib() { malloc_lib="$1" if expr "$malloc_lib" : "\(tcmalloc\|jemalloc\)" > /dev/null ; then - if ! my_which ldconfig > /dev/null 2>&1 + export PATH=$PATH:/sbin + if ! command -v ldconfig > /dev/null 2>&1 then log_error "ldconfig command not found, required for ldconfig -p" exit 1 -- cgit v1.2.1 From 83f3d12fcdeb033528a124d18e98a4f7fec1d078 Mon Sep 17 00:00:00 2001 From: Daniele Sciascia Date: Thu, 1 Oct 2020 11:18:54 +0200 Subject: Update wsrep-lib MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewed-by: Jan Lindström --- sql/wsrep_client_service.cc | 27 ++++++++++++++++++++++----- sql/wsrep_client_service.h | 11 +++++++++++ wsrep-lib | 2 +- 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/sql/wsrep_client_service.cc b/sql/wsrep_client_service.cc index 20bb7748a95..e563a0dba2d 100644 --- a/sql/wsrep_client_service.cc +++ b/sql/wsrep_client_service.cc @@ -246,6 +246,16 @@ void Wsrep_client_service::will_replay() mysql_mutex_unlock(&LOCK_wsrep_replaying); } +void Wsrep_client_service::signal_replayed() +{ + DBUG_ASSERT(m_thd == current_thd); + mysql_mutex_lock(&LOCK_wsrep_replaying); + --wsrep_replaying; + DBUG_ASSERT(wsrep_replaying >= 0); + mysql_cond_broadcast(&COND_wsrep_replaying); + mysql_mutex_unlock(&LOCK_wsrep_replaying); +} + enum wsrep::provider::status Wsrep_client_service::replay() { @@ -274,14 +284,15 @@ enum wsrep::provider::status Wsrep_client_service::replay() } delete replayer_thd; - - mysql_mutex_lock(&LOCK_wsrep_replaying); - --wsrep_replaying; - mysql_cond_broadcast(&COND_wsrep_replaying); - mysql_mutex_unlock(&LOCK_wsrep_replaying); DBUG_RETURN(ret); } +enum wsrep::provider::status Wsrep_client_service::replay_unordered() +{ + DBUG_ASSERT(0); + return wsrep::provider::error_not_implemented; +} + void Wsrep_client_service::wait_for_replayers(wsrep::unique_lock& lock) { DBUG_ASSERT(m_thd == current_thd); @@ -301,6 +312,12 @@ void Wsrep_client_service::wait_for_replayers(wsrep::unique_lock& lock.lock(); } +enum wsrep::provider::status Wsrep_client_service::commit_by_xid() +{ + DBUG_ASSERT(0); + return wsrep::provider::error_not_implemented; +} + void Wsrep_client_service::debug_sync(const char* sync_point) { DBUG_ASSERT(m_thd == current_thd); diff --git a/sql/wsrep_client_service.h b/sql/wsrep_client_service.h index aa58d99c3cf..253d2f43ac3 100644 --- a/sql/wsrep_client_service.h +++ b/sql/wsrep_client_service.h @@ -48,8 +48,19 @@ public: void emergency_shutdown() { throw wsrep::not_implemented_error(); } void will_replay(); + void signal_replayed(); enum wsrep::provider::status replay(); + enum wsrep::provider::status replay_unordered(); void wait_for_replayers(wsrep::unique_lock&); + enum wsrep::provider::status commit_by_xid(); + bool is_explicit_xa() + { + return false; + } + bool is_xa_rollback() + { + return false; + } void debug_sync(const char*); void debug_crash(const char*); int bf_rollback(); diff --git a/wsrep-lib b/wsrep-lib index 2da6e4894e1..41a6e9dad79 160000 --- a/wsrep-lib +++ b/wsrep-lib @@ -1 +1 @@ -Subproject commit 2da6e4894e1df5d1db51db2bbc49255e02251b9d +Subproject commit 41a6e9dad79c921134e44cf974b6b7ca3b84e538 -- cgit v1.2.1 From bd528b0c93409b81157314d9699af519fd9d52ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Tue, 10 Nov 2020 09:47:29 +0200 Subject: MDEV-24182 ibuf_merge_or_delete_for_page() contains dead code The function ibuf_merge_or_delete_for_page() was always being invoked with update_ibuf_bitmap=true ever since commit cd623508dff53c210154392da6c0f65b7b6bcf4c fixed up something after MDEV-9566. Furthermore, the parameter page_size is never being passed as a null pointer, and therefore it should better be a reference to a constant object. --- storage/innobase/buf/buf0buf.cc | 6 +- storage/innobase/ibuf/ibuf0ibuf.cc | 123 +++++++++++++---------------------- storage/innobase/include/ibuf0ibuf.h | 8 +-- storage/innobase/log/log0recv.cc | 3 +- 4 files changed, 50 insertions(+), 90 deletions(-) diff --git a/storage/innobase/buf/buf0buf.cc b/storage/innobase/buf/buf0buf.cc index e2b68011077..a54d98adee0 100644 --- a/storage/innobase/buf/buf0buf.cc +++ b/storage/innobase/buf/buf0buf.cc @@ -4601,7 +4601,7 @@ evict_from_pool: if (!access_time && !recv_no_ibuf_operations) { ibuf_merge_or_delete_for_page( - block, page_id, &page_size, TRUE); + block, page_id, page_size); } buf_pool_mutex_enter(buf_pool); @@ -5678,7 +5678,7 @@ loop: /* Delete possible entries for the page from the insert buffer: such can exist if the page belonged to an index which was dropped */ if (!recv_recovery_is_on()) { - ibuf_merge_or_delete_for_page(NULL, page_id, &page_size, TRUE); + ibuf_merge_or_delete_for_page(NULL, page_id, page_size); } frame = block->frame; @@ -6133,7 +6133,7 @@ database_corrupted: ibuf_merge_or_delete_for_page( (buf_block_t*) bpage, bpage->id, - &bpage->size, TRUE); + bpage->size); } fil_space_release_for_io(space); diff --git a/storage/innobase/ibuf/ibuf0ibuf.cc b/storage/innobase/ibuf/ibuf0ibuf.cc index 7068dab77a4..ee6fd235d5e 100644 --- a/storage/innobase/ibuf/ibuf0ibuf.cc +++ b/storage/innobase/ibuf/ibuf0ibuf.cc @@ -4384,16 +4384,12 @@ exist entries for such a page if the page belonged to an index which subsequently was dropped. @param[in,out] block if page has been read from disk, pointer to the page x-latched, else NULL -@param[in] page_id page id of the index page -@param[in] update_ibuf_bitmap normally this is set to TRUE, but -if we have deleted or are deleting the tablespace, then we naturally do not -want to update a non-existent bitmap page */ +@param[in] page_id page id of the index page */ void ibuf_merge_or_delete_for_page( buf_block_t* block, const page_id_t page_id, - const page_size_t* page_size, - ibool update_ibuf_bitmap) + const page_size_t& page_size) { btr_pcur_t pcur; #ifdef UNIV_IBUF_DEBUG @@ -4417,81 +4413,50 @@ ibuf_merge_or_delete_for_page( return; } - /* We cannot refer to page_size in the following, because it is passed - as NULL (it is unknown) when buf_read_ibuf_merge_pages() is merging - (discarding) changes for a dropped tablespace. When block != NULL or - update_ibuf_bitmap is specified, then page_size must be known. - That is why we will repeat the check below, with page_size in - place of univ_page_size. Passing univ_page_size assumes that the - uncompressed page size always is a power-of-2 multiple of the - compressed page size. */ - - if (ibuf_fixed_addr_page(page_id, univ_page_size) - || fsp_descr_page(page_id, univ_page_size)) { + if (ibuf_fixed_addr_page(page_id, page_size) + || fsp_descr_page(page_id, page_size)) { return; } - fil_space_t* space; - - if (update_ibuf_bitmap) { - - ut_ad(page_size != NULL); - - if (ibuf_fixed_addr_page(page_id, *page_size) - || fsp_descr_page(page_id, *page_size)) { - return; - } + fil_space_t* space = fil_space_acquire_silent(page_id.space()); - space = fil_space_acquire_silent(page_id.space()); - - if (UNIV_UNLIKELY(!space)) { - /* Do not try to read the bitmap page from the - non-existent tablespace, delete the ibuf records */ - block = NULL; - update_ibuf_bitmap = FALSE; - } else { - page_t* bitmap_page = NULL; - ulint bitmap_bits = 0; + if (UNIV_UNLIKELY(!space)) { + block = NULL; + } else { + page_t* bitmap_page = NULL; + ulint bitmap_bits = 0; - ibuf_mtr_start(&mtr); + ibuf_mtr_start(&mtr); - bitmap_page = ibuf_bitmap_get_map_page( - page_id, *page_size, &mtr); + bitmap_page = ibuf_bitmap_get_map_page( + page_id, page_size, &mtr); - if (bitmap_page && - fil_page_get_type(bitmap_page) != FIL_PAGE_TYPE_ALLOCATED) { - bitmap_bits = ibuf_bitmap_page_get_bits( - bitmap_page, page_id, *page_size, - IBUF_BITMAP_BUFFERED, &mtr); - } + if (bitmap_page && + fil_page_get_type(bitmap_page) != FIL_PAGE_TYPE_ALLOCATED) { + bitmap_bits = ibuf_bitmap_page_get_bits( + bitmap_page, page_id, page_size, + IBUF_BITMAP_BUFFERED, &mtr); + } - ibuf_mtr_commit(&mtr); + ibuf_mtr_commit(&mtr); - if (!bitmap_bits) { - /* No changes are buffered for this page. */ - - fil_space_release(space); - if (UNIV_UNLIKELY(srv_shutdown_state) - && !srv_fast_shutdown - && (!block - || btr_page_get_index_id(block->frame) - != DICT_IBUF_ID_MIN + IBUF_SPACE_ID)) { - /* Prevent an infinite loop on slow - shutdown, in case the bitmap bits are - wrongly clear even though buffered - changes exist. */ - ibuf_delete_recs(page_id); - } - return; + if (!bitmap_bits) { + /* No changes are buffered for this page. */ + + fil_space_release(space); + if (UNIV_UNLIKELY(srv_shutdown_state) + && !srv_fast_shutdown + && (!block + || btr_page_get_index_id(block->frame) + != DICT_IBUF_ID_MIN + IBUF_SPACE_ID)) { + /* Prevent an infinite loop on slow + shutdown, in case the bitmap bits are + wrongly clear even though buffered + changes exist. */ + ibuf_delete_recs(page_id); } + return; } - } else if (block != NULL - && (ibuf_fixed_addr_page(page_id, *page_size) - || fsp_descr_page(page_id, *page_size))) { - - return; - } else { - space = NULL; } mem_heap_t* heap = mem_heap_create(512); @@ -4541,7 +4506,7 @@ loop: if (block != NULL) { ibool success; - mtr.set_named_space(page_id.space()); + mtr.set_named_space(space); success = buf_page_get_known_nowait( RW_X_LATCH, block, @@ -4556,8 +4521,8 @@ loop: the block is io-fixed. Other threads must not try to latch an io-fixed block. */ buf_block_dbg_add_level(block, SYNC_IBUF_TREE_NODE); - } else if (update_ibuf_bitmap) { - mtr.set_named_space(page_id.space()); + } else if (space) { + mtr.set_named_space(space); } if (!btr_pcur_is_on_user_rec(&pcur)) { @@ -4658,7 +4623,7 @@ loop: ibuf_btr_pcur_commit_specify_mtr(&pcur, &mtr); ibuf_mtr_start(&mtr); - mtr.set_named_space(page_id.space()); + mtr.set_named_space(space); success = buf_page_get_known_nowait( RW_X_LATCH, block, @@ -4714,26 +4679,26 @@ loop: } reset_bit: - if (update_ibuf_bitmap) { + if (space) { page_t* bitmap_page; - bitmap_page = ibuf_bitmap_get_map_page(page_id, *page_size, + bitmap_page = ibuf_bitmap_get_map_page(page_id, page_size, &mtr); ibuf_bitmap_page_set_bits( - bitmap_page, page_id, *page_size, + bitmap_page, page_id, page_size, IBUF_BITMAP_BUFFERED, FALSE, &mtr); if (block != NULL) { ulint old_bits = ibuf_bitmap_page_get_bits( - bitmap_page, page_id, *page_size, + bitmap_page, page_id, page_size, IBUF_BITMAP_FREE, &mtr); ulint new_bits = ibuf_index_page_calc_free(block); if (old_bits != new_bits) { ibuf_bitmap_page_set_bits( - bitmap_page, page_id, *page_size, + bitmap_page, page_id, page_size, IBUF_BITMAP_FREE, new_bits, &mtr); } } diff --git a/storage/innobase/include/ibuf0ibuf.h b/storage/innobase/include/ibuf0ibuf.h index a69b63ee16b..1a5214085de 100644 --- a/storage/innobase/include/ibuf0ibuf.h +++ b/storage/innobase/include/ibuf0ibuf.h @@ -342,16 +342,12 @@ exist entries for such a page if the page belonged to an index which subsequently was dropped. @param[in,out] block if page has been read from disk, pointer to the page x-latched, else NULL -@param[in] page_id page id of the index page -@param[in] update_ibuf_bitmap normally this is set to TRUE, but -if we have deleted or are deleting the tablespace, then we naturally do not -want to update a non-existent bitmap page */ +@param[in] page_id page id of the index page */ void ibuf_merge_or_delete_for_page( buf_block_t* block, const page_id_t page_id, - const page_size_t* page_size, - ibool update_ibuf_bitmap); + const page_size_t& page_size); /*********************************************************************//** Deletes all entries in the insert buffer for a given space id. This is used diff --git a/storage/innobase/log/log0recv.cc b/storage/innobase/log/log0recv.cc index 446e0afe576..4c3886caeaf 100644 --- a/storage/innobase/log/log0recv.cc +++ b/storage/innobase/log/log0recv.cc @@ -320,8 +320,7 @@ public: &mtr, NULL)) { mutex_exit(&recv_sys->mutex); ibuf_merge_or_delete_for_page( - block, i->first, - &block->page.size, true); + block, i->first, block->page.size); mtr.commit(); mtr.start(); mutex_enter(&recv_sys->mutex); -- cgit v1.2.1 From 7b7e5922afa38241e0ed54aad266c54225d3f861 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Tue, 10 Nov 2020 13:49:01 +0200 Subject: MDEV-24156 trx_undo_left() fails to prevent overflow trx_undo_left(): Return 0 in case of an overflow, instead of returning a negative number interpreted as a large positive number. Also, add debug assertions to check that the pointer is within the page area. This should allow us to catch bugs like MDEV-24096 easier in the future. --- storage/innobase/trx/trx0rec.cc | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/storage/innobase/trx/trx0rec.cc b/storage/innobase/trx/trx0rec.cc index 4aecc8ae610..e3e1c33b305 100644 --- a/storage/innobase/trx/trx0rec.cc +++ b/storage/innobase/trx/trx0rec.cc @@ -128,20 +128,18 @@ trx_undo_parse_add_undo_rec( return(ptr + len); } -/**********************************************************************//** -Calculates the free space left for extending an undo log record. +/** Calculate the free space left for extending an undo log record. +@param page undo log page +@param ptr current end of the undo page @return bytes left */ -UNIV_INLINE -ulint -trx_undo_left( -/*==========*/ - const page_t* page, /*!< in: undo log page */ - const byte* ptr) /*!< in: pointer to page */ +static ulint trx_undo_left(const page_t *page, const byte *ptr) { - /* The '- 10' is a safety margin, in case we have some small - calculation error below */ - - return(UNIV_PAGE_SIZE - (ptr - page) - 10 - FIL_PAGE_DATA_END); + ut_ad(ptr >= &page[TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_HDR_SIZE]); + /* The 10 is supposed to be an extra safety margin (and needed for + compatibility with older versions) */ + lint left= srv_page_size - (ptr - page) - (10 + FIL_PAGE_DATA_END); + ut_ad(left >= 0); + return left < 0 ? 0 : static_cast(left); } /**********************************************************************//** -- cgit v1.2.1 From d6ee28582a177679c79272c6db6a6f204d8fd49f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Wed, 11 Nov 2020 08:16:06 +0200 Subject: Cleanup: Remove dict_space_is_empty(), dict_space_get_id() As noted in commit 0b66d3f70d365bbb936aae4ca67892c17d68d241, MariaDB does not support CREATE TABLESPACE for InnoDB. Hence, some code that was added in commit fec844aca88e1c6b9c36bb0b811e92d9d023ffb9 and originally in mysql/mysql-server@c71dd213bd434c0579e454ab8880e6d3756b0fb0 is unused in MariaDB and should be removed. --- storage/innobase/dict/dict0dict.cc | 138 ----------------------------------- storage/innobase/include/dict0dict.h | 24 +----- 2 files changed, 1 insertion(+), 161 deletions(-) diff --git a/storage/innobase/dict/dict0dict.cc b/storage/innobase/dict/dict0dict.cc index e3ce13b5b27..2124fc05faa 100644 --- a/storage/innobase/dict/dict0dict.cc +++ b/storage/innobase/dict/dict0dict.cc @@ -6707,144 +6707,6 @@ dict_sys_get_size() return size; } -/** Look for any dictionary objects that are found in the given tablespace. -@param[in] space_id Tablespace ID to search for. -@return true if tablespace is empty. */ -bool -dict_space_is_empty( - ulint space_id) -{ - btr_pcur_t pcur; - const rec_t* rec; - mtr_t mtr; - bool found = false; - - rw_lock_x_lock(&dict_operation_lock); - mutex_enter(&dict_sys->mutex); - mtr_start(&mtr); - - for (rec = dict_startscan_system(&pcur, &mtr, SYS_TABLES); - rec != NULL; - rec = dict_getnext_system(&pcur, &mtr)) { - const byte* field; - ulint len; - ulint space_id_for_table; - - field = rec_get_nth_field_old( - rec, DICT_FLD__SYS_TABLES__SPACE, &len); - ut_ad(len == 4); - space_id_for_table = mach_read_from_4(field); - - if (space_id_for_table == space_id) { - found = true; - } - } - - mtr_commit(&mtr); - mutex_exit(&dict_sys->mutex); - rw_lock_x_unlock(&dict_operation_lock); - - return(!found); -} - -/** Find the space_id for the given name in sys_tablespaces. -@param[in] name Tablespace name to search for. -@return the tablespace ID. */ -ulint -dict_space_get_id( - const char* name) -{ - btr_pcur_t pcur; - const rec_t* rec; - mtr_t mtr; - ulint name_len = strlen(name); - ulint id = ULINT_UNDEFINED; - - rw_lock_x_lock(&dict_operation_lock); - mutex_enter(&dict_sys->mutex); - mtr_start(&mtr); - - for (rec = dict_startscan_system(&pcur, &mtr, SYS_TABLESPACES); - rec != NULL; - rec = dict_getnext_system(&pcur, &mtr)) { - const byte* field; - ulint len; - - field = rec_get_nth_field_old( - rec, DICT_FLD__SYS_TABLESPACES__NAME, &len); - ut_ad(len > 0); - ut_ad(len < OS_FILE_MAX_PATH); - - if (len == name_len && ut_memcmp(name, field, len) == 0) { - - field = rec_get_nth_field_old( - rec, DICT_FLD__SYS_TABLESPACES__SPACE, &len); - ut_ad(len == 4); - id = mach_read_from_4(field); - - /* This is normally called by dict_getnext_system() - at the end of the index. */ - btr_pcur_close(&pcur); - break; - } - } - - mtr_commit(&mtr); - mutex_exit(&dict_sys->mutex); - rw_lock_x_unlock(&dict_operation_lock); - - return(id); -} - -/** Determine the extent size (in pages) for the given table -@param[in] table the table whose extent size is being - calculated. -@return extent size in pages (256, 128 or 64) */ -ulint -dict_table_extent_size( - const dict_table_t* table) -{ - const ulint mb_1 = 1024 * 1024; - const ulint mb_2 = 2 * mb_1; - const ulint mb_4 = 4 * mb_1; - - page_size_t page_size = dict_table_page_size(table); - ulint pages_in_extent = FSP_EXTENT_SIZE; - - if (page_size.is_compressed()) { - - ulint disk_page_size = page_size.physical(); - - switch (disk_page_size) { - case 1024: - pages_in_extent = mb_1/1024; - break; - case 2048: - pages_in_extent = mb_1/2048; - break; - case 4096: - pages_in_extent = mb_1/4096; - break; - case 8192: - pages_in_extent = mb_1/8192; - break; - case 16384: - pages_in_extent = mb_1/16384; - break; - case 32768: - pages_in_extent = mb_2/32768; - break; - case 65536: - pages_in_extent = mb_4/65536; - break; - default: - ut_ad(0); - } - } - - return(pages_in_extent); -} - size_t dict_table_t::get_overflow_field_local_len() const { diff --git a/storage/innobase/include/dict0dict.h b/storage/innobase/include/dict0dict.h index 7351b5fb682..6cfb92a94d3 100644 --- a/storage/innobase/include/dict0dict.h +++ b/storage/innobase/include/dict0dict.h @@ -2,7 +2,7 @@ Copyright (c) 1996, 2018, Oracle and/or its affiliates. All Rights Reserved. Copyright (c) 2012, Facebook Inc. -Copyright (c) 2013, 2019, MariaDB Corporation. +Copyright (c) 2013, 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 @@ -989,14 +989,6 @@ dict_tf_get_page_size( ulint flags) MY_ATTRIBUTE((const)); -/** Determine the extent size (in pages) for the given table -@param[in] table the table whose extent size is being - calculated. -@return extent size in pages (256, 128 or 64) */ -ulint -dict_table_extent_size( - const dict_table_t* table); - /** Get the table page size. @param[in] table table @return compressed page size, or 0 if not compressed */ @@ -1905,20 +1897,6 @@ UNIV_INTERN ulint dict_sys_get_size(); -/** Look for any dictionary objects that are found in the given tablespace. -@param[in] space_id Tablespace ID to search for. -@return true if tablespace is empty. */ -bool -dict_space_is_empty( - ulint space_id); - -/** Find the space_id for the given name in sys_tablespaces. -@param[in] name Tablespace name to search for. -@return the tablespace ID. */ -ulint -dict_space_get_id( - const char* name); - /** Free the virtual column template @param[in,out] vc_templ virtual column template */ UNIV_INLINE -- cgit v1.2.1 From 15550ed3a4d81423210a1729f72852d115da8190 Mon Sep 17 00:00:00 2001 From: Daniel Bartholomew Date: Wed, 11 Nov 2020 10:17:22 -0500 Subject: bump the VERSION --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index b451aa8a789..fa4ec3694cd 100644 --- a/VERSION +++ b/VERSION @@ -1,3 +1,3 @@ MYSQL_VERSION_MAJOR=10 MYSQL_VERSION_MINOR=2 -MYSQL_VERSION_PATCH=36 +MYSQL_VERSION_PATCH=37 -- cgit v1.2.1 From bafbfb5530e29a83aef7e6e55d5604e08b637681 Mon Sep 17 00:00:00 2001 From: Daniel Bartholomew Date: Wed, 11 Nov 2020 10:18:33 -0500 Subject: bump the VERSION --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 1eee99cfc9f..4a7dd018a2c 100644 --- a/VERSION +++ b/VERSION @@ -1,4 +1,4 @@ MYSQL_VERSION_MAJOR=10 MYSQL_VERSION_MINOR=3 -MYSQL_VERSION_PATCH=27 +MYSQL_VERSION_PATCH=28 SERVER_MATURITY=stable -- cgit v1.2.1 From 940db6abc8a382bab3f708bf9873eb6496e9a5e3 Mon Sep 17 00:00:00 2001 From: Daniel Bartholomew Date: Wed, 11 Nov 2020 10:19:27 -0500 Subject: bump the VERSION --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 9de6839c534..9d0f9661bdd 100644 --- a/VERSION +++ b/VERSION @@ -1,4 +1,4 @@ MYSQL_VERSION_MAJOR=10 MYSQL_VERSION_MINOR=4 -MYSQL_VERSION_PATCH=17 +MYSQL_VERSION_PATCH=18 SERVER_MATURITY=stable -- cgit v1.2.1 From ad432ef4c0b81cf796f9b1f66848a3cde6becf77 Mon Sep 17 00:00:00 2001 From: sjaakola Date: Wed, 4 Nov 2020 13:42:18 +0200 Subject: MDEV-24119 MDL BF-BF Conflict caused by TRUNCATE TABLE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR fixes same issue as MDEV-21577 for TRUNCATE TABLE. MDEV-21577 fixed TOI replication for OPTIMIZE, REPAIR and ALTER TABLE operating on FK child table. It was later found out that also TRUNCATE has similar problem and needs a fix. The actual fix is to do FK parent table lookup before TRUNCATE TOI isolation and append found FK parent table names in certification key list for the write set. PR contains also new test scenario in galera_ddl_fk_conflict test where FK child has two FK parent tables and there are two DML transactions operating on both parent tables. For development convenience, new TO isolation macro was added: WSREP_TO_ISOLATION_BEGIN_IF and WSREP_TO_ISOLATION_BEGIN_ALTER macro was changed to skip the goto statement. Reviewed-by: Jan Lindström --- include/wsrep.h | 10 +- .../suite/galera/r/galera_ddl_fk_conflict.result | 437 ++++++++++++++++++--- .../suite/galera/t/galera_ddl_fk_conflict.inc | 100 ++++- .../suite/galera/t/galera_ddl_fk_conflict.test | 10 + sql/sql_admin.cc | 4 +- sql/sql_alter.cc | 11 +- sql/sql_truncate.cc | 20 +- 7 files changed, 512 insertions(+), 80 deletions(-) diff --git a/include/wsrep.h b/include/wsrep.h index 65cceba1065..5272b687732 100644 --- a/include/wsrep.h +++ b/include/wsrep.h @@ -23,15 +23,17 @@ #define DBUG_ASSERT_IF_WSREP(A) DBUG_ASSERT(A) #define WSREP_MYSQL_DB (char *)"mysql" -#define WSREP_TO_ISOLATION_BEGIN(db_, table_, table_list_) \ +#define WSREP_TO_ISOLATION_BEGIN_IF(db_, table_, table_list_) \ + if (WSREP_ON && WSREP(thd) && wsrep_to_isolation_begin(thd, db_, table_, table_list_)) + +#define WSREP_TO_ISOLATION_BEGIN(db_, table_, table_list_) \ if (WSREP_ON && WSREP(thd) && wsrep_to_isolation_begin(thd, db_, table_, table_list_)) \ goto wsrep_error_label; #define WSREP_TO_ISOLATION_BEGIN_ALTER(db_, table_, table_list_, alter_info_, fk_tables_) \ if (WSREP(thd) && wsrep_thd_is_local(thd) && \ wsrep_to_isolation_begin(thd, db_, table_, \ - table_list_, alter_info_, fk_tables_)) \ - goto wsrep_error_label; + table_list_, alter_info_, fk_tables_)) #define WSREP_TO_ISOLATION_END \ if ((WSREP(thd) && wsrep_thd_is_local_toi(thd)) || \ @@ -48,7 +50,7 @@ #define WSREP_TO_ISOLATION_BEGIN_FK_TABLES(db_, table_, table_list_, fk_tables) \ if (WSREP(thd) && !thd->lex->no_write_to_binlog \ - && wsrep_to_isolation_begin(thd, db_, table_, table_list_, NULL, fk_tables)) goto wsrep_error_label; + && wsrep_to_isolation_begin(thd, db_, table_, table_list_, NULL, fk_tables)) #define WSREP_DEBUG(...) \ if (wsrep_debug) WSREP_LOG(sql_print_information, ##__VA_ARGS__) diff --git a/mysql-test/suite/galera/r/galera_ddl_fk_conflict.result b/mysql-test/suite/galera/r/galera_ddl_fk_conflict.result index a39fa6a6219..f0d0d61e58f 100644 --- a/mysql-test/suite/galera/r/galera_ddl_fk_conflict.result +++ b/mysql-test/suite/galera/r/galera_ddl_fk_conflict.result @@ -3,6 +3,9 @@ connection node_1; connect node_1a, 127.0.0.1, root, , test, $NODE_MYPORT_1; connection node_1a; SET SESSION wsrep_sync_wait=0; +connect node_1b, 127.0.0.1, root, , test, $NODE_MYPORT_1; +connection node_1b; +SET SESSION wsrep_sync_wait=0; ###################################################################### # Test for OPTIMIZE ###################################################################### @@ -14,29 +17,39 @@ SET SESSION wsrep_sync_wait=0; ###################################################################### connection node_1; SET SESSION wsrep_sync_wait=0; -CREATE TABLE p (pk INTEGER PRIMARY KEY, f2 CHAR(30)); -INSERT INTO p VALUES (1, 'INITIAL VALUE'); -INSERT INTO p VALUES (2, 'INITIAL VALUE'); -CREATE TABLE c (pk INTEGER PRIMARY KEY, fk INTEGER, FOREIGN KEY (fk) REFERENCES p(pk)); -INSERT INTO c VALUES (1,1), (2,2); +CREATE TABLE p1 (pk INTEGER PRIMARY KEY, f2 CHAR(30)); +INSERT INTO p1 VALUES (1, 'INITIAL VALUE'); +CREATE TABLE p2 (pk INTEGER PRIMARY KEY, f2 CHAR(30)); +INSERT INTO p2 VALUES (1, 'INITIAL VALUE'); +INSERT INTO p2 VALUES (2, 'INITIAL VALUE'); +CREATE TABLE c1 (pk INTEGER PRIMARY KEY, fk INTEGER, FOREIGN KEY (fk) REFERENCES p1(pk)); +INSERT INTO c1 VALUES (1,1); +CREATE TABLE c2 (pk INTEGER PRIMARY KEY, fk1 INTEGER, fk2 INTEGER, FOREIGN KEY (fk1) REFERENCES p1(pk), FOREIGN KEY (fk2) REFERENCES p2(pk)); +INSERT INTO c2 VALUES (1,1,1), (2,1,2); connection node_1; SET AUTOCOMMIT=ON; START TRANSACTION; -UPDATE p SET f2 = 'TO DEADLOCK' WHERE pk = 1; +UPDATE p1 SET f2 = 'TO DEADLOCK' WHERE pk = 1; connection node_2; SET SESSION wsrep_sync_wait=0; -OPTIMIZE TABLE c ; +OPTIMIZE TABLE c1 ; Table Op Msg_type Msg_text -test.c optimize note Table does not support optimize, doing recreate + analyze instead -test.c optimize status OK +test.c1 optimize note Table does not support optimize, doing recreate + analyze instead +test.c1 optimize status OK connection node_1; COMMIT; ERROR 40001: Deadlock found when trying to get lock; try restarting transaction -SELECT COUNT(*) AS EXPECT_2 FROM p WHERE f2 = 'INITIAL VALUE'; +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; EXPECT_2 2 connection node_2; -SELECT COUNT(*) AS EXPECT_2 FROM p WHERE f2 = 'INITIAL VALUE'; +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; EXPECT_2 2 ###################################################################### @@ -49,16 +62,63 @@ connection node_1; BEGIN; SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; connection node_2; -OPTIMIZE TABLE c ; +OPTIMIZE TABLE c1 ; +Table Op Msg_type Msg_text +test.c1 optimize note Table does not support optimize, doing recreate + analyze instead +test.c1 optimize status OK +connection node_1a; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +connection node_1; +UPDATE p1 SET f2 = 'TO DEADLOCK' WHERE pk = 1; +COMMIT; +connection node_1a; +SET GLOBAL wsrep_provider_options = 'signal=apply_monitor_slave_enter_sync'; +connection node_1; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +SELECT 'I deadlocked'; +I deadlocked +I deadlocked +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +connection node_2; +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +###################################################################### +# +# Scenario #3: 2 DMLs working on two FK parent tables try to replicate, +# but fails in certification for earlier DDL on child table +# which is child to both FK parents +# +###################################################################### +connection node_1; +BEGIN; +connection node_1b; +BEGIN; +SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; +connection node_2; +OPTIMIZE TABLE c2 ; Table Op Msg_type Msg_text -test.c optimize note Table does not support optimize, doing recreate + analyze instead -test.c optimize status OK +test.c2 optimize note Table does not support optimize, doing recreate + analyze instead +test.c2 optimize status OK connection node_1a; SET SESSION wsrep_on = 0; SET SESSION wsrep_on = 1; SET GLOBAL wsrep_provider_options = 'dbug='; connection node_1; -UPDATE p SET f2 = 'TO DEADLOCK' WHERE pk = 1; +UPDATE p1 SET f2 = 'TO DEADLOCK' WHERE pk = 1; +COMMIT; +connection node_1b; +UPDATE p2 SET f2 = 'TO DEADLOCK' WHERE pk = 2; COMMIT; connection node_1a; SET GLOBAL wsrep_provider_options = 'signal=apply_monitor_slave_enter_sync'; @@ -67,15 +127,26 @@ ERROR 40001: Deadlock found when trying to get lock; try restarting transaction SELECT 'I deadlocked'; I deadlocked I deadlocked -SELECT COUNT(*) AS EXPECT_2 FROM p WHERE f2 = 'INITIAL VALUE'; +connection node_1b; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +SELECT 'I deadlocked'; +I deadlocked +I deadlocked +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; EXPECT_2 2 connection node_2; -SELECT COUNT(*) AS EXPECT_2 FROM p WHERE f2 = 'INITIAL VALUE'; +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; EXPECT_2 2 -DROP TABLE c; -DROP TABLE p; +DROP TABLE c1, c2; +DROP TABLE p1, p2; ###################################################################### # Test for REPAIR ###################################################################### @@ -87,28 +158,38 @@ DROP TABLE p; ###################################################################### connection node_1; SET SESSION wsrep_sync_wait=0; -CREATE TABLE p (pk INTEGER PRIMARY KEY, f2 CHAR(30)); -INSERT INTO p VALUES (1, 'INITIAL VALUE'); -INSERT INTO p VALUES (2, 'INITIAL VALUE'); -CREATE TABLE c (pk INTEGER PRIMARY KEY, fk INTEGER, FOREIGN KEY (fk) REFERENCES p(pk)); -INSERT INTO c VALUES (1,1), (2,2); +CREATE TABLE p1 (pk INTEGER PRIMARY KEY, f2 CHAR(30)); +INSERT INTO p1 VALUES (1, 'INITIAL VALUE'); +CREATE TABLE p2 (pk INTEGER PRIMARY KEY, f2 CHAR(30)); +INSERT INTO p2 VALUES (1, 'INITIAL VALUE'); +INSERT INTO p2 VALUES (2, 'INITIAL VALUE'); +CREATE TABLE c1 (pk INTEGER PRIMARY KEY, fk INTEGER, FOREIGN KEY (fk) REFERENCES p1(pk)); +INSERT INTO c1 VALUES (1,1); +CREATE TABLE c2 (pk INTEGER PRIMARY KEY, fk1 INTEGER, fk2 INTEGER, FOREIGN KEY (fk1) REFERENCES p1(pk), FOREIGN KEY (fk2) REFERENCES p2(pk)); +INSERT INTO c2 VALUES (1,1,1), (2,1,2); connection node_1; SET AUTOCOMMIT=ON; START TRANSACTION; -UPDATE p SET f2 = 'TO DEADLOCK' WHERE pk = 1; +UPDATE p1 SET f2 = 'TO DEADLOCK' WHERE pk = 1; connection node_2; SET SESSION wsrep_sync_wait=0; -REPAIR TABLE c ; +REPAIR TABLE c1 ; Table Op Msg_type Msg_text -test.c repair note The storage engine for the table doesn't support repair +test.c1 repair note The storage engine for the table doesn't support repair connection node_1; COMMIT; ERROR 40001: Deadlock found when trying to get lock; try restarting transaction -SELECT COUNT(*) AS EXPECT_2 FROM p WHERE f2 = 'INITIAL VALUE'; +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; EXPECT_2 2 connection node_2; -SELECT COUNT(*) AS EXPECT_2 FROM p WHERE f2 = 'INITIAL VALUE'; +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; EXPECT_2 2 ###################################################################### @@ -121,15 +202,15 @@ connection node_1; BEGIN; SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; connection node_2; -REPAIR TABLE c ; +REPAIR TABLE c1 ; Table Op Msg_type Msg_text -test.c repair note The storage engine for the table doesn't support repair +test.c1 repair note The storage engine for the table doesn't support repair connection node_1a; SET SESSION wsrep_on = 0; SET SESSION wsrep_on = 1; SET GLOBAL wsrep_provider_options = 'dbug='; connection node_1; -UPDATE p SET f2 = 'TO DEADLOCK' WHERE pk = 1; +UPDATE p1 SET f2 = 'TO DEADLOCK' WHERE pk = 1; COMMIT; connection node_1a; SET GLOBAL wsrep_provider_options = 'signal=apply_monitor_slave_enter_sync'; @@ -138,15 +219,72 @@ ERROR 40001: Deadlock found when trying to get lock; try restarting transaction SELECT 'I deadlocked'; I deadlocked I deadlocked -SELECT COUNT(*) AS EXPECT_2 FROM p WHERE f2 = 'INITIAL VALUE'; +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; EXPECT_2 2 connection node_2; -SELECT COUNT(*) AS EXPECT_2 FROM p WHERE f2 = 'INITIAL VALUE'; +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; EXPECT_2 2 -DROP TABLE c; -DROP TABLE p; +###################################################################### +# +# Scenario #3: 2 DMLs working on two FK parent tables try to replicate, +# but fails in certification for earlier DDL on child table +# which is child to both FK parents +# +###################################################################### +connection node_1; +BEGIN; +connection node_1b; +BEGIN; +SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; +connection node_2; +REPAIR TABLE c2 ; +Table Op Msg_type Msg_text +test.c2 repair note The storage engine for the table doesn't support repair +connection node_1a; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +connection node_1; +UPDATE p1 SET f2 = 'TO DEADLOCK' WHERE pk = 1; +COMMIT; +connection node_1b; +UPDATE p2 SET f2 = 'TO DEADLOCK' WHERE pk = 2; +COMMIT; +connection node_1a; +SET GLOBAL wsrep_provider_options = 'signal=apply_monitor_slave_enter_sync'; +connection node_1; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +SELECT 'I deadlocked'; +I deadlocked +I deadlocked +connection node_1b; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +SELECT 'I deadlocked'; +I deadlocked +I deadlocked +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +connection node_2; +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +DROP TABLE c1, c2; +DROP TABLE p1, p2; ###################################################################### # Test for ALTER ENGINE=INNODB ###################################################################### @@ -158,26 +296,168 @@ DROP TABLE p; ###################################################################### connection node_1; SET SESSION wsrep_sync_wait=0; -CREATE TABLE p (pk INTEGER PRIMARY KEY, f2 CHAR(30)); -INSERT INTO p VALUES (1, 'INITIAL VALUE'); -INSERT INTO p VALUES (2, 'INITIAL VALUE'); -CREATE TABLE c (pk INTEGER PRIMARY KEY, fk INTEGER, FOREIGN KEY (fk) REFERENCES p(pk)); -INSERT INTO c VALUES (1,1), (2,2); +CREATE TABLE p1 (pk INTEGER PRIMARY KEY, f2 CHAR(30)); +INSERT INTO p1 VALUES (1, 'INITIAL VALUE'); +CREATE TABLE p2 (pk INTEGER PRIMARY KEY, f2 CHAR(30)); +INSERT INTO p2 VALUES (1, 'INITIAL VALUE'); +INSERT INTO p2 VALUES (2, 'INITIAL VALUE'); +CREATE TABLE c1 (pk INTEGER PRIMARY KEY, fk INTEGER, FOREIGN KEY (fk) REFERENCES p1(pk)); +INSERT INTO c1 VALUES (1,1); +CREATE TABLE c2 (pk INTEGER PRIMARY KEY, fk1 INTEGER, fk2 INTEGER, FOREIGN KEY (fk1) REFERENCES p1(pk), FOREIGN KEY (fk2) REFERENCES p2(pk)); +INSERT INTO c2 VALUES (1,1,1), (2,1,2); +connection node_1; +SET AUTOCOMMIT=ON; +START TRANSACTION; +UPDATE p1 SET f2 = 'TO DEADLOCK' WHERE pk = 1; +connection node_2; +SET SESSION wsrep_sync_wait=0; +ALTER TABLE c1 ENGINE=INNODB; +connection node_1; +COMMIT; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +connection node_2; +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +###################################################################### +# +# Scenario #2: DML working on FK parent table tries to replicate, but +# fails in certification for earlier DDL on child table +# +###################################################################### +connection node_1; +BEGIN; +SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; +connection node_2; +ALTER TABLE c1 ENGINE=INNODB; +connection node_1a; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +connection node_1; +UPDATE p1 SET f2 = 'TO DEADLOCK' WHERE pk = 1; +COMMIT; +connection node_1a; +SET GLOBAL wsrep_provider_options = 'signal=apply_monitor_slave_enter_sync'; +connection node_1; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +SELECT 'I deadlocked'; +I deadlocked +I deadlocked +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +connection node_2; +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +###################################################################### +# +# Scenario #3: 2 DMLs working on two FK parent tables try to replicate, +# but fails in certification for earlier DDL on child table +# which is child to both FK parents +# +###################################################################### +connection node_1; +BEGIN; +connection node_1b; +BEGIN; +SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; +connection node_2; +ALTER TABLE c2 ENGINE=INNODB; +connection node_1a; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +connection node_1; +UPDATE p1 SET f2 = 'TO DEADLOCK' WHERE pk = 1; +COMMIT; +connection node_1b; +UPDATE p2 SET f2 = 'TO DEADLOCK' WHERE pk = 2; +COMMIT; +connection node_1a; +SET GLOBAL wsrep_provider_options = 'signal=apply_monitor_slave_enter_sync'; +connection node_1; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +SELECT 'I deadlocked'; +I deadlocked +I deadlocked +connection node_1b; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +SELECT 'I deadlocked'; +I deadlocked +I deadlocked +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +connection node_2; +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +DROP TABLE c1, c2; +DROP TABLE p1, p2; +###################################################################### +# Test for TRUNCATE +###################################################################### +###################################################################### +# +# Scenario #1: DML working on FK parent table BF aborted by DDL +# over child table +# +###################################################################### +connection node_1; +SET SESSION wsrep_sync_wait=0; +CREATE TABLE p1 (pk INTEGER PRIMARY KEY, f2 CHAR(30)); +INSERT INTO p1 VALUES (1, 'INITIAL VALUE'); +CREATE TABLE p2 (pk INTEGER PRIMARY KEY, f2 CHAR(30)); +INSERT INTO p2 VALUES (1, 'INITIAL VALUE'); +INSERT INTO p2 VALUES (2, 'INITIAL VALUE'); +CREATE TABLE c1 (pk INTEGER PRIMARY KEY, fk INTEGER, FOREIGN KEY (fk) REFERENCES p1(pk)); +INSERT INTO c1 VALUES (1,1); +CREATE TABLE c2 (pk INTEGER PRIMARY KEY, fk1 INTEGER, fk2 INTEGER, FOREIGN KEY (fk1) REFERENCES p1(pk), FOREIGN KEY (fk2) REFERENCES p2(pk)); +INSERT INTO c2 VALUES (1,1,1), (2,1,2); connection node_1; SET AUTOCOMMIT=ON; START TRANSACTION; -UPDATE p SET f2 = 'TO DEADLOCK' WHERE pk = 1; +UPDATE p1 SET f2 = 'TO DEADLOCK' WHERE pk = 1; connection node_2; SET SESSION wsrep_sync_wait=0; -ALTER TABLE c ENGINE=INNODB; +TRUNCATE TABLE c1 ; connection node_1; COMMIT; ERROR 40001: Deadlock found when trying to get lock; try restarting transaction -SELECT COUNT(*) AS EXPECT_2 FROM p WHERE f2 = 'INITIAL VALUE'; +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; EXPECT_2 2 connection node_2; -SELECT COUNT(*) AS EXPECT_2 FROM p WHERE f2 = 'INITIAL VALUE'; +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; EXPECT_2 2 ###################################################################### @@ -190,13 +470,13 @@ connection node_1; BEGIN; SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; connection node_2; -ALTER TABLE c ENGINE=INNODB; +TRUNCATE TABLE c1 ; connection node_1a; SET SESSION wsrep_on = 0; SET SESSION wsrep_on = 1; SET GLOBAL wsrep_provider_options = 'dbug='; connection node_1; -UPDATE p SET f2 = 'TO DEADLOCK' WHERE pk = 1; +UPDATE p1 SET f2 = 'TO DEADLOCK' WHERE pk = 1; COMMIT; connection node_1a; SET GLOBAL wsrep_provider_options = 'signal=apply_monitor_slave_enter_sync'; @@ -205,12 +485,67 @@ ERROR 40001: Deadlock found when trying to get lock; try restarting transaction SELECT 'I deadlocked'; I deadlocked I deadlocked -SELECT COUNT(*) AS EXPECT_2 FROM p WHERE f2 = 'INITIAL VALUE'; +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +connection node_2; +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +###################################################################### +# +# Scenario #3: 2 DMLs working on two FK parent tables try to replicate, +# but fails in certification for earlier DDL on child table +# which is child to both FK parents +# +###################################################################### +connection node_1; +BEGIN; +connection node_1b; +BEGIN; +SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; +connection node_2; +TRUNCATE TABLE c2 ; +connection node_1a; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +connection node_1; +UPDATE p1 SET f2 = 'TO DEADLOCK' WHERE pk = 1; +COMMIT; +connection node_1b; +UPDATE p2 SET f2 = 'TO DEADLOCK' WHERE pk = 2; +COMMIT; +connection node_1a; +SET GLOBAL wsrep_provider_options = 'signal=apply_monitor_slave_enter_sync'; +connection node_1; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +SELECT 'I deadlocked'; +I deadlocked +I deadlocked +connection node_1b; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +SELECT 'I deadlocked'; +I deadlocked +I deadlocked +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; EXPECT_2 2 connection node_2; -SELECT COUNT(*) AS EXPECT_2 FROM p WHERE f2 = 'INITIAL VALUE'; +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; EXPECT_2 2 -DROP TABLE c; -DROP TABLE p; +DROP TABLE c1, c2; +DROP TABLE p1, p2; diff --git a/mysql-test/suite/galera/t/galera_ddl_fk_conflict.inc b/mysql-test/suite/galera/t/galera_ddl_fk_conflict.inc index 18271d0b641..4a473d3776c 100644 --- a/mysql-test/suite/galera/t/galera_ddl_fk_conflict.inc +++ b/mysql-test/suite/galera/t/galera_ddl_fk_conflict.inc @@ -40,31 +40,40 @@ --connection node_1 SET SESSION wsrep_sync_wait=0; -CREATE TABLE p (pk INTEGER PRIMARY KEY, f2 CHAR(30)); -INSERT INTO p VALUES (1, 'INITIAL VALUE'); -INSERT INTO p VALUES (2, 'INITIAL VALUE'); +CREATE TABLE p1 (pk INTEGER PRIMARY KEY, f2 CHAR(30)); +INSERT INTO p1 VALUES (1, 'INITIAL VALUE'); -CREATE TABLE c (pk INTEGER PRIMARY KEY, fk INTEGER, FOREIGN KEY (fk) REFERENCES p(pk)); -INSERT INTO c VALUES (1,1), (2,2); + +CREATE TABLE p2 (pk INTEGER PRIMARY KEY, f2 CHAR(30)); +INSERT INTO p2 VALUES (1, 'INITIAL VALUE'); +INSERT INTO p2 VALUES (2, 'INITIAL VALUE'); + +CREATE TABLE c1 (pk INTEGER PRIMARY KEY, fk INTEGER, FOREIGN KEY (fk) REFERENCES p1(pk)); +INSERT INTO c1 VALUES (1,1); + +CREATE TABLE c2 (pk INTEGER PRIMARY KEY, fk1 INTEGER, fk2 INTEGER, FOREIGN KEY (fk1) REFERENCES p1(pk), FOREIGN KEY (fk2) REFERENCES p2(pk)); +INSERT INTO c2 VALUES (1,1,1), (2,1,2); --connection node_1 SET AUTOCOMMIT=ON; START TRANSACTION; -UPDATE p SET f2 = 'TO DEADLOCK' WHERE pk = 1; +UPDATE p1 SET f2 = 'TO DEADLOCK' WHERE pk = 1; --connection node_2 SET SESSION wsrep_sync_wait=0; ---eval $table_admin_command TABLE c $table_admin_command_end +--eval $table_admin_command TABLE c1 $table_admin_command_end --connection node_1 --error ER_LOCK_DEADLOCK COMMIT; -SELECT COUNT(*) AS EXPECT_2 FROM p WHERE f2 = 'INITIAL VALUE'; +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; --connection node_2 -SELECT COUNT(*) AS EXPECT_2 FROM p WHERE f2 = 'INITIAL VALUE'; +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; --echo ###################################################################### --echo # @@ -81,7 +90,7 @@ BEGIN; --source include/galera_set_sync_point.inc --connection node_2 ---eval $table_admin_command TABLE c $table_admin_command_end +--eval $table_admin_command TABLE c1 $table_admin_command_end --connection node_1a --source include/galera_wait_sync_point.inc @@ -89,7 +98,62 @@ BEGIN; --let $expected_cert_failures = `SELECT VARIABLE_VALUE+1 FROM information_schema.global_status WHERE VARIABLE_NAME = 'wsrep_local_cert_failures'` --connection node_1 -UPDATE p SET f2 = 'TO DEADLOCK' WHERE pk = 1; +UPDATE p1 SET f2 = 'TO DEADLOCK' WHERE pk = 1; +--send COMMIT + +--connection node_1a +--let $galera_sync_point = apply_monitor_slave_enter_sync +--source include/galera_signal_sync_point.inc + +--let $wait_condition = SELECT VARIABLE_VALUE = $expected_cert_failures FROM information_schema.global_status WHERE VARIABLE_NAME = 'wsrep_local_cert_failures' +--source include/wait_condition.inc + +--connection node_1 +--error ER_LOCK_DEADLOCK +--reap + +SELECT 'I deadlocked'; + +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; + +--connection node_2 +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; + + +--echo ###################################################################### +--echo # +--echo # Scenario #3: 2 DMLs working on two FK parent tables try to replicate, +--echo # but fails in certification for earlier DDL on child table +--echo # which is child to both FK parents +--echo # +--echo ###################################################################### + +--connection node_1 +BEGIN; + +--connection node_1b +BEGIN; + +# Block the applier on node #1 and issue DDL on node 2 +--let $galera_sync_point = apply_monitor_slave_enter_sync +--source include/galera_set_sync_point.inc + +--connection node_2 +--eval $table_admin_command TABLE c2 $table_admin_command_end + +--connection node_1a +--source include/galera_wait_sync_point.inc +--source include/galera_clear_sync_point.inc +--let $expected_cert_failures = `SELECT VARIABLE_VALUE+2 FROM information_schema.global_status WHERE VARIABLE_NAME = 'wsrep_local_cert_failures'` + +--connection node_1 +UPDATE p1 SET f2 = 'TO DEADLOCK' WHERE pk = 1; +--send COMMIT + +--connection node_1b +UPDATE p2 SET f2 = 'TO DEADLOCK' WHERE pk = 2; --send COMMIT --connection node_1a @@ -102,13 +166,19 @@ UPDATE p SET f2 = 'TO DEADLOCK' WHERE pk = 1; --connection node_1 --error ER_LOCK_DEADLOCK --reap +SELECT 'I deadlocked'; +--connection node_1b +--error ER_LOCK_DEADLOCK +--reap SELECT 'I deadlocked'; -SELECT COUNT(*) AS EXPECT_2 FROM p WHERE f2 = 'INITIAL VALUE'; +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; --connection node_2 -SELECT COUNT(*) AS EXPECT_2 FROM p WHERE f2 = 'INITIAL VALUE'; +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; -DROP TABLE c; -DROP TABLE p; +DROP TABLE c1, c2; +DROP TABLE p1, p2; diff --git a/mysql-test/suite/galera/t/galera_ddl_fk_conflict.test b/mysql-test/suite/galera/t/galera_ddl_fk_conflict.test index a07006a8e49..c6d20e3bffd 100644 --- a/mysql-test/suite/galera/t/galera_ddl_fk_conflict.test +++ b/mysql-test/suite/galera/t/galera_ddl_fk_conflict.test @@ -7,10 +7,16 @@ --source include/have_debug_sync.inc --source include/galera_have_debug_sync.inc +# sync point controlling session --connect node_1a, 127.0.0.1, root, , test, $NODE_MYPORT_1 --connection node_1a SET SESSION wsrep_sync_wait=0; +# secondary conflicting DML victim session +--connect node_1b, 127.0.0.1, root, , test, $NODE_MYPORT_1 +--connection node_1b +SET SESSION wsrep_sync_wait=0; + --let $table_admin_command = OPTIMIZE --source galera_ddl_fk_conflict.inc @@ -21,5 +27,9 @@ SET SESSION wsrep_sync_wait=0; --let $table_admin_command_end = ENGINE=INNODB --source galera_ddl_fk_conflict.inc +--let $table_admin_command = TRUNCATE +--let $table_admin_command_end = +--source galera_ddl_fk_conflict.inc + # CHECK and ANALYZE are not affected diff --git a/sql/sql_admin.cc b/sql/sql_admin.cc index ebd0e9b39aa..c13d61d9f7a 100644 --- a/sql/sql_admin.cc +++ b/sql/sql_admin.cc @@ -453,7 +453,9 @@ static bool wsrep_toi_replication(THD *thd, TABLE_LIST *tables) { WSREP_TO_ISOLATION_BEGIN_WRTCHK(NULL, NULL, tables); } else { - WSREP_TO_ISOLATION_BEGIN_FK_TABLES(NULL, NULL, tables, &keys); + WSREP_TO_ISOLATION_BEGIN_FK_TABLES(NULL, NULL, tables, &keys) { + return true; + } } return false; diff --git a/sql/sql_alter.cc b/sql/sql_alter.cc index cefd37811a2..2bbc8169df2 100644 --- a/sql/sql_alter.cc +++ b/sql/sql_alter.cc @@ -480,7 +480,11 @@ bool Sql_cmd_alter_table::execute(THD *thd) WSREP_TO_ISOLATION_BEGIN_ALTER((lex->name.str ? select_lex->db.str : NULL), (lex->name.str ? lex->name.str : NULL), - first_table, &alter_info, &keys); + first_table, &alter_info, &keys) + { + WSREP_WARN("ALTER TABLE isolation failure"); + DBUG_RETURN(TRUE); + } thd->variables.auto_increment_offset = 1; thd->variables.auto_increment_increment = 1; @@ -522,11 +526,6 @@ bool Sql_cmd_alter_table::execute(THD *thd) lex->ignore); DBUG_RETURN(result); -#ifdef WITH_WSREP -wsrep_error_label: - WSREP_WARN("ALTER TABLE isolation failure"); - DBUG_RETURN(TRUE); -#endif } bool Sql_cmd_discard_import_tablespace::execute(THD *thd) diff --git a/sql/sql_truncate.cc b/sql/sql_truncate.cc index bfcdda6e0e9..c3d347307a2 100644 --- a/sql/sql_truncate.cc +++ b/sql/sql_truncate.cc @@ -416,9 +416,23 @@ bool Sql_cmd_truncate_table::truncate_table(THD *thd, TABLE_LIST *table_ref) bool hton_can_recreate; #ifdef WITH_WSREP - if (WSREP(thd) && - wsrep_to_isolation_begin(thd, table_ref->db.str, table_ref->table_name.str, 0)) - DBUG_RETURN(TRUE); + if (WSREP(thd)) + { + wsrep::key_array keys; + wsrep_append_fk_parent_table(thd, table_ref, &keys); + if (keys.empty()) + { + WSREP_TO_ISOLATION_BEGIN_IF(table_ref->db.str, table_ref->table_name.str, NULL) + { + DBUG_RETURN(TRUE); + } + } else { + WSREP_TO_ISOLATION_BEGIN_FK_TABLES(NULL, NULL, table_ref, &keys) + { + DBUG_RETURN(TRUE); + } + } + } #endif /* WITH_WSREP */ if (lock_table(thd, table_ref, &hton_can_recreate)) DBUG_RETURN(TRUE); -- cgit v1.2.1 From 2fbcddbeafb558720f4b74b0a8f68c18b48d9f2e Mon Sep 17 00:00:00 2001 From: sjaakola Date: Mon, 9 Nov 2020 12:41:52 +0200 Subject: MDEV-24119 MDL BF-BF Conflict caused by TRUNCATE TABLE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A follow-up fix, for original fix for MDEV-21577, which did not handle well temporary tables. OPTIMIZE and REPAIR TABLE statements can take a list of tables as argument, and some of the tables may be temporary. Proper handling of temporary tables is to skip them and continue working on the real tables. The bad version, skipped all tables, if a single temporary table was in the argument list. And this resulted so that FK parent tables were not scnanned for the remaining real tables. Some mtr tests, using OPTIMIZE or REPAIR for temporary tables caused regressions bacause of this, e.g. galera.galera_optimize_analyze_multi The fix in this PR opens temporary and real tables first, and in the table handling loop skips temporary tables, FK parent scanning is done only for real tables. The test has new scenario for OPTIMIZE and REPAIR issued for two tables of which one is temporary table. Reviewed-by: Jan Lindström --- .../suite/galera/r/galera_ddl_fk_conflict.result | 106 +++++++++++++++++++++ .../suite/galera/t/galera_ddl_fk_conflict.inc | 20 ++-- .../suite/galera/t/galera_ddl_fk_conflict.test | 2 + .../galera/t/galera_ddl_fk_conflict_with_tmp.inc | 69 ++++++++++++++ sql/wsrep_mysqld.cc | 35 ++++--- 5 files changed, 207 insertions(+), 25 deletions(-) create mode 100644 mysql-test/suite/galera/t/galera_ddl_fk_conflict_with_tmp.inc diff --git a/mysql-test/suite/galera/r/galera_ddl_fk_conflict.result b/mysql-test/suite/galera/r/galera_ddl_fk_conflict.result index f0d0d61e58f..5f09345b79b 100644 --- a/mysql-test/suite/galera/r/galera_ddl_fk_conflict.result +++ b/mysql-test/suite/galera/r/galera_ddl_fk_conflict.result @@ -104,6 +104,7 @@ connection node_1; BEGIN; connection node_1b; BEGIN; +connection node_1a; SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; connection node_2; OPTIMIZE TABLE c2 ; @@ -148,6 +149,58 @@ EXPECT_2 DROP TABLE c1, c2; DROP TABLE p1, p2; ###################################################################### +# Test for OPTIMIZE +###################################################################### +connection node_1; +SET SESSION wsrep_sync_wait=0; +CREATE TABLE p1 (pk INTEGER PRIMARY KEY, f2 CHAR(30)); +INSERT INTO p1 VALUES (1, 'INITIAL VALUE'); +CREATE TABLE c1 (pk INTEGER PRIMARY KEY, fk INTEGER, FOREIGN KEY (fk) REFERENCES p1(pk)); +INSERT INTO c1 VALUES (1,1); +###################################################################### +# +# Scenario #4: DML working on FK parent table tries to replicate, but +# fails in certification for earlier DDL on child table +# and another temporary table. TMP table should be skipped +# but FK child table should be replicated with proper keys +# +###################################################################### +connection node_1; +BEGIN; +SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; +connection node_2; +CREATE TEMPORARY TABLE tmp (i int); +OPTIMIZE TABLE c1, tmp ; +Table Op Msg_type Msg_text +test.c1 optimize note Table does not support optimize, doing recreate + analyze instead +test.c1 optimize status OK +test.tmp optimize note Table does not support optimize, doing recreate + analyze instead +test.tmp optimize status OK +DROP TABLE tmp; +connection node_1a; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +connection node_1; +UPDATE p1 SET f2 = 'TO DEADLOCK' WHERE pk = 1; +COMMIT; +connection node_1a; +SET GLOBAL wsrep_provider_options = 'signal=apply_monitor_slave_enter_sync'; +connection node_1; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +SELECT 'I deadlocked'; +I deadlocked +I deadlocked +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +connection node_2; +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +DROP TABLE c1; +DROP TABLE p1; +###################################################################### # Test for REPAIR ###################################################################### ###################################################################### @@ -243,6 +296,7 @@ connection node_1; BEGIN; connection node_1b; BEGIN; +connection node_1a; SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; connection node_2; REPAIR TABLE c2 ; @@ -286,6 +340,56 @@ EXPECT_2 DROP TABLE c1, c2; DROP TABLE p1, p2; ###################################################################### +# Test for REPAIR +###################################################################### +connection node_1; +SET SESSION wsrep_sync_wait=0; +CREATE TABLE p1 (pk INTEGER PRIMARY KEY, f2 CHAR(30)); +INSERT INTO p1 VALUES (1, 'INITIAL VALUE'); +CREATE TABLE c1 (pk INTEGER PRIMARY KEY, fk INTEGER, FOREIGN KEY (fk) REFERENCES p1(pk)); +INSERT INTO c1 VALUES (1,1); +###################################################################### +# +# Scenario #4: DML working on FK parent table tries to replicate, but +# fails in certification for earlier DDL on child table +# and another temporary table. TMP table should be skipped +# but FK child table should be replicated with proper keys +# +###################################################################### +connection node_1; +BEGIN; +SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; +connection node_2; +CREATE TEMPORARY TABLE tmp (i int); +REPAIR TABLE c1, tmp ; +Table Op Msg_type Msg_text +test.c1 repair note The storage engine for the table doesn't support repair +test.tmp repair note The storage engine for the table doesn't support repair +DROP TABLE tmp; +connection node_1a; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +connection node_1; +UPDATE p1 SET f2 = 'TO DEADLOCK' WHERE pk = 1; +COMMIT; +connection node_1a; +SET GLOBAL wsrep_provider_options = 'signal=apply_monitor_slave_enter_sync'; +connection node_1; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +SELECT 'I deadlocked'; +I deadlocked +I deadlocked +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +connection node_2; +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +DROP TABLE c1; +DROP TABLE p1; +###################################################################### # Test for ALTER ENGINE=INNODB ###################################################################### ###################################################################### @@ -377,6 +481,7 @@ connection node_1; BEGIN; connection node_1b; BEGIN; +connection node_1a; SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; connection node_2; ALTER TABLE c2 ENGINE=INNODB; @@ -509,6 +614,7 @@ connection node_1; BEGIN; connection node_1b; BEGIN; +connection node_1a; SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; connection node_2; TRUNCATE TABLE c2 ; diff --git a/mysql-test/suite/galera/t/galera_ddl_fk_conflict.inc b/mysql-test/suite/galera/t/galera_ddl_fk_conflict.inc index 4a473d3776c..06b7bbe41c4 100644 --- a/mysql-test/suite/galera/t/galera_ddl_fk_conflict.inc +++ b/mysql-test/suite/galera/t/galera_ddl_fk_conflict.inc @@ -62,6 +62,13 @@ UPDATE p1 SET f2 = 'TO DEADLOCK' WHERE pk = 1; --connection node_2 SET SESSION wsrep_sync_wait=0; +# wait for tables to be created in node 2 and all rows inserted as well +--let $wait_condition = SELECT COUNT(*) = 2 FROM INFORMATION_SCHEMA.INNODB_SYS_TABLES WHERE NAME LIKE 'test/c%' +--source include/wait_condition.inc +--let $wait_condition = SELECT COUNT(*) = 2 FROM c2 +--source include/wait_condition.inc + +# replicate the DDL to be tested --eval $table_admin_command TABLE c1 $table_admin_command_end --connection node_1 @@ -102,12 +109,12 @@ UPDATE p1 SET f2 = 'TO DEADLOCK' WHERE pk = 1; --send COMMIT --connection node_1a ---let $galera_sync_point = apply_monitor_slave_enter_sync ---source include/galera_signal_sync_point.inc - --let $wait_condition = SELECT VARIABLE_VALUE = $expected_cert_failures FROM information_schema.global_status WHERE VARIABLE_NAME = 'wsrep_local_cert_failures' --source include/wait_condition.inc +--let $galera_sync_point = apply_monitor_slave_enter_sync +--source include/galera_signal_sync_point.inc + --connection node_1 --error ER_LOCK_DEADLOCK --reap @@ -136,6 +143,7 @@ BEGIN; --connection node_1b BEGIN; +--connection node_1a # Block the applier on node #1 and issue DDL on node 2 --let $galera_sync_point = apply_monitor_slave_enter_sync --source include/galera_set_sync_point.inc @@ -157,12 +165,12 @@ UPDATE p2 SET f2 = 'TO DEADLOCK' WHERE pk = 2; --send COMMIT --connection node_1a ---let $galera_sync_point = apply_monitor_slave_enter_sync ---source include/galera_signal_sync_point.inc - --let $wait_condition = SELECT VARIABLE_VALUE = $expected_cert_failures FROM information_schema.global_status WHERE VARIABLE_NAME = 'wsrep_local_cert_failures' --source include/wait_condition.inc +--let $galera_sync_point = apply_monitor_slave_enter_sync +--source include/galera_signal_sync_point.inc + --connection node_1 --error ER_LOCK_DEADLOCK --reap diff --git a/mysql-test/suite/galera/t/galera_ddl_fk_conflict.test b/mysql-test/suite/galera/t/galera_ddl_fk_conflict.test index c6d20e3bffd..88837933e5a 100644 --- a/mysql-test/suite/galera/t/galera_ddl_fk_conflict.test +++ b/mysql-test/suite/galera/t/galera_ddl_fk_conflict.test @@ -19,9 +19,11 @@ SET SESSION wsrep_sync_wait=0; --let $table_admin_command = OPTIMIZE --source galera_ddl_fk_conflict.inc +--source galera_ddl_fk_conflict_with_tmp.inc --let $table_admin_command = REPAIR --source galera_ddl_fk_conflict.inc +--source galera_ddl_fk_conflict_with_tmp.inc --let $table_admin_command = ALTER --let $table_admin_command_end = ENGINE=INNODB diff --git a/mysql-test/suite/galera/t/galera_ddl_fk_conflict_with_tmp.inc b/mysql-test/suite/galera/t/galera_ddl_fk_conflict_with_tmp.inc new file mode 100644 index 00000000000..acf3c54180b --- /dev/null +++ b/mysql-test/suite/galera/t/galera_ddl_fk_conflict_with_tmp.inc @@ -0,0 +1,69 @@ +--echo ###################################################################### +--echo # Test for $table_admin_command $table_admin_command_end +--echo ###################################################################### + + +--connection node_1 +SET SESSION wsrep_sync_wait=0; + +CREATE TABLE p1 (pk INTEGER PRIMARY KEY, f2 CHAR(30)); +INSERT INTO p1 VALUES (1, 'INITIAL VALUE'); + + +CREATE TABLE c1 (pk INTEGER PRIMARY KEY, fk INTEGER, FOREIGN KEY (fk) REFERENCES p1(pk)); +INSERT INTO c1 VALUES (1,1); + +--echo ###################################################################### +--echo # +--echo # Scenario #4: DML working on FK parent table tries to replicate, but +--echo # fails in certification for earlier DDL on child table +--echo # and another temporary table. TMP table should be skipped +--echo # but FK child table should be replicated with proper keys +--echo # +--echo ###################################################################### + +--connection node_1 +BEGIN; + +# Block the applier on node #1 and issue DDL on node 2 +--let $galera_sync_point = apply_monitor_slave_enter_sync +--source include/galera_set_sync_point.inc + +--connection node_2 +# wait for tables to be created in node 2 and all rows inserted as well +--let $wait_condition = SELECT COUNT(*) = 1 FROM INFORMATION_SCHEMA.INNODB_SYS_TABLES WHERE NAME LIKE 'test/c1' +--source include/wait_condition.inc +--let $wait_condition = SELECT COUNT(*) = 1 FROM c1 +--source include/wait_condition.inc +CREATE TEMPORARY TABLE tmp (i int); +--eval $table_admin_command TABLE c1, tmp $table_admin_command_end +DROP TABLE tmp; + +--connection node_1a +--source include/galera_wait_sync_point.inc +--source include/galera_clear_sync_point.inc +--let $expected_cert_failures = `SELECT VARIABLE_VALUE+1 FROM information_schema.global_status WHERE VARIABLE_NAME = 'wsrep_local_cert_failures'` + +--connection node_1 +UPDATE p1 SET f2 = 'TO DEADLOCK' WHERE pk = 1; +--send COMMIT + +--connection node_1a +--let $wait_condition = SELECT VARIABLE_VALUE = $expected_cert_failures FROM information_schema.global_status WHERE VARIABLE_NAME = 'wsrep_local_cert_failures' +--source include/wait_condition.inc + +--let $galera_sync_point = apply_monitor_slave_enter_sync +--source include/galera_signal_sync_point.inc + +--connection node_1 +--error ER_LOCK_DEADLOCK +--reap + +SELECT 'I deadlocked'; +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; + +--connection node_2 +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; + +DROP TABLE c1; +DROP TABLE p1; diff --git a/sql/wsrep_mysqld.cc b/sql/wsrep_mysqld.cc index 304da7ec979..99a8105efd9 100644 --- a/sql/wsrep_mysqld.cc +++ b/sql/wsrep_mysqld.cc @@ -1191,30 +1191,27 @@ wsrep_append_fk_parent_table(THD* thd, TABLE_LIST* tables, wsrep::key_array* key uint counter; MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint(); - bool open_error= - open_tables(thd, &tables, &counter, MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL); - if (unlikely(open_error && (thd->killed || thd->is_error()))) + if (thd->open_temporary_tables(tables) || + open_tables(thd, &tables, &counter, MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL)) { - WSREP_WARN("unable to open table for FK checks in OPTIMIZE/REPAIR/ALTER processing"); + WSREP_DEBUG("unable to open table for FK checks for %s", thd->query()); } - else + + for (table= tables; table; table= table->next_local) { - for (table= tables; table; table= table->next_local) + if (!is_temporary_table(table) && table->table) { - if (table->table) + FOREIGN_KEY_INFO *f_key_info; + List f_key_list; + + table->table->file->get_foreign_key_list(thd, &f_key_list); + List_iterator_fast it(f_key_list); + while ((f_key_info=it++)) { - FOREIGN_KEY_INFO *f_key_info; - List f_key_list; - - table->table->file->get_foreign_key_list(thd, &f_key_list); - List_iterator_fast it(f_key_list); - while ((f_key_info=it++)) - { - WSREP_DEBUG("appended fkey %s", f_key_info->referenced_table->str); - keys->push_back(wsrep_prepare_key_for_toi(f_key_info->referenced_db->str, - f_key_info->referenced_table->str, - wsrep::key::shared)); - } + WSREP_DEBUG("appended fkey %s", f_key_info->referenced_table->str); + keys->push_back(wsrep_prepare_key_for_toi(f_key_info->referenced_db->str, + f_key_info->referenced_table->str, + wsrep::key::shared)); } } } -- cgit v1.2.1 From 984a06db2ce2b2e3c7c5028245905417f2141cd7 Mon Sep 17 00:00:00 2001 From: Sujatha Date: Thu, 12 Nov 2020 13:04:39 +0530 Subject: MDEV-4633: multi_source.simple test fails sporadically Analysis: ======== Writes to 'rli->log_space_total' needs to be synchronized, otherwise both SQL_THREAD and IO_THREAD can try to modify the variable simultaneously resulting in incorrect rli->log_space_total. In the current test scenario SQL_THREAD is trying to decrement 'rli->log_space_total' in 'purge_first_log' and IO_THREAD is trying to increment the 'rli->log_space_total' in 'queue_event' simultaneously. Hence test occasionally fails with result mismatch. Fix: === Convert 'rli->log_space_total' variable to atomic type. --- sql/log.cc | 4 +++- sql/log.h | 4 +++- sql/rpl_rli.cc | 10 ++++++---- sql/slave.cc | 19 +++++++++++++++---- 4 files changed, 27 insertions(+), 10 deletions(-) diff --git a/sql/log.cc b/sql/log.cc index 54a8bd9552f..dc8df9c6fdb 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -4397,7 +4397,9 @@ int MYSQL_BIN_LOG::purge_first_log(Relay_log_info* rli, bool included) 0, 0, &log_space_reclaimed); mysql_mutex_lock(&rli->log_space_lock); - rli->log_space_total-= log_space_reclaimed; + my_atomic_add64_explicit((volatile int64*)(&rli->log_space_total), + (-(int64)log_space_reclaimed), + MY_MEMORY_ORDER_RELAXED); mysql_cond_broadcast(&rli->log_space_cond); mysql_mutex_unlock(&rli->log_space_lock); diff --git a/sql/log.h b/sql/log.h index 0e6b2c895af..0770861fe01 100644 --- a/sql/log.h +++ b/sql/log.h @@ -716,7 +716,9 @@ public: char buf1[22],buf2[22]; #endif DBUG_ENTER("harvest_bytes_written"); - (*counter)+=bytes_written; + + my_atomic_add64_explicit((volatile int64*)(counter), bytes_written, + MY_MEMORY_ORDER_RELAXED); DBUG_PRINT("info",("counter: %s bytes_written: %s", llstr(*counter,buf1), llstr(bytes_written,buf2))); bytes_written=0; diff --git a/sql/rpl_rli.cc b/sql/rpl_rli.cc index 99f8da2c928..d2d1bc3421d 100644 --- a/sql/rpl_rli.cc +++ b/sql/rpl_rli.cc @@ -454,7 +454,8 @@ static inline int add_relay_log(Relay_log_info* rli,LOG_INFO* linfo) linfo->log_file_name); DBUG_RETURN(1); } - rli->log_space_total += s.st_size; + my_atomic_add64_explicit((volatile int64*)(&rli->log_space_total), + s.st_size, MY_MEMORY_ORDER_RELAXED); DBUG_PRINT("info",("log_space_total: %llu", rli->log_space_total)); DBUG_RETURN(0); } @@ -464,7 +465,8 @@ static int count_relay_log_space(Relay_log_info* rli) { LOG_INFO linfo; DBUG_ENTER("count_relay_log_space"); - rli->log_space_total= 0; + my_atomic_store64_explicit((volatile int64*)(&rli->log_space_total), 0, + MY_MEMORY_ORDER_RELAXED); if (rli->relay_log.find_log_pos(&linfo, NullS, 1)) { sql_print_error("Could not find first log while counting relay log space"); @@ -1223,8 +1225,8 @@ int purge_relay_logs(Relay_log_info* rli, THD *thd, bool just_reset, strmake_buf(rli->group_relay_log_name, rli->relay_log.get_log_fname()); strmake_buf(rli->event_relay_log_name, rli->relay_log.get_log_fname()); rli->group_relay_log_pos= rli->event_relay_log_pos= BIN_LOG_HEADER_SIZE; - rli->log_space_total= 0; - + my_atomic_store64_explicit((volatile int64*)(&rli->log_space_total), 0, + MY_MEMORY_ORDER_RELAXED); if (count_relay_log_space(rli)) { *errmsg= "Error counting relay log space"; diff --git a/sql/slave.cc b/sql/slave.cc index 87eacfcfd0a..a2c35e5f116 100644 --- a/sql/slave.cc +++ b/sql/slave.cc @@ -2373,7 +2373,10 @@ static bool wait_for_relay_log_space(Relay_log_info* rli) &rli->log_space_lock, &stage_waiting_for_relay_log_space, &old_stage); - while (rli->log_space_limit < rli->log_space_total && + while (rli->log_space_limit < + (ulonglong)my_atomic_load64_explicit((volatile int64*) + (&rli->log_space_total), + MY_MEMORY_ORDER_RELAXED) && !(slave_killed=io_slave_killed(mi)) && !rli->ignore_log_space_limit) mysql_cond_wait(&rli->log_space_cond, &rli->log_space_lock); @@ -2923,7 +2926,10 @@ static bool send_show_master_info_data(THD *thd, Master_info *mi, bool full, protocol->store(mi->rli.last_error().message, &my_charset_bin); protocol->store((uint32) mi->rli.slave_skip_counter); protocol->store((ulonglong) mi->rli.group_master_log_pos); - protocol->store((ulonglong) mi->rli.log_space_total); + protocol->store((ulonglong) + my_atomic_load64_explicit((volatile int64*) + (&mi->rli.log_space_total), + MY_MEMORY_ORDER_RELAXED)); protocol->store( mi->rli.until_condition==Relay_log_info::UNTIL_NONE ? "None": @@ -4623,7 +4629,9 @@ Stopping slave I/O thread due to out-of-memory error from master"); #endif if (rli->log_space_limit && rli->log_space_limit < - rli->log_space_total && + (ulonglong) my_atomic_load64_explicit((volatile int64*) + (&rli->log_space_total), + MY_MEMORY_ORDER_RELAXED) && !rli->ignore_log_space_limit) if (wait_for_relay_log_space(rli)) { @@ -7222,7 +7230,10 @@ static Log_event* next_event(rpl_group_info *rgi, ulonglong *event_size) is are able to rotate and purge sometime soon. */ if (rli->log_space_limit && - rli->log_space_limit < rli->log_space_total) + rli->log_space_limit < + (ulonglong) my_atomic_load64_explicit((volatile int64*) + (&rli->log_space_total), + MY_MEMORY_ORDER_RELAXED)) { /* force rotation if not in an unfinished group */ rli->sql_force_rotate_relay= !rli->is_in_group(); -- cgit v1.2.1 From 190e8a4c2aeb417b405756b193e135c542d46b34 Mon Sep 17 00:00:00 2001 From: Igor Babaev Date: Tue, 10 Nov 2020 16:02:30 -0800 Subject: MDEV-23619 MariaDB crash on WITH RECURSIVE UNION ALL (CTE) query Due to a premature cleanup of the unit that specified a recursive CTE used in the second operand of union the server fell into an infinite loop in the reported test case. In other cases this premature cleanup could cause other problems. The bug is the result of a not quite correct fix for MDEV-17024. The unit that specifies a recursive CTE has to be cleaned only after the cleanup of the last external reference to this CTE. It means that cleanups of the unit triggered not by the cleanup of a external reference to the CTE must be blocked. Usage of local table chains in selects to get external references to recursive CTEs was not correct either because of possible merges of some selects. Also fixed a minor bug in st_select_lex::set_explain_type() that caused typing 'RECURSIVE UNION' instead of 'UNION' in EXPLAIN output for external references to a recursive CTE. --- mysql-test/r/cte_recursive.result | 229 +++++++++++++++++++++++++++++++++++++- mysql-test/t/cte_recursive.test | 50 +++++++++ sql/sql_lex.cc | 8 +- sql/sql_select.cc | 5 +- sql/sql_union.cc | 42 +++---- 5 files changed, 309 insertions(+), 25 deletions(-) diff --git a/mysql-test/r/cte_recursive.result b/mysql-test/r/cte_recursive.result index 640493142bd..85883d3e6f1 100644 --- a/mysql-test/r/cte_recursive.result +++ b/mysql-test/r/cte_recursive.result @@ -1301,7 +1301,7 @@ select ancestors.name, ancestors.dob from ancestors; id select_type table type possible_keys key key_len ref rows Extra 1 PRIMARY ALL NULL NULL NULL NULL 24 4 DERIVED folks ALL NULL NULL NULL NULL 12 Using where -6 RECURSIVE UNION ALL NULL NULL NULL NULL 12 +6 UNION ALL NULL NULL NULL NULL 12 5 RECURSIVE UNION ALL NULL NULL NULL NULL 24 NULL UNION RESULT ALL NULL NULL NULL NULL NULL 3 DERIVED folks ALL NULL NULL NULL NULL 12 Using where @@ -3964,5 +3964,232 @@ YEAR d1 d2 DROP PROCEDURE p; DROP TABLE t1,t2,t3,t4; # +# MDEV-23619: recursive CTE used only in the second operand of UNION +# +create table t1 ( +a bigint(10) not null auto_increment, +b int(5) not null, +c bigint(10) default null, +primary key (a) +) engine myisam; +insert into t1 values +(1,3,12), (2,7,15), (3,1,3), (4,3,1); +explain with recursive r_cte as +( select * from t1 as s +union +select t1.* from t1, r_cte as r where t1.c = r.a ) +select 0 as b FROM dual union all select b FROM r_cte as t; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY NULL NULL NULL NULL NULL NULL NULL No tables used +2 DERIVED s ALL NULL NULL NULL NULL 4 +3 RECURSIVE UNION t1 ALL NULL NULL NULL NULL 4 Using where +3 RECURSIVE UNION ref key0 key0 8 test.t1.c 2 +NULL UNION RESULT ALL NULL NULL NULL NULL NULL +4 UNION ALL NULL NULL NULL NULL 4 +with recursive r_cte as +( select * from t1 as s +union +select t1.* from t1, r_cte as r where t1.c = r.a ) +select 0 as b FROM dual union all select b FROM r_cte as t; +b +0 +3 +7 +1 +3 +analyze format=json with recursive r_cte as +( select * from t1 as s +union +select t1.* from t1, r_cte as r where t1.c = r.a ) +select 0 as b FROM dual union all select b FROM r_cte as t; +ANALYZE +{ + "query_block": { + "union_result": { + "table_name": "", + "access_type": "ALL", + "r_loops": 0, + "r_rows": null, + "query_specifications": [ + { + "query_block": { + "select_id": 1, + "table": { + "message": "No tables used" + } + } + }, + { + "query_block": { + "select_id": 4, + "r_loops": 1, + "r_total_time_ms": "REPLACED", + "table": { + "table_name": "", + "access_type": "ALL", + "r_loops": 1, + "rows": 4, + "r_rows": 4, + "r_total_time_ms": "REPLACED", + "filtered": 100, + "r_filtered": 100, + "materialized": { + "query_block": { + "recursive_union": { + "table_name": "", + "access_type": "ALL", + "r_loops": 0, + "r_rows": null, + "query_specifications": [ + { + "query_block": { + "select_id": 2, + "r_loops": 1, + "r_total_time_ms": "REPLACED", + "table": { + "table_name": "s", + "access_type": "ALL", + "r_loops": 1, + "rows": 4, + "r_rows": 4, + "r_total_time_ms": "REPLACED", + "filtered": 100, + "r_filtered": 100 + } + } + }, + { + "query_block": { + "select_id": 3, + "r_loops": 1, + "r_total_time_ms": "REPLACED", + "table": { + "table_name": "t1", + "access_type": "ALL", + "r_loops": 1, + "rows": 4, + "r_rows": 4, + "r_total_time_ms": "REPLACED", + "filtered": 100, + "r_filtered": 100, + "attached_condition": "t1.c is not null" + }, + "table": { + "table_name": "", + "access_type": "ref", + "possible_keys": ["key0"], + "key": "key0", + "key_length": "8", + "used_key_parts": ["a"], + "ref": ["test.t1.c"], + "r_loops": 4, + "rows": 2, + "r_rows": 0.5, + "r_total_time_ms": "REPLACED", + "filtered": 100, + "r_filtered": 100 + } + } + } + ] + } + } + } + } + } + } + ] + } + } +} +prepare stmt from "with recursive r_cte as +( select * from t1 as s +union +select t1.* from t1, r_cte as r where t1.c = r.a ) +select 0 as b FROM dual union all select b FROM r_cte as t"; +execute stmt; +b +0 +3 +7 +1 +3 +execute stmt; +b +0 +3 +7 +1 +3 +deallocate prepare stmt; +#checking hanging cte that uses a recursive cte +explain with h_cte as +( with recursive r_cte as +( select * from t1 as s +union +select t1.* from t1, r_cte as r where t1.c = r.a ) +select 0 as b FROM dual union all select b FROM r_cte as t) +select * from t1 as tt; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY tt ALL NULL NULL NULL NULL 4 +with h_cte as +( with recursive r_cte as +( select * from t1 as s +union +select t1.* from t1, r_cte as r where t1.c = r.a ) +select 0 as b FROM dual union all select b FROM r_cte as t) +select * from t1 as tt; +a b c +1 3 12 +2 7 15 +3 1 3 +4 3 1 +analyze format=json with h_cte as +( with recursive r_cte as +( select * from t1 as s +union +select t1.* from t1, r_cte as r where t1.c = r.a ) +select 0 as b FROM dual union all select b FROM r_cte as t) +select * from t1 as tt; +ANALYZE +{ + "query_block": { + "select_id": 1, + "r_loops": 1, + "r_total_time_ms": "REPLACED", + "table": { + "table_name": "tt", + "access_type": "ALL", + "r_loops": 1, + "rows": 4, + "r_rows": 4, + "r_total_time_ms": "REPLACED", + "filtered": 100, + "r_filtered": 100 + } + } +} +prepare stmt from "with h_cte as +( with recursive r_cte as +( select * from t1 as s +union +select t1.* from t1, r_cte as r where t1.c = r.a ) +select 0 as b FROM dual union all select b FROM r_cte as t) +select * from t1 as tt"; +execute stmt; +a b c +1 3 12 +2 7 15 +3 1 3 +4 3 1 +execute stmt; +a b c +1 3 12 +2 7 15 +3 1 3 +4 3 1 +deallocate prepare stmt; +drop table t1; +# # End of 10.2 tests # diff --git a/mysql-test/t/cte_recursive.test b/mysql-test/t/cte_recursive.test index d1904586ffe..082e9be7a7a 100644 --- a/mysql-test/t/cte_recursive.test +++ b/mysql-test/t/cte_recursive.test @@ -2640,6 +2640,56 @@ SELECT * FROM t4; DROP PROCEDURE p; DROP TABLE t1,t2,t3,t4; +--echo # +--echo # MDEV-23619: recursive CTE used only in the second operand of UNION +--echo # + +create table t1 ( + a bigint(10) not null auto_increment, + b int(5) not null, + c bigint(10) default null, + primary key (a) +) engine myisam; +insert into t1 values + (1,3,12), (2,7,15), (3,1,3), (4,3,1); + +let $q= +with recursive r_cte as +( select * from t1 as s + union + select t1.* from t1, r_cte as r where t1.c = r.a ) +select 0 as b FROM dual union all select b FROM r_cte as t; + +eval explain $q; +eval $q; +--source include/analyze-format.inc +eval analyze format=json $q; +eval prepare stmt from "$q"; +execute stmt; +execute stmt; +deallocate prepare stmt; + +--echo #checking hanging cte that uses a recursive cte +let $q1= +with h_cte as +( with recursive r_cte as + ( select * from t1 as s + union + select t1.* from t1, r_cte as r where t1.c = r.a ) + select 0 as b FROM dual union all select b FROM r_cte as t) +select * from t1 as tt; + +eval explain $q1; +eval $q1; +--source include/analyze-format.inc +eval analyze format=json $q1; +eval prepare stmt from "$q1"; +execute stmt; +execute stmt; +deallocate prepare stmt; + +drop table t1; + --echo # --echo # End of 10.2 tests --echo # diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index e1e307330e3..77e6b2b6571 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -4450,9 +4450,11 @@ void st_select_lex::set_explain_type(bool on_the_fly) /* pos_in_table_list=NULL for e.g. post-join aggregation JOIN_TABs. */ - if (tab->table && tab->table->pos_in_table_list && - tab->table->pos_in_table_list->with && - tab->table->pos_in_table_list->with->is_recursive) + if (!(tab->table && tab->table->pos_in_table_list)) + continue; + TABLE_LIST *tbl= tab->table->pos_in_table_list; + if (tbl->with && tbl->with->is_recursive && + tbl->is_with_table_recursive_reference()) { uses_cte= true; break; diff --git a/sql/sql_select.cc b/sql/sql_select.cc index d1076a54f1b..8b4401b1009 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -12275,6 +12275,9 @@ void JOIN::join_free() for (tmp_unit= select_lex->first_inner_unit(); tmp_unit; tmp_unit= tmp_unit->next_unit()) + { + if (tmp_unit->with_element && tmp_unit->with_element->is_recursive) + continue; for (sl= tmp_unit->first_select(); sl; sl= sl->next_select()) { Item_subselect *subselect= sl->master_unit()->item; @@ -12292,7 +12295,7 @@ void JOIN::join_free() /* Can't unlock if at least one JOIN is still needed */ can_unlock= can_unlock && full_local; } - + } /* We are not using tables anymore Unlock all tables. We may be in an INSERT .... SELECT statement. diff --git a/sql/sql_union.cc b/sql/sql_union.cc index 8b1719c60cb..9a16237042b 100644 --- a/sql/sql_union.cc +++ b/sql/sql_union.cc @@ -1370,13 +1370,7 @@ bool st_select_lex_unit::cleanup() { DBUG_RETURN(FALSE); } - /* - When processing a PS/SP or an EXPLAIN command cleanup of a unit can - be performed immediately when the unit is reached in the cleanup - traversal initiated by the cleanup of the main unit. - */ - if (!thd->stmt_arena->is_stmt_prepare() && !thd->lex->describe && - with_element && with_element->is_recursive && union_result) + if (with_element && with_element->is_recursive && union_result) { select_union_recursive *result= with_element->rec_result; if (++result->cleanup_count == with_element->rec_outer_references) @@ -1554,27 +1548,31 @@ bool st_select_lex::cleanup() if (join) { + List_iterator ti(leaf_tables); + TABLE_LIST *tbl; + while ((tbl= ti++)) + { + if (tbl->is_recursive_with_table() && + !tbl->is_with_table_recursive_reference()) + { + /* + If query is killed before open_and_process_table() for tbl + is called then 'with' is already set, but 'derived' is not. + */ + st_select_lex_unit *unit= tbl->with->spec; + error|= (bool) error | (uint) unit->cleanup(); + } + } DBUG_ASSERT((st_select_lex*)join->select_lex == this); error= join->destroy(); delete join; join= 0; } - for (TABLE_LIST *tbl= get_table_list(); tbl; tbl= tbl->next_local) - { - if (tbl->is_recursive_with_table() && - !tbl->is_with_table_recursive_reference()) - { - /* - If query is killed before open_and_process_table() for tbl - is called then 'with' is already set, but 'derived' is not. - */ - st_select_lex_unit *unit= tbl->with->spec; - error|= (bool) error | (uint) unit->cleanup(); - } - } for (SELECT_LEX_UNIT *lex_unit= first_inner_unit(); lex_unit ; lex_unit= lex_unit->next_unit()) { + if (lex_unit->with_element && lex_unit->with_element->is_recursive) + continue; error= (bool) ((uint) error | (uint) lex_unit->cleanup()); } inner_refs_list.empty(); @@ -1594,8 +1592,12 @@ void st_select_lex::cleanup_all_joins(bool full) join->cleanup(full); for (unit= first_inner_unit(); unit; unit= unit->next_unit()) + { + if (unit->with_element && unit->with_element->is_recursive) + continue; for (sl= unit->first_select(); sl; sl= sl->next_select()) sl->cleanup_all_joins(full); + } DBUG_VOID_RETURN; } -- cgit v1.2.1 From bb328a2a274b0b5cc024f298fcac70b3f0fb75aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Fri, 13 Nov 2020 20:12:29 +0200 Subject: MDEV-24188 Hang in buf_page_create() after reusing a previously freed page The fix of MDEV-23456 (commit b1009ae5c16697d5eef443cc6a60a74301148c73) introduced a livelock between page flushing and a thread that is executing buf_page_create(). buf_page_create(): If the current mini-transaction is holding an exclusive latch on the page, do not attempt to acquire another one, and do not care about any I/O fix. mtr_t::have_x_latch(): Replaces mtr_t::get_fix_count(). dyn_buf_t::for_each_block(const Functor&) const: A new variant. rw_lock_own(): Add a const qualifier. Reviewed by: Thirunarayanan Balathandayuthapani --- storage/innobase/buf/buf0buf.cc | 52 ++++++++++++++------------- storage/innobase/include/dyn0buf.h | 20 ++++++++++- storage/innobase/include/mtr0mtr.h | 7 ++-- storage/innobase/include/sync0rw.h | 2 +- storage/innobase/mtr/mtr0mtr.cc | 72 ++++++++++++++++++++++++-------------- storage/innobase/sync/sync0rw.cc | 2 +- 6 files changed, 97 insertions(+), 58 deletions(-) diff --git a/storage/innobase/buf/buf0buf.cc b/storage/innobase/buf/buf0buf.cc index a54d98adee0..9f3d743ada1 100644 --- a/storage/innobase/buf/buf0buf.cc +++ b/storage/innobase/buf/buf0buf.cc @@ -5511,6 +5511,7 @@ loop: && !buf_pool_watch_is_sentinel(buf_pool, &block->page)) { ut_d(block->page.file_page_was_freed = FALSE); buf_page_state page_state = buf_block_get_state(block); + bool have_x_latch = false; #ifdef BTR_CUR_HASH_ADAPT const dict_index_t *drop_hash_entry= NULL; #endif @@ -5563,26 +5564,26 @@ loop: free_block = NULL; break; case BUF_BLOCK_FILE_PAGE: - buf_block_fix(block); - const int32_t num_fix_count = - mtr->get_fix_count(block) + 1; - buf_page_mutex_enter(block); - while (buf_block_get_io_fix(block) != BUF_IO_NONE - || (num_fix_count - != block->page.buf_fix_count)) { - buf_page_mutex_exit(block); - buf_pool_mutex_exit(buf_pool); - rw_lock_x_unlock(hash_lock); - - os_thread_yield(); - - buf_pool_mutex_enter(buf_pool); - rw_lock_x_lock(hash_lock); + have_x_latch = mtr->have_x_latch(*block); + if (!have_x_latch) { + buf_block_fix(block); buf_page_mutex_enter(block); + while (buf_block_get_io_fix(block) + != BUF_IO_NONE + || block->page.buf_fix_count != 1) { + buf_page_mutex_exit(block); + buf_pool_mutex_exit(buf_pool); + rw_lock_x_unlock(hash_lock); + + os_thread_sleep(1000); + + buf_pool_mutex_enter(buf_pool); + rw_lock_x_lock(hash_lock); + buf_page_mutex_enter(block); + } + rw_lock_x_lock(&block->lock); + buf_page_mutex_exit(block); } - - rw_lock_x_lock(&block->lock); - buf_page_mutex_exit(block); #ifdef BTR_CUR_HASH_ADAPT drop_hash_entry = block->index; #endif @@ -5601,16 +5602,17 @@ loop: } #endif /* BTR_CUR_HASH_ADAPT */ + if (!have_x_latch) { #ifdef UNIV_DEBUG - if (!fsp_is_system_temporary(page_id.space())) { - rw_lock_s_lock_nowait( - &block->debug_latch, - __FILE__, __LINE__); - } + if (!fsp_is_system_temporary(page_id.space())) { + rw_lock_s_lock_nowait( + &block->debug_latch, + __FILE__, __LINE__); + } #endif /* UNIV_DEBUG */ - mtr_memo_push(mtr, block, MTR_MEMO_PAGE_X_FIX); - + mtr_memo_push(mtr, block, MTR_MEMO_PAGE_X_FIX); + } return block; } diff --git a/storage/innobase/include/dyn0buf.h b/storage/innobase/include/dyn0buf.h index b3302409e5f..da8d4b7de26 100644 --- a/storage/innobase/include/dyn0buf.h +++ b/storage/innobase/include/dyn0buf.h @@ -1,7 +1,7 @@ /***************************************************************************** Copyright (c) 2013, 2016, Oracle and/or its affiliates. All Rights Reserved. -Copyright (c) 2019, MariaDB Corporation. +Copyright (c) 2019, 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 @@ -347,6 +347,24 @@ public: return(true); } + /** + Iterate over each block and call the functor. + @return false if iteration was terminated. */ + template + bool for_each_block(const Functor& functor) const + { + for (typename list_t::iterator it = m_list.begin(), + end = m_list.end(); + it != end; ++it) { + + if (!functor(&*it)) { + return false; + } + } + + return(true); + } + /** Iterate over all the blocks in reverse and call the iterator @return false if iteration was terminated. */ diff --git a/storage/innobase/include/mtr0mtr.h b/storage/innobase/include/mtr0mtr.h index 5270cd671db..b57a38f8eab 100644 --- a/storage/innobase/include/mtr0mtr.h +++ b/storage/innobase/include/mtr0mtr.h @@ -434,10 +434,9 @@ struct mtr_t { static inline bool is_block_dirtied(const buf_block_t* block) MY_ATTRIBUTE((warn_unused_result)); - /** Get the buffer fix count for the block added by this mtr. - @param[in] block block to be checked - @return number of buffer count added by this mtr */ - int32_t get_fix_count(buf_block_t *block); + /** Check if we are holding a block latch in exclusive mode + @param block buffer pool block to search for */ + bool have_x_latch(const buf_block_t& block) const; private: /** Look up the system tablespace. */ void lookup_sys_space(); diff --git a/storage/innobase/include/sync0rw.h b/storage/innobase/include/sync0rw.h index b2a436804fe..855d4439280 100644 --- a/storage/innobase/include/sync0rw.h +++ b/storage/innobase/include/sync0rw.h @@ -508,7 +508,7 @@ the pass value == 0. */ ibool rw_lock_own( /*========*/ - rw_lock_t* lock, /*!< in: rw-lock */ + const rw_lock_t*lock, /*!< in: rw-lock */ ulint lock_type) /*!< in: lock type: RW_LOCK_S, RW_LOCK_X */ MY_ATTRIBUTE((warn_unused_result)); diff --git a/storage/innobase/mtr/mtr0mtr.cc b/storage/innobase/mtr/mtr0mtr.cc index bc30d6efd55..f966d4c74c6 100644 --- a/storage/innobase/mtr/mtr0mtr.cc +++ b/storage/innobase/mtr/mtr0mtr.cc @@ -308,24 +308,6 @@ struct DebugCheck { }; #endif -/** Find buffer fix count of the given block acquired by the -mini-transaction */ -struct FindBlock -{ - int32_t num_fix; - const buf_block_t *const block; - - FindBlock(const buf_block_t *block_buf): num_fix(0), block(block_buf) {} - - bool operator()(const mtr_memo_slot_t* slot) - { - if (slot->object == block) - ut_d(if (slot->type != MTR_MEMO_MODIFY)) - num_fix++; - return true; - } -}; - /** Release a resource acquired by the mini-transaction. */ struct ReleaseBlocks { /** Release specific object */ @@ -822,12 +804,48 @@ mtr_t::release_free_extents(ulint n_reserved) space->release_free_extents(n_reserved); } -int32_t mtr_t::get_fix_count(buf_block_t *block) +/** Find out whether a block was X-latched by the mini-transaction */ +struct FindBlockX { - Iterate iteration((FindBlock(block))); - if (m_memo.for_each_block(iteration)) - return iteration.functor.num_fix; - return 0; + const buf_block_t █ + + FindBlockX(const buf_block_t &block): block(block) {} + + /** @return whether the block was not found x-latched */ + bool operator()(const mtr_memo_slot_t *slot) const + { + return slot->object != &block || slot->type == MTR_MEMO_PAGE_X_FIX; + } +}; + +#ifdef UNIV_DEBUG +/** Assert that the block is not present in the mini-transaction */ +struct FindNoBlock +{ + const buf_block_t █ + + FindNoBlock(const buf_block_t &block): block(block) {} + + /** @return whether the block was not found */ + bool operator()(const mtr_memo_slot_t *slot) const + { + return slot->object != █ + } +}; +#endif /* UNIV_DEBUG */ + +bool mtr_t::have_x_latch(const buf_block_t &block) const +{ + if (m_memo.for_each_block(CIterate(FindBlockX(block)))) + { + ut_ad(m_memo.for_each_block(CIterate(FindNoBlock(block)))); + ut_ad(!memo_contains_flagged(&block, + MTR_MEMO_PAGE_S_FIX | MTR_MEMO_PAGE_SX_FIX | + MTR_MEMO_BUF_FIX | MTR_MEMO_MODIFY)); + return false; + } + ut_ad(rw_lock_own(&block.lock, RW_LOCK_X)); + return true; } #ifdef UNIV_DEBUG @@ -844,15 +862,17 @@ mtr_t::memo_contains( return(false); } + const rw_lock_t *lock = static_cast(object); + switch (type) { case MTR_MEMO_X_LOCK: - ut_ad(rw_lock_own((rw_lock_t*) object, RW_LOCK_X)); + ut_ad(rw_lock_own(lock, RW_LOCK_X)); break; case MTR_MEMO_SX_LOCK: - ut_ad(rw_lock_own((rw_lock_t*) object, RW_LOCK_SX)); + ut_ad(rw_lock_own(lock, RW_LOCK_SX)); break; case MTR_MEMO_S_LOCK: - ut_ad(rw_lock_own((rw_lock_t*) object, RW_LOCK_S)); + ut_ad(rw_lock_own(lock, RW_LOCK_S)); break; } diff --git a/storage/innobase/sync/sync0rw.cc b/storage/innobase/sync/sync0rw.cc index b46126bde58..ded4f44c2a9 100644 --- a/storage/innobase/sync/sync0rw.cc +++ b/storage/innobase/sync/sync0rw.cc @@ -990,7 +990,7 @@ the pass value == 0. ibool rw_lock_own( /*========*/ - rw_lock_t* lock, /*!< in: rw-lock */ + const rw_lock_t*lock, /*!< in: rw-lock */ ulint lock_type) /*!< in: lock type: RW_LOCK_S, RW_LOCK_X */ { -- cgit v1.2.1 From c8be6aafb97a6fac9ddc190aeb40a30eaeb47d4b Mon Sep 17 00:00:00 2001 From: Oleksandr Byelkin Date: Sat, 14 Nov 2020 09:55:09 +0100 Subject: Fix to make it compiling on new ubuntu. --- unittest/sql/mf_iocache-t.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unittest/sql/mf_iocache-t.cc b/unittest/sql/mf_iocache-t.cc index 01cc8725ecc..de24e39539c 100644 --- a/unittest/sql/mf_iocache-t.cc +++ b/unittest/sql/mf_iocache-t.cc @@ -95,7 +95,7 @@ void sql_print_error(const char *format, ...) /*** end of encryption tweaks and stubs ****************************/ -IO_CACHE info; +static IO_CACHE info; #define CACHE_SIZE 16384 #define INFO_TAIL ", pos_in_file = %llu, pos_in_mem = %lu", \ -- cgit v1.2.1 From 81b9c78500a9326a85290992a9340eb887b74048 Mon Sep 17 00:00:00 2001 From: Oleksandr Byelkin Date: Sat, 14 Nov 2020 12:28:28 +0100 Subject: MDEV-24098: 10.5 followup remove version data from the test output --- mysql-test/main/lock_user.result | 2 +- mysql-test/main/lock_user.test | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/mysql-test/main/lock_user.result b/mysql-test/main/lock_user.result index d2221e2cd8c..3ee8bbf2296 100644 --- a/mysql-test/main/lock_user.result +++ b/mysql-test/main/lock_user.result @@ -163,7 +163,7 @@ CREATE USER `user1`@`localhost` ACCOUNT LOCK PASSWORD EXPIRE alter user user1@localhost PASSWORD EXPIRE INTERVAL 60 DAY ACCOUNT UNLOCK; select * from mysql.global_priv where user='user1'; Host User Priv -localhost user1 {"access":0,"version_id":100509,"plugin":"mysql_native_password","authentication_string":"","account_locked":false,"password_last_changed":0,"password_lifetime":60} +localhost user1 {"access":0,"version_id":XXX,"plugin":"mysql_native_password","authentication_string":"","account_locked":false,"password_last_changed":0,"password_lifetime":60} show create user user1@localhost; CREATE USER for user1@localhost CREATE USER `user1`@`localhost` PASSWORD EXPIRE diff --git a/mysql-test/main/lock_user.test b/mysql-test/main/lock_user.test index 530883f33ef..4e480c19360 100644 --- a/mysql-test/main/lock_user.test +++ b/mysql-test/main/lock_user.test @@ -160,6 +160,7 @@ alter user user1@localhost ACCOUNT LOCK PASSWORD EXPIRE DEFAULT; show create user user1@localhost; # note output needs to be corrected by MDEV-24114: password expire users cannot be unexpired alter user user1@localhost PASSWORD EXPIRE INTERVAL 60 DAY ACCOUNT UNLOCK; +--replace_regex /"version_id":[0-9]*,/"version_id":XXX,/ select * from mysql.global_priv where user='user1'; show create user user1@localhost; -- cgit v1.2.1