From: Monty <monty@mariadb.org> PURGE BINARY LOGS did not always purge binary logs. This commit fixes some of the issues and adds notifications if a binary log cannot be purged. User visible changes: - 'PURGE BINARY LOG TO log_name' and 'PURGE BINARY LOGS BEFORE date' worked differently. 'TO' ignored 'slave_connections_needed_for_purge' while 'BEFORE' did not. Now both versions ignores the 'slave_connections_needed_for_purge variable'. - 'PURGE BINARY LOG..' commands now returns 'note' if a binary log cannot be deleted like Note 1375 Binary log 'master-bin.000004' is not purged because it is the current active binlog - Automatic binary log purges, based on date or size, will write a note to the error log if a binary log matching the size or date cannot yet be deleted. - If 'slave_connections_needed_for_purge' is set from a config or command line, it is set to 0 if Galera is enabled and 1 otherwise (old default). This ensures that automatic binary log purge works with Galera as before the addition of 'slave_connections_needed_for_purge'. If the variable is changed to 0, a warning will be printed to the error log. Code changes: - Added THD argument to several purge_logs related functions that needed THD. - Added 'interactive' options to purge_logs functions. This allowed me to remove testing of sql_command == SQLCOM_PURGE. - Changed purge_logs_before_date() to first check if log is applicable before calling can_purge_logs(). This ensures we do not get a notification for logs that does not match the remove criteria. - MYSQL_BIN_LOG::can_purge_log() will write notifications to the user or error log if a log cannot yet be removed. - log_in_use() will return reason why a binary log cannot be removed. - Moved checking of binlog_format for Galera to be after Galera is initialized (The old check never worked). If Galera is enabled we now change the binlog_format to ROW, with a warning, instead of aborting the server. If this change happens, the binlog_format variable will be marked with AUTO or FORCED, for information_schema.system_variables, and a warning will be printed to the error log. - Print also a warning if FLASHBACK changes the binlog_format to ROW. Before this was done silently. --- mysql-test/main/mysqld--help.result | 1 + .../binlog_flush_binlogs_delete_domain.result | 3 + mysql-test/suite/binlog/r/binlog_index.result | 1 + .../suite/binlog/r/binlog_xa_recover.result | 2 + .../t/binlog_flush_binlogs_delete_domain.test | 2 + .../binlog_encryption/binlog_index.result | 1 + .../binlog_xa_recover.result | 2 + .../oracle/r/binlog_ptr_mysqlbinlog.result | 1 + .../oracle/t/binlog_ptr_mysqlbinlog.test | 2 + mysql-test/suite/galera/r/basic.result | 6 ++ mysql-test/suite/galera/t/basic.test | 3 + mysql-test/suite/rpl/r/purge_binlog.result | 52 ++++++++++ mysql-test/suite/rpl/r/rpl_rotate_logs.result | 2 + mysql-test/suite/rpl/t/purge_binlog.test | 54 +++++++++++ .../r/sysvars_server_notembedded.result | 2 +- sql/log.cc | 95 ++++++++++++++----- sql/log.h | 8 +- sql/mysqld.cc | 48 +++++++--- sql/mysqld.h | 1 + sql/set_var.cc | 3 +- sql/set_var.h | 3 +- sql/sql_repl.cc | 26 +++-- sql/sql_repl.h | 2 +- sql/sys_vars.cc | 21 +++- sql/sys_vars.h | 17 ++++ 25 files changed, 303 insertions(+), 55 deletions(-) create mode 100644 mysql-test/suite/rpl/r/purge_binlog.result create mode 100644 mysql-test/suite/rpl/t/purge_binlog.test create mode 100644 sql/sys_vars.h diff --git a/mysql-test/main/mysqld--help.result b/mysql-test/main/mysqld--help.result index ee75f586970..026f135ebd5 100644 --- a/mysql-test/main/mysqld--help.result +++ b/mysql-test/main/mysqld--help.result @@ -1308,6 +1308,7 @@ The following specify which files/extra groups are read (specified before remain Minimum number of connected slaves required for automatic binary log purge with max_binlog_total_size, binlog_expire_logs_seconds or binlog_expire_logs_days. + Default is 0 when Galera is enabled and 1 otherwise. --slave-ddl-exec-mode=name How replication events should be executed. Legal values are STRICT and IDEMPOTENT (default). In IDEMPOTENT mode, diff --git a/mysql-test/suite/binlog/r/binlog_flush_binlogs_delete_domain.result b/mysql-test/suite/binlog/r/binlog_flush_binlogs_delete_domain.result index 1c11191802f..f967a367898 100644 --- a/mysql-test/suite/binlog/r/binlog_flush_binlogs_delete_domain.result +++ b/mysql-test/suite/binlog/r/binlog_flush_binlogs_delete_domain.result @@ -49,6 +49,9 @@ ERROR HY000: Could not delete gtid domain. Reason: binlog files may contain gtid MDEV-31140: Missing error from DELETE_DOMAIN_ID when gtid_binlog_state partially matches GTID_LIST. FLUSH BINARY LOGS; PURGE BINARY LOGS TO 'master-bin.000005'; +SHOW BINARY LOGS; +Log_name File_size +master-bin.000005 # SET @@SESSION.gtid_domain_id=8; SET @@SESSION.server_id=10*8 + 1; INSERT INTO t SELECT 1+MAX(a) FROM t; diff --git a/mysql-test/suite/binlog/r/binlog_index.result b/mysql-test/suite/binlog/r/binlog_index.result index 9dfda71f9a7..2d2363a7fec 100644 --- a/mysql-test/suite/binlog/r/binlog_index.result +++ b/mysql-test/suite/binlog/r/binlog_index.result @@ -30,6 +30,7 @@ flush logs; flush logs; *** must be a warning master-bin.000001 was not found *** Warnings: +Note 1375 Binary log 'master-bin.000004' is not purged because it is the current active binlog Warning 1612 Being purged log master-bin.000001 was not found *** must show one record, of the active binlog, left in the index file after PURGE *** show binary logs; diff --git a/mysql-test/suite/binlog/r/binlog_xa_recover.result b/mysql-test/suite/binlog/r/binlog_xa_recover.result index f5060fd5160..da8dff2dfd1 100644 --- a/mysql-test/suite/binlog/r/binlog_xa_recover.result +++ b/mysql-test/suite/binlog/r/binlog_xa_recover.result @@ -89,6 +89,8 @@ master-bin.000006 # Format_desc # # SERVER_VERSION, BINLOG_VERSION master-bin.000006 # Gtid_list # # [#-#-#] master-bin.000006 # Binlog_checkpoint # # master-bin.000004 PURGE BINARY LOGS TO "master-bin.000006"; +Warnings: +Note 1375 Binary log 'master-bin.000004' is not purged because it is in use by an active XID transaction show binary logs; Log_name File_size master-bin.000004 # diff --git a/mysql-test/suite/binlog/t/binlog_flush_binlogs_delete_domain.test b/mysql-test/suite/binlog/t/binlog_flush_binlogs_delete_domain.test index 1643ecff72d..4a974c642c0 100644 --- a/mysql-test/suite/binlog/t/binlog_flush_binlogs_delete_domain.test +++ b/mysql-test/suite/binlog/t/binlog_flush_binlogs_delete_domain.test @@ -91,6 +91,8 @@ while ($domain_cnt) FLUSH BINARY LOGS; --let $purge_to_binlog= query_get_value(SHOW MASTER STATUS, File, 1) --eval PURGE BINARY LOGS TO '$purge_to_binlog' +--replace_column 2 # +SHOW BINARY LOGS; --eval SET @@SESSION.gtid_domain_id=$err_domain_id --eval SET @@SESSION.server_id=10*$err_domain_id + $err_server_id eval INSERT INTO t SELECT 1+MAX(a) FROM t; diff --git a/mysql-test/suite/binlog_encryption/binlog_index.result b/mysql-test/suite/binlog_encryption/binlog_index.result index 9dfda71f9a7..2d2363a7fec 100644 --- a/mysql-test/suite/binlog_encryption/binlog_index.result +++ b/mysql-test/suite/binlog_encryption/binlog_index.result @@ -30,6 +30,7 @@ flush logs; flush logs; *** must be a warning master-bin.000001 was not found *** Warnings: +Note 1375 Binary log 'master-bin.000004' is not purged because it is the current active binlog Warning 1612 Being purged log master-bin.000001 was not found *** must show one record, of the active binlog, left in the index file after PURGE *** show binary logs; diff --git a/mysql-test/suite/binlog_encryption/binlog_xa_recover.result b/mysql-test/suite/binlog_encryption/binlog_xa_recover.result index 3e4ed42cf7c..7aacd0e034a 100644 --- a/mysql-test/suite/binlog_encryption/binlog_xa_recover.result +++ b/mysql-test/suite/binlog_encryption/binlog_xa_recover.result @@ -93,6 +93,8 @@ master-bin.000006 # Start_encryption # # master-bin.000006 # Gtid_list # # [#-#-#] master-bin.000006 # Binlog_checkpoint # # master-bin.000004 PURGE BINARY LOGS TO "master-bin.000006"; +Warnings: +Note 1375 Binary log 'master-bin.000004' is not purged because it is in use by an active XID transaction show binary logs; Log_name File_size master-bin.000004 # diff --git a/mysql-test/suite/compat/oracle/r/binlog_ptr_mysqlbinlog.result b/mysql-test/suite/compat/oracle/r/binlog_ptr_mysqlbinlog.result index 0656a685976..543ec5336d8 100644 --- a/mysql-test/suite/compat/oracle/r/binlog_ptr_mysqlbinlog.result +++ b/mysql-test/suite/compat/oracle/r/binlog_ptr_mysqlbinlog.result @@ -1,3 +1,4 @@ +call mtr.add_suppression("Binlog_format changed to.*flashback"); SET @@SQL_MODE = 'ORACLE'; ########################################################################## # Test verifies Gtid_log_event/Xid_log_event specific print # diff --git a/mysql-test/suite/compat/oracle/t/binlog_ptr_mysqlbinlog.test b/mysql-test/suite/compat/oracle/t/binlog_ptr_mysqlbinlog.test index bda32af5d4e..165f2cc0afe 100644 --- a/mysql-test/suite/compat/oracle/t/binlog_ptr_mysqlbinlog.test +++ b/mysql-test/suite/compat/oracle/t/binlog_ptr_mysqlbinlog.test @@ -18,6 +18,8 @@ --source include/have_log_bin.inc --source include/have_innodb.inc +call mtr.add_suppression("Binlog_format changed to.*flashback"); + let $MYSQLD_DATADIR= `select @@datadir`; SET @@SQL_MODE = 'ORACLE'; diff --git a/mysql-test/suite/galera/r/basic.result b/mysql-test/suite/galera/r/basic.result index 10f180e7a94..7b4cbd93c7b 100644 --- a/mysql-test/suite/galera/r/basic.result +++ b/mysql-test/suite/galera/r/basic.result @@ -1,5 +1,11 @@ connection node_2; connection node_1; +select @@slave_connections_needed_for_purge; +@@slave_connections_needed_for_purge +0 +select VARIABLE_NAME, GLOBAL_VALUE, GLOBAL_VALUE_ORIGIN from information_schema.system_variables where variable_name="slave_connections_needed_for_purge"; +VARIABLE_NAME GLOBAL_VALUE GLOBAL_VALUE_ORIGIN +SLAVE_CONNECTIONS_NEEDED_FOR_PURGE 0 AUTO USE test; CREATE TABLE t1(c1 INT PRIMARY KEY) ENGINE=INNODB; INSERT INTO t1 VALUES (1), (2), (3), (4), (5); diff --git a/mysql-test/suite/galera/t/basic.test b/mysql-test/suite/galera/t/basic.test index 8fc6eee3b3b..a70ee962bc0 100644 --- a/mysql-test/suite/galera/t/basic.test +++ b/mysql-test/suite/galera/t/basic.test @@ -1,6 +1,9 @@ --source include/galera_cluster.inc --source include/have_innodb.inc +select @@slave_connections_needed_for_purge; +select VARIABLE_NAME, GLOBAL_VALUE, GLOBAL_VALUE_ORIGIN from information_schema.system_variables where variable_name="slave_connections_needed_for_purge"; + USE test; CREATE TABLE t1(c1 INT PRIMARY KEY) ENGINE=INNODB; INSERT INTO t1 VALUES (1), (2), (3), (4), (5); diff --git a/mysql-test/suite/rpl/r/purge_binlog.result b/mysql-test/suite/rpl/r/purge_binlog.result new file mode 100644 index 00000000000..6be060e5774 --- /dev/null +++ b/mysql-test/suite/rpl/r/purge_binlog.result @@ -0,0 +1,52 @@ +include/master-slave.inc +[connection master] +# +# MDEV-34504 PURGE BINARY LOGS not working anymore +# +select @@slave_connections_needed_for_purge; +@@slave_connections_needed_for_purge +0 +set @old_dbug= @@global.debug_dbug; +create table t1 (a int, b varchar(32768)); +insert into t1 values(1,repeat("a",32768)); +connection slave; +select a from t1; +a +1 +set @@global.debug_dbug= "+d,pause_before_io_read_event"; +connection master; +insert into t1 values(2,repeat("b",32768)); +insert into t1 values(3,repeat("c",32768)); +connection slave; +set debug_sync='now wait_for io_thread_at_read_event'; +select a from t1; +a +1 +connection master; +FLUSH BINARY LOGS; +SHOW BINARY LOGS; +Log_name File_size +master-bin.000001 # +master-bin.000002 # +PURGE BINARY LOGS TO 'master-bin.000002'; +Warnings: +Note 1375 Binary log XXX is not purged because it is in use by a slave thread +SHOW BINARY LOGS; +Log_name File_size +master-bin.000001 # +master-bin.000002 # +connection slave; +set @@global.debug_dbug= @old_dbug; +set debug_sync='now signal io_thread_continue_read_event'; +connection master; +connection slave; +select count(*) from t1; +count(*) +103 +connection master; +PURGE BINARY LOGS TO 'master-bin.000002'; +SHOW BINARY LOGS; +Log_name File_size +master-bin.000002 # +drop table t1; +include/rpl_end.inc diff --git a/mysql-test/suite/rpl/r/rpl_rotate_logs.result b/mysql-test/suite/rpl/r/rpl_rotate_logs.result index 4b7c1642a11..8e8f0026c93 100644 --- a/mysql-test/suite/rpl/r/rpl_rotate_logs.result +++ b/mysql-test/suite/rpl/r/rpl_rotate_logs.result @@ -70,6 +70,8 @@ master-bin.000002 # master-bin.000003 # SELECT @time_for_purge:=DATE_ADD('tmpval', INTERVAL 1 SECOND); purge master logs before (@time_for_purge); +Warnings: +Note 1375 Binary log 'master-bin.000003' is not purged because it is the current active binlog show binary logs; Log_name File_size master-bin.000003 # diff --git a/mysql-test/suite/rpl/t/purge_binlog.test b/mysql-test/suite/rpl/t/purge_binlog.test new file mode 100644 index 00000000000..5c804ca22bb --- /dev/null +++ b/mysql-test/suite/rpl/t/purge_binlog.test @@ -0,0 +1,54 @@ +--source include/have_debug.inc +--source include/have_debug_sync.inc +--source include/master-slave.inc +--source include/have_binlog_format_row.inc + +--echo # +--echo # MDEV-34504 PURGE BINARY LOGS not working anymore +--echo # + +select @@slave_connections_needed_for_purge; +set @old_dbug= @@global.debug_dbug; + +create table t1 (a int, b varchar(32768)); +insert into t1 values(1,repeat("a",32768)); +--sync_slave_with_master +select a from t1; +set @@global.debug_dbug= "+d,pause_before_io_read_event"; +--connection master +insert into t1 values(2,repeat("b",32768)); +insert into t1 values(3,repeat("c",32768)); +--connection slave +set debug_sync='now wait_for io_thread_at_read_event'; +select a from t1; +--connection master +--disable_query_log +let $i=100; +while ($i) +{ +--eval insert into t1 values($i+4,repeat(char(64+$i),32768)); +--dec $i +} +--enable_query_log + +FLUSH BINARY LOGS; +--replace_column 2 # +SHOW BINARY LOGS; +--let $purge_to_binlog= query_get_value(SHOW MASTER STATUS, File, 1) +--replace_regex /Binary log.*is not/Binary log XXX is not/ +--eval PURGE BINARY LOGS TO '$purge_to_binlog' +--replace_column 2 # +SHOW BINARY LOGS; +--connection slave +set @@global.debug_dbug= @old_dbug; +set debug_sync='now signal io_thread_continue_read_event'; +--connection master +--sync_slave_with_master +select count(*) from t1; +--connection master +--eval PURGE BINARY LOGS TO '$purge_to_binlog' +--replace_column 2 # +SHOW BINARY LOGS; +drop table t1; + +--source include/rpl_end.inc diff --git a/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result b/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result index 2740319dd05..0b2feb675b4 100644 --- a/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result +++ b/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result @@ -3995,7 +3995,7 @@ COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME SLAVE_CONNECTIONS_NEEDED_FOR_PURGE VARIABLE_SCOPE GLOBAL VARIABLE_TYPE INT UNSIGNED -VARIABLE_COMMENT Minimum number of connected slaves required for automatic binary log purge with max_binlog_total_size, binlog_expire_logs_seconds or binlog_expire_logs_days. +VARIABLE_COMMENT Minimum number of connected slaves required for automatic binary log purge with max_binlog_total_size, binlog_expire_logs_seconds or binlog_expire_logs_days. Default is 0 when Galera is enabled and 1 otherwise. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 4294967295 NUMERIC_BLOCK_SIZE 1 diff --git a/sql/log.cc b/sql/log.cc index c27c4f3353b..1384fb0b3e7 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -4791,8 +4791,8 @@ int MYSQL_BIN_LOG::purge_first_log(Relay_log_info* rli, bool included) DBUG_EXECUTE_IF("crash_before_purge_logs", DBUG_SUICIDE();); - rli->relay_log.purge_logs(to_purge_if_included, included, - 0, 0, &log_space_reclaimed); + rli->relay_log.purge_logs(current_thd, to_purge_if_included, included, + 0, 0, 0, &log_space_reclaimed); mysql_mutex_lock(&rli->log_space_lock); rli->log_space_total-= log_space_reclaimed; @@ -4859,16 +4859,17 @@ int MYSQL_BIN_LOG::update_log_index(LOG_INFO* log_info, bool need_update_threads mysql_file_stat() or mysql_file_delete() */ -int MYSQL_BIN_LOG::purge_logs(const char *to_log, +int MYSQL_BIN_LOG::purge_logs(THD *thd, + const char *to_log, bool included, bool need_mutex, - bool need_update_threads, + bool need_update_threads, + bool interactive, ulonglong *reclaimed_space) { int error= 0; bool exit_loop= 0; LOG_INFO log_info; - THD *thd= current_thd; DBUG_ENTER("purge_logs"); DBUG_PRINT("info",("to_log= %s",to_log)); @@ -4894,7 +4895,7 @@ int MYSQL_BIN_LOG::purge_logs(const char *to_log, if (unlikely((error=find_log_pos(&log_info, NullS, 0 /*no mutex*/)))) goto err; while ((strcmp(to_log,log_info.log_file_name) || (exit_loop=included)) && - can_purge_log(log_info.log_file_name)) + can_purge_log(thd, log_info.log_file_name, interactive)) { if (unlikely((error= register_purge_index_entry(log_info.log_file_name)))) { @@ -4902,7 +4903,6 @@ int MYSQL_BIN_LOG::purge_logs(const char *to_log, log_info.log_file_name); goto err; } - if (find_next_log(&log_info, 0) || exit_loop) break; } @@ -5243,13 +5243,13 @@ int MYSQL_BIN_LOG::purge_index_entry(THD *thd, ulonglong *reclaimed_space, mysql_file_stat() or mysql_file_delete() */ -int MYSQL_BIN_LOG::purge_logs_before_date(time_t purge_time) +int MYSQL_BIN_LOG::purge_logs_before_date(THD *thd, time_t purge_time, + bool interactive) { int error; char to_log[FN_REFLEN]; LOG_INFO log_info; MY_STAT stat_area; - THD *thd= current_thd; DBUG_ENTER("purge_logs_before_date"); mysql_mutex_lock(&LOCK_index); @@ -5258,7 +5258,7 @@ int MYSQL_BIN_LOG::purge_logs_before_date(time_t purge_time) if (unlikely((error=find_log_pos(&log_info, NullS, 0 /*no mutex*/)))) goto err; - while (can_purge_log(log_info.log_file_name)) + for (;;) { if (!mysql_file_stat(m_key_file_log, log_info.log_file_name, &stat_area, MYF(0))) @@ -5296,7 +5296,8 @@ int MYSQL_BIN_LOG::purge_logs_before_date(time_t purge_time) } else { - if (stat_area.st_mtime >= purge_time) + if (stat_area.st_mtime >= purge_time || + !can_purge_log(thd, log_info.log_file_name, interactive)) break; strmake_buf(to_log, log_info.log_file_name); } @@ -5307,7 +5308,7 @@ int MYSQL_BIN_LOG::purge_logs_before_date(time_t purge_time) if (to_log[0]) { ulonglong reclaimed_space= 0; - error= purge_logs(to_log, 1, 0, 1, &reclaimed_space); + error= purge_logs(thd, to_log, 1, 0, 1, interactive, &reclaimed_space); binlog_space_total-= reclaimed_space; } @@ -5338,6 +5339,7 @@ int MYSQL_BIN_LOG::real_purge_logs_by_size(ulonglong binlog_pos) MY_STAT stat_area; char to_log[FN_REFLEN]; ulonglong found_space= 0; + THD *thd= current_thd; DBUG_ENTER("real_purge_logs_by_size"); mysql_mutex_lock(&LOCK_index); @@ -5351,7 +5353,7 @@ int MYSQL_BIN_LOG::real_purge_logs_by_size(ulonglong binlog_pos) goto err; to_log[0] = 0; - while (can_purge_log(log_info.log_file_name)) + while (can_purge_log(thd, log_info.log_file_name, 0)) { if (!mysql_file_stat(m_key_file_log, log_info.log_file_name, &stat_area, MYF(0))) @@ -5394,8 +5396,9 @@ int MYSQL_BIN_LOG::real_purge_logs_by_size(ulonglong binlog_pos) if (found_space) { ulonglong reclaimed_space= 0; - purge_logs(to_log, true, false /*need_lock_index=false*/, + purge_logs(thd, to_log, true, false /*need_lock_index=false*/, true /*need_update_threads=true*/, + false /* not interactive */, &reclaimed_space); DBUG_ASSERT(reclaimed_space == found_space); binlog_space_total-= reclaimed_space; @@ -5414,6 +5417,10 @@ int MYSQL_BIN_LOG::real_purge_logs_by_size(ulonglong binlog_pos) } /* + @param THD thd, may be null at startup + @param log_file_name_arg Name of log file to check + @param interactive True if called by a PURGE BINLOG command. + The following variables are here to allows us to quickly check if the can_purge_log(log_file_name_arg) name will fail in the 'log_in_use' call. @@ -5428,18 +5435,23 @@ int MYSQL_BIN_LOG::real_purge_logs_by_size(ulonglong binlog_pos) static bool waiting_for_slave_to_change_binlog= 0; static ulonglong purge_sending_new_binlog_file= 0; static char purge_binlog_name[FN_REFLEN]; +static bool purge_warning_given= 0; bool -MYSQL_BIN_LOG::can_purge_log(const char *log_file_name_arg) +MYSQL_BIN_LOG::can_purge_log(THD *thd, const char *log_file_name_arg, + bool interactive) { - THD *thd= current_thd; // May be NULL at startup - bool res; + int res; + const char *reason; if (is_active(log_file_name_arg) || (!is_relay_log && waiting_for_slave_to_change_binlog && purge_sending_new_binlog_file == sending_new_binlog_file && !strcmp(log_file_name_arg, purge_binlog_name))) - return false; + { + reason= "it is the current active binlog"; + goto error; + } DBUG_ASSERT(!is_relay_log || binlog_xid_count_list.is_empty()); if (!is_relay_log) @@ -5455,7 +5467,10 @@ MYSQL_BIN_LOG::can_purge_log(const char *log_file_name_arg) } mysql_mutex_unlock(&LOCK_xid_list); if (b) - return false; + { + reason= "it is in use by an active XID transaction"; + goto error; + } } if (!is_relay_log) @@ -5464,8 +5479,7 @@ MYSQL_BIN_LOG::can_purge_log(const char *log_file_name_arg) purge_sending_new_binlog_file= sending_new_binlog_file; } if ((res= log_in_use(log_file_name_arg, - (is_relay_log || - (thd && thd->lex->sql_command == SQLCOM_PURGE)) ? + (is_relay_log || interactive) ? 0 : slave_connections_needed_for_purge))) { if (!is_relay_log) @@ -5473,9 +5487,39 @@ MYSQL_BIN_LOG::can_purge_log(const char *log_file_name_arg) waiting_for_slave_to_change_binlog= 1; strmake(purge_binlog_name, log_file_name_arg, sizeof(purge_binlog_name)-1); + if (res == 1) + reason= "it is in use by a slave thread"; + else + reason= "less than 'slave_connections_needed_for_purge' slaves has " + "processed it"; + goto error; } } - return !res; + /* We can purge this file, reset for next failure */ + purge_warning_given= 0; + return 1; + +error: + if (!is_relay_log && (interactive || !purge_warning_given)) + { + /* Remove directory (to keep things shorter and compatible */ + log_file_name_arg+= dirname_length(log_file_name_arg); + + /* purge_warning_given is reset after next sucessful purge */ + purge_warning_given= 1; + if (interactive) + { + my_printf_error(ER_BINLOG_PURGE_PROHIBITED, + "Binary log '%s' is not purged because %s", + MYF(ME_NOTE), log_file_name_arg, reason); + } + else + { + sql_print_information("Binary log '%s' is not purged because %s", + log_file_name_arg, reason); + } + } + return 0; } #endif /* HAVE_REPLICATION */ @@ -7611,16 +7655,17 @@ void MYSQL_BIN_LOG::purge(bool all) { mysql_mutex_assert_not_owner(&LOCK_log); #ifdef HAVE_REPLICATION + THD *thd= current_thd; if (binlog_expire_logs_seconds) { - DEBUG_SYNC(current_thd, "at_purge_logs_before_date"); + DEBUG_SYNC(thd, "at_purge_logs_before_date"); time_t purge_time= my_time(0) - binlog_expire_logs_seconds; DBUG_EXECUTE_IF("expire_logs_always", { purge_time = my_time(0); }); if (purge_time >= 0) { - purge_logs_before_date(purge_time); + purge_logs_before_date(thd, purge_time, 0); } - DEBUG_SYNC(current_thd, "after_purge_logs_before_date"); + DEBUG_SYNC(thd, "after_purge_logs_before_date"); } if (all && binlog_space_limit) { diff --git a/sql/log.h b/sql/log.h index 23513c75ac5..c8a8eb72b37 100644 --- a/sql/log.h +++ b/sql/log.h @@ -1035,7 +1035,7 @@ class MYSQL_BIN_LOG: public TC_LOG, private Event_log void mark_xid_done(ulong cookie, bool write_checkpoint); void make_log_name(char* buf, const char* log_ident); bool is_active(const char* log_file_name); - bool can_purge_log(const char *log_file_name); + bool can_purge_log(THD *thd, const char *log_file_name, bool interactive); int update_log_index(LOG_INFO* linfo, bool need_update_threads); int rotate(bool force_rotate, bool* check_purge); void checkpoint_and_purge(ulong binlog_id); @@ -1054,10 +1054,10 @@ class MYSQL_BIN_LOG: public TC_LOG, private Event_log @retval other Failure */ bool flush_and_sync(bool *synced); - int purge_logs(const char *to_log, bool included, - bool need_mutex, bool need_update_threads, + int purge_logs(THD *thd, const char *to_log, bool included, + bool need_mutex, bool need_update_threads, bool interactive, ulonglong *decrease_log_space); - int purge_logs_before_date(time_t purge_time); + int purge_logs_before_date(THD *thd, time_t purge_time, bool interactive); int purge_first_log(Relay_log_info* rli, bool included); int count_binlog_space(); void count_binlog_space_with_mutex() diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 2b12001de9e..604eaadcc92 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -52,6 +52,7 @@ #include "sql_manager.h" // stop_handle_manager, start_handle_manager #include "sql_expression_cache.h" // subquery_cache_miss, subquery_cache_hit #include "sys_vars_shared.h" +#include "sys_vars.h" #include "ddl_log.h" #include "optimizer_defaults.h" @@ -519,6 +520,8 @@ uint default_password_lifetime; my_bool disconnect_on_expired_password; bool max_user_connections_checking=0; +bool slave_connections_needed_for_purge_option_used= 0; + /** Limit of the total number of prepared statements in the server. Is necessary to protect the server against out-of-memory attacks. @@ -5847,7 +5850,28 @@ int mysqld_main(int argc, char **argv) #ifdef WITH_WSREP wsrep_set_wsrep_on(nullptr); if (WSREP_ON && wsrep_check_opts()) unireg_abort(1); -#endif + + if (!opt_bootstrap && WSREP_PROVIDER_EXISTS && WSREP_ON) + { + if (global_system_variables.binlog_format != BINLOG_FORMAT_ROW) + { + sql_print_warning("Binlog_format changed to \"ROW\" because of Galera"); + global_system_variables.binlog_format= BINLOG_FORMAT_ROW; + mark_binlog_format_used(binlog_format_used); + } + binlog_format_used= 1; + if (!slave_connections_needed_for_purge_option_used) + { + slave_connections_needed_for_purge= + internal_slave_connections_needed_for_purge= 0; + mark_slave_connections_needed_for_purge_as_auto(); + sql_print_information( + "slave_connections_needed_for_purge changed to 0 because " + "of Galera. Change it to 1 or higher if this Galera node " + "is also Master in a normal replication setup"); + } + } +#endif /* WITH_WSREP */ #ifdef _WIN32 /* @@ -8219,7 +8243,9 @@ mysqld_get_one_option(const struct my_option *opt, const char *argument, ((enum_slave_parallel_mode)opt_slave_parallel_mode); break; } - + case (int) OPT_SLAVE_CONNECTIONS_NEEDED_FOR_PURGE: + slave_connections_needed_for_purge_option_used= 1; + break; case (int)OPT_BINLOG_IGNORE_DB: { binlog_filter->add_ignore_db(argument); @@ -8714,18 +8740,14 @@ static int get_options(int *argc_ptr, char ***argv_ptr) opt_bin_log= opt_bin_log_used= 1; /* Force format to row */ + if (global_system_variables.binlog_format != BINLOG_FORMAT_ROW) + { + sql_print_warning("Binlog_format changed to \"ROW\" because of " + "flashback"); + global_system_variables.binlog_format= BINLOG_FORMAT_ROW; + mark_binlog_format_used(binlog_format_used); + } binlog_format_used= 1; - global_system_variables.binlog_format= BINLOG_FORMAT_ROW; - } - - if (!opt_bootstrap && WSREP_PROVIDER_EXISTS && WSREP_ON && - global_system_variables.binlog_format != BINLOG_FORMAT_ROW) - { - - WSREP_ERROR ("Only binlog_format = 'ROW' is currently supported. " - "Configured value: '%s'. Please adjust your configuration.", - binlog_format_names[global_system_variables.binlog_format]); - return 1; } // Synchronize @@global.autocommit on --autocommit diff --git a/sql/mysqld.h b/sql/mysqld.h index 33492ad7286..61313079f3f 100644 --- a/sql/mysqld.h +++ b/sql/mysqld.h @@ -852,6 +852,7 @@ enum options_mysqld OPT_SILENT, OPT_SKIP_HOST_CACHE, OPT_SLAVE_PARALLEL_MODE, + OPT_SLAVE_CONNECTIONS_NEEDED_FOR_PURGE, OPT_SSL_CA, OPT_SSL_CAPATH, OPT_SSL_CERT, diff --git a/sql/set_var.cc b/sql/set_var.cc index 61528b45de6..de537174825 100644 --- a/sql/set_var.cc +++ b/sql/set_var.cc @@ -1119,7 +1119,8 @@ int fill_sysvars(THD *thd, TABLE_LIST *tables, COND *cond) { STRING_WITH_LEN("AUTO") }, { STRING_WITH_LEN("SQL") }, { STRING_WITH_LEN("COMPILE-TIME") }, - { STRING_WITH_LEN("ENVIRONMENT") } + { STRING_WITH_LEN("ENVIRONMENT") }, + { STRING_WITH_LEN("FORCED") }, }; const LEX_CSTRING *origin= origins + var->value_origin; fields[3]->store(origin->str, origin->length, scs); diff --git a/sql/set_var.h b/sql/set_var.h index aed4955ef62..94ddef6a127 100644 --- a/sql/set_var.h +++ b/sql/set_var.h @@ -66,7 +66,8 @@ class sys_var: protected Value_source // for double_from_string_with_check READONLY=1024, ALLOCATED=2048, PARSE_EARLY=4096, NO_SET_STATEMENT=8192, AUTO_SET=16384}; enum { NO_GETOPT=-1, GETOPT_ONLY_HELP=-2 }; - enum where { CONFIG, COMMAND_LINE, AUTO, SQL, COMPILE_TIME, ENV }; + /* If where is changed, change also GLOBAL_VALUE_ORIGIN in set_var.h */ + enum where { CONFIG, COMMAND_LINE, AUTO, SQL, COMPILE_TIME, ENV, FORCED }; /** Enumeration type to indicate for a system variable whether diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index 895ff090da6..22bc1befa6e 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -612,14 +612,26 @@ static my_bool log_in_use_callback(THD *thd, st_log_in_use *arg) } -bool log_in_use(const char* log_name, uint min_connected) +/* + Check if a log is in use. + + @return 0 Not used + @return 1 A slave is reading from the log + @return 2 There are less than 'min_connected' slaves that + has recived the log. +*/ + +int log_in_use(const char* log_name, uint min_connected) { st_log_in_use arg; arg.log_name= log_name; arg.connected_slaves= 0; - return ((server_threads.iterate(log_in_use_callback, &arg) || - arg.connected_slaves < min_connected)); + if (server_threads.iterate(log_in_use_callback, &arg)) + return 1; + if (arg.connected_slaves < min_connected) + return 2; + return 0; } @@ -659,8 +671,8 @@ bool purge_master_logs(THD* thd, const char* to_log) mysql_bin_log.make_log_name(search_file_name, to_log); return purge_error_message(thd, - mysql_bin_log.purge_logs(search_file_name, 0, 1, - 1, NULL)); + mysql_bin_log.purge_logs(thd, search_file_name, + 0, 1, 1, 1, NULL)); } @@ -683,7 +695,9 @@ bool purge_master_logs_before_date(THD* thd, time_t purge_time) return 0; } return purge_error_message(thd, - mysql_bin_log.purge_logs_before_date(purge_time)); + mysql_bin_log.purge_logs_before_date(thd, + purge_time, + 1)); } void set_read_error(binlog_send_info *info, int error) diff --git a/sql/sql_repl.h b/sql/sql_repl.h index 51b6a599d5f..c03384aa5a3 100644 --- a/sql/sql_repl.h +++ b/sql/sql_repl.h @@ -38,7 +38,7 @@ int reset_master(THD* thd, rpl_gtid *init_state, uint32 init_state_len, ulong next_log_number); bool purge_master_logs(THD* thd, const char* to_log); bool purge_master_logs_before_date(THD* thd, time_t purge_time); -bool log_in_use(const char* log_name, uint min_connections); +int log_in_use(const char* log_name, uint min_connections); void adjust_linfo_offsets(my_off_t purge_offset); void show_binlogs_get_fields(THD *thd, List<Item> *field_list); bool show_binlogs(THD* thd); diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index a2705990736..35a29c29ef8 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -35,6 +35,7 @@ #include "sql_priv.h" #include "sql_class.h" // set_var.h: THD #include "sys_vars.inl" +#include "sys_vars.h" #include "my_sys.h" #include "events.h" @@ -704,6 +705,13 @@ Sys_binlog_format( NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(binlog_format_check), ON_UPDATE(fix_binlog_format_after_update)); + +void mark_binlog_format_used(bool forced) +{ + Sys_binlog_format.value_origin= forced ? sys_var::FORCED : sys_var::AUTO; +} + + static bool binlog_direct_check(sys_var *self, THD *thd, set_var *var) { if (var->type == OPT_GLOBAL) @@ -1265,7 +1273,7 @@ static bool update_binlog_space_limit(sys_var *, THD *, mysql_bin_log.count_binlog_space(); /* Inform can_purge_log() that it should do a recheck of log_in_use() */ sending_new_binlog_file++; - mysql_bin_log.unlock_index(); + mysql_bin_log.unlock_index(); mysql_bin_log.purge(1); return 0; } @@ -1274,6 +1282,7 @@ static bool update_binlog_space_limit(sys_var *, THD *, return 0; } + static Sys_var_on_access_global<Sys_var_ulonglong, PRIV_SET_SYSTEM_GLOBAL_VAR_MAX_BINLOG_CACHE_SIZE> Sys_max_binlog_total_size( @@ -1303,13 +1312,19 @@ Sys_slave_connections_needed_for_purge( "slave_connections_needed_for_purge", "Minimum number of connected slaves required for automatic binary " "log purge with max_binlog_total_size, binlog_expire_logs_seconds " - "or binlog_expire_logs_days.", + "or binlog_expire_logs_days. Default is 0 when Galera is enabled and 1 " + "otherwise.", GLOBAL_VAR(internal_slave_connections_needed_for_purge), - CMD_LINE(REQUIRED_ARG), + CMD_LINE(REQUIRED_ARG, OPT_SLAVE_CONNECTIONS_NEEDED_FOR_PURGE), VALID_RANGE(0, UINT_MAX), DEFAULT(1), BLOCK_SIZE(1), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0), ON_UPDATE(update_binlog_space_limit)); +void mark_slave_connections_needed_for_purge_as_auto() +{ + Sys_slave_connections_needed_for_purge.value_origin= sys_var::AUTO; +} + static Sys_var_mybool Sys_flush( "flush", "Flush MyISAM tables to disk between SQL commands", GLOBAL_VAR(myisam_flush), diff --git a/sql/sys_vars.h b/sql/sys_vars.h new file mode 100644 index 00000000000..8f4eac38cd0 --- /dev/null +++ b/sql/sys_vars.h @@ -0,0 +1,17 @@ +/* Copyright (c) 2024, MariaDB Corporation. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */ + +extern void mark_binlog_format_used(bool forced); +extern void mark_slave_connections_needed_for_purge_as_auto(); -- 2.30.2