From c08d1218c242a11e50817fc173e7bb8706e37f40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Fri, 27 Mar 2026 11:45:37 +0200 Subject: [PATCH 1/7] WIP MDEV-14992 BACKUP SERVER This introduces a basic driver Sql_cmd_backup, storage engine interfaces, and basic copying of InnoDB data files. On Windows, we pass a target directory name; elsewhere, we pass a target directory handle. TODO: Copy the InnoDB log and prevent concurrent log_t::set_archive(). TODO: Implement proper mutual exclusion with buf_page_t::flush(). TODO: Implement other storage engine interfaces. TODO: Implement locking to exclude concurrent BACKUP STAGE. innodb_log_recovery_start: Make settable, for incremental backup. Alas, the space.get_create_lsn() < checkpoint logic does not work yet. --- libmysqld/CMakeLists.txt | 1 + mysql-test/collections/buildbot_suites.bat | 1 + mysql-test/main/backup_server.result | 6 + mysql-test/main/backup_server.test | 10 + mysql-test/main/grant_backup_server.result | 27 ++ mysql-test/main/grant_backup_server.test | 29 ++ mysql-test/main/mysqld--help.result | 2 +- mysql-test/mariadb-test-run.pl | 1 + mysql-test/suite/backup/backup_innodb.result | 21 + mysql-test/suite/backup/backup_innodb.test | 25 + mysql-test/suite/backup/suite.pm | 10 + .../perfschema/r/max_program_zero.result | 2 +- .../suite/perfschema/r/ortho_iter.result | 2 +- .../perfschema/r/privilege_table_io.result | 2 +- .../r/start_server_disable_idle.result | 2 +- .../r/start_server_disable_stages.result | 2 +- .../r/start_server_disable_statements.result | 2 +- .../start_server_disable_transactions.result | 2 +- .../r/start_server_disable_waits.result | 2 +- .../perfschema/r/start_server_innodb.result | 2 +- .../r/start_server_low_index.result | 2 +- .../r/start_server_low_table_lock.result | 2 +- .../r/start_server_no_account.result | 2 +- .../r/start_server_no_cond_class.result | 2 +- .../r/start_server_no_cond_inst.result | 2 +- .../r/start_server_no_file_class.result | 2 +- .../r/start_server_no_file_inst.result | 2 +- .../perfschema/r/start_server_no_host.result | 2 +- .../perfschema/r/start_server_no_index.result | 2 +- .../perfschema/r/start_server_no_mdl.result | 2 +- .../r/start_server_no_memory_class.result | 2 +- .../r/start_server_no_mutex_class.result | 2 +- .../r/start_server_no_mutex_inst.result | 2 +- ..._server_no_prepared_stmts_instances.result | 2 +- .../r/start_server_no_rwlock_class.result | 2 +- .../r/start_server_no_rwlock_inst.result | 2 +- .../r/start_server_no_setup_actors.result | 2 +- .../r/start_server_no_setup_objects.result | 2 +- .../r/start_server_no_socket_class.result | 2 +- .../r/start_server_no_socket_inst.result | 2 +- .../r/start_server_no_stage_class.result | 2 +- .../r/start_server_no_stages_history.result | 2 +- ...start_server_no_stages_history_long.result | 2 +- .../start_server_no_statements_history.result | 2 +- ...t_server_no_statements_history_long.result | 2 +- .../r/start_server_no_table_hdl.result | 2 +- .../r/start_server_no_table_inst.result | 2 +- .../r/start_server_no_table_lock.result | 2 +- .../r/start_server_no_thread_class.result | 2 +- .../r/start_server_no_thread_inst.result | 2 +- ...tart_server_no_transactions_history.result | 2 +- ...server_no_transactions_history_long.result | 2 +- .../perfschema/r/start_server_no_user.result | 2 +- .../r/start_server_no_waits_history.result | 2 +- .../start_server_no_waits_history_long.result | 2 +- .../perfschema/r/start_server_off.result | 2 +- .../suite/perfschema/r/start_server_on.result | 2 +- .../r/start_server_variables.result | 2 +- .../r/statement_program_lost_inst.result | 2 +- .../suite/sys_vars/r/sysvars_innodb.result | 2 +- sql/CMakeLists.txt | 1 + sql/handler.h | 32 ++ sql/mysqld.cc | 1 + sql/sql_backup.cc | 101 ++++ sql/sql_backup.h | 36 ++ sql/sql_command.h | 1 + sql/sql_parse.cc | 4 +- sql/sql_yacc.yy | 6 + sql/sys_vars.inl | 2 + storage/innobase/CMakeLists.txt | 2 + storage/innobase/buf/buf0flu.cc | 3 + storage/innobase/handler/backup_innodb.cc | 440 ++++++++++++++++++ storage/innobase/handler/backup_innodb.h | 46 ++ storage/innobase/handler/ha_innodb.cc | 19 +- storage/innobase/include/log0log.h | 10 +- storage/innobase/log/log0log.cc | 17 +- storage/innobase/log/log0recv.cc | 9 +- 77 files changed, 896 insertions(+), 65 deletions(-) create mode 100644 mysql-test/main/backup_server.result create mode 100644 mysql-test/main/backup_server.test create mode 100644 mysql-test/main/grant_backup_server.result create mode 100644 mysql-test/main/grant_backup_server.test create mode 100644 mysql-test/suite/backup/backup_innodb.result create mode 100644 mysql-test/suite/backup/backup_innodb.test create mode 100644 mysql-test/suite/backup/suite.pm create mode 100644 sql/sql_backup.cc create mode 100644 sql/sql_backup.h create mode 100644 storage/innobase/handler/backup_innodb.cc create mode 100644 storage/innobase/handler/backup_innodb.h diff --git a/libmysqld/CMakeLists.txt b/libmysqld/CMakeLists.txt index eb5e38427f7f6..99eb5ea0cdfcc 100644 --- a/libmysqld/CMakeLists.txt +++ b/libmysqld/CMakeLists.txt @@ -166,6 +166,7 @@ SET(SQL_EMBEDDED_SOURCES emb_qcache.cc libmysqld.c lib_sql.cc ../sql/opt_hints.cc ../sql/opt_hints.h ../sql/opt_trace_ddl_info.cc ../sql/opt_trace_ddl_info.h ../sql/sql_path.cc + ../sql/sql_backup.cc ${GEN_SOURCES} ${MYSYS_LIBWRAP_SOURCE} ) diff --git a/mysql-test/collections/buildbot_suites.bat b/mysql-test/collections/buildbot_suites.bat index 053057872bf40..129a2f67d79de 100644 --- a/mysql-test/collections/buildbot_suites.bat +++ b/mysql-test/collections/buildbot_suites.bat @@ -6,6 +6,7 @@ innodb,^ versioning,^ plugins,^ mariabackup,^ +backup,^ roles,^ auth_gssapi,^ mysql_sha2,^ diff --git a/mysql-test/main/backup_server.result b/mysql-test/main/backup_server.result new file mode 100644 index 0000000000000..7a064d7e5fe11 --- /dev/null +++ b/mysql-test/main/backup_server.result @@ -0,0 +1,6 @@ +BACKUP SERVER TO '$datadir/some_directory'; +ERROR HY000: Incorrect arguments to BACKUP SERVER TO +BACKUP SERVER TO '$MYSQLTEST_VARDIR/some_directory'; +BACKUP SERVER TO '$MYSQLTEST_VARDIR/some_directory'; +ERROR HY000: Can't create directory 'MYSQLTEST_VARDIR/some_directory' (Errcode: 17 "File exists") +BACKUP SERVER TO '$MYSQLTEST_VARDIR/some_directory'; diff --git a/mysql-test/main/backup_server.test b/mysql-test/main/backup_server.test new file mode 100644 index 0000000000000..50cf326d39838 --- /dev/null +++ b/mysql-test/main/backup_server.test @@ -0,0 +1,10 @@ +--let $datadir=`select @@datadir` +--error ER_WRONG_ARGUMENTS +evalp BACKUP SERVER TO '$datadir/some_directory'; +evalp BACKUP SERVER TO '$MYSQLTEST_VARDIR/some_directory'; +--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR +--error 21 +evalp BACKUP SERVER TO '$MYSQLTEST_VARDIR/some_directory'; +--rmdir $MYSQLTEST_VARDIR/some_directory +evalp BACKUP SERVER TO '$MYSQLTEST_VARDIR/some_directory'; +--rmdir $MYSQLTEST_VARDIR/some_directory diff --git a/mysql-test/main/grant_backup_server.result b/mysql-test/main/grant_backup_server.result new file mode 100644 index 0000000000000..a4d85292dee45 --- /dev/null +++ b/mysql-test/main/grant_backup_server.result @@ -0,0 +1,27 @@ +CREATE USER user1@localhost IDENTIFIED BY ''; +connect con1,localhost,user1; +BACKUP SERVER TO 'some_directory'; +ERROR 42000: Access denied; you need (at least one of) the RELOAD privilege(s) for this operation +disconnect con1; +connection default; +GRANT SELECT ON test.* TO user1@localhost; +connect con1,localhost,user1; +BACKUP SERVER TO 'some_directory'; +ERROR 42000: Access denied; you need (at least one of) the RELOAD privilege(s) for this operation +disconnect con1; +connection default; +GRANT RELOAD ON test.* TO user1@localhost; +ERROR HY000: Incorrect usage of DB GRANT and GLOBAL PRIVILEGES +GRANT RELOAD ON *.* TO user1@localhost; +connect con1,localhost,user1; +BACKUP SERVER TO 'some_directory'; +ERROR 42000: Access denied; you need (at least one of) the SELECT privilege(s) for this operation +disconnect con1; +connection default; +GRANT SELECT ON *.* TO user1@localhost; +connect con1,localhost,user1; +BACKUP SERVER TO '$datadir/some_directory'; +ERROR HY000: Incorrect arguments to BACKUP SERVER TO +disconnect con1; +connection default; +DROP USER user1@localhost; diff --git a/mysql-test/main/grant_backup_server.test b/mysql-test/main/grant_backup_server.test new file mode 100644 index 0000000000000..726abb19908bb --- /dev/null +++ b/mysql-test/main/grant_backup_server.test @@ -0,0 +1,29 @@ +--source include/not_embedded.inc +CREATE USER user1@localhost IDENTIFIED BY ''; +--connect con1,localhost,user1 +--error ER_SPECIFIC_ACCESS_DENIED_ERROR +BACKUP SERVER TO 'some_directory'; +--disconnect con1 +--connection default +GRANT SELECT ON test.* TO user1@localhost; +--connect con1,localhost,user1 +--error ER_SPECIFIC_ACCESS_DENIED_ERROR +BACKUP SERVER TO 'some_directory'; +--disconnect con1 +--connection default +--error ER_WRONG_USAGE +GRANT RELOAD ON test.* TO user1@localhost; +GRANT RELOAD ON *.* TO user1@localhost; +--connect con1,localhost,user1 +--error ER_SPECIFIC_ACCESS_DENIED_ERROR +BACKUP SERVER TO 'some_directory'; +--disconnect con1 +--connection default +GRANT SELECT ON *.* TO user1@localhost; +--connect con1,localhost,user1 +--let $datadir=`select @@datadir` +--error ER_WRONG_ARGUMENTS +evalp BACKUP SERVER TO '$datadir/some_directory'; +--disconnect con1 +--connection default +DROP USER user1@localhost; diff --git a/mysql-test/main/mysqld--help.result b/mysql-test/main/mysqld--help.result index 3e6804a150dc8..00575695fa90f 100644 --- a/mysql-test/main/mysqld--help.result +++ b/mysql-test/main/mysqld--help.result @@ -2056,7 +2056,7 @@ performance-schema-max-socket-classes 10 performance-schema-max-socket-instances -1 performance-schema-max-sql-text-length 1024 performance-schema-max-stage-classes 170 -performance-schema-max-statement-classes 227 +performance-schema-max-statement-classes 228 performance-schema-max-statement-stack 10 performance-schema-max-table-handles -1 performance-schema-max-table-instances -1 diff --git a/mysql-test/mariadb-test-run.pl b/mysql-test/mariadb-test-run.pl index 8e200af6e208b..8b2e671588adf 100755 --- a/mysql-test/mariadb-test-run.pl +++ b/mysql-test/mariadb-test-run.pl @@ -180,6 +180,7 @@ END main- archive- atomic- + backup- binlog- binlog_encryption- binlog_in_engine- diff --git a/mysql-test/suite/backup/backup_innodb.result b/mysql-test/suite/backup/backup_innodb.result new file mode 100644 index 0000000000000..ee2d34ae10bc9 --- /dev/null +++ b/mysql-test/suite/backup/backup_innodb.result @@ -0,0 +1,21 @@ +CREATE TABLE t(i INT PRIMARY KEY) ENGINE=INNODB; +INSERT INTO t VALUES(1); +BEGIN; +DELETE FROM t; +connect backup,localhost,root; +BACKUP SERVER TO '$MYSQLTEST_VARDIR/some_directory'; +disconnect backup; +connection default; +COMMIT; +SELECT * FROM t; +i +ibdata1 +mysql +test +undo001 +undo002 +undo003 +# restart +SELECT * FROM t; +i +DROP TABLE t; diff --git a/mysql-test/suite/backup/backup_innodb.test b/mysql-test/suite/backup/backup_innodb.test new file mode 100644 index 0000000000000..cbad0c803b1e0 --- /dev/null +++ b/mysql-test/suite/backup/backup_innodb.test @@ -0,0 +1,25 @@ +--source include/have_innodb.inc + +CREATE TABLE t(i INT PRIMARY KEY) ENGINE=INNODB; +INSERT INTO t VALUES(1); +BEGIN; +DELETE FROM t; + +--connect backup,localhost,root +evalp BACKUP SERVER TO '$MYSQLTEST_VARDIR/some_directory'; +--disconnect backup +--connection default + +COMMIT; +SELECT * FROM t; + +let $datadir=`SELECT @@datadir`; +--source include/shutdown_mysqld.inc +--list_files $MYSQLTEST_VARDIR/some_directory +#--rmdir $datadir +#--move_file $MYSQLTEST_VARDIR/some_directory $datadir +--rmdir $MYSQLTEST_VARDIR/some_directory +--source include/start_mysqld.inc + +SELECT * FROM t; +DROP TABLE t; diff --git a/mysql-test/suite/backup/suite.pm b/mysql-test/suite/backup/suite.pm new file mode 100644 index 0000000000000..e24cb5b5185b1 --- /dev/null +++ b/mysql-test/suite/backup/suite.pm @@ -0,0 +1,10 @@ +package My::Suite::Backup; + +@ISA = qw(My::Suite); +use My::Find; +use File::Basename; +use strict; + +return "Not run for embedded server" if $::opt_embedded_server; + +bless { }; diff --git a/mysql-test/suite/perfschema/r/max_program_zero.result b/mysql-test/suite/perfschema/r/max_program_zero.result index 047643e06988d..a0e486b2af9c0 100644 --- a/mysql-test/suite/perfschema/r/max_program_zero.result +++ b/mysql-test/suite/perfschema/r/max_program_zero.result @@ -129,7 +129,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 170 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 1 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/ortho_iter.result b/mysql-test/suite/perfschema/r/ortho_iter.result index 56c22c8453d8e..589704f4056de 100644 --- a/mysql-test/suite/perfschema/r/ortho_iter.result +++ b/mysql-test/suite/perfschema/r/ortho_iter.result @@ -251,7 +251,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 170 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/privilege_table_io.result b/mysql-test/suite/perfschema/r/privilege_table_io.result index 0c82be9f05810..b518052613308 100644 --- a/mysql-test/suite/perfschema/r/privilege_table_io.result +++ b/mysql-test/suite/perfschema/r/privilege_table_io.result @@ -57,7 +57,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 170 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_disable_idle.result b/mysql-test/suite/perfschema/r/start_server_disable_idle.result index d0665e3bf4c65..12b956d90769c 100644 --- a/mysql-test/suite/perfschema/r/start_server_disable_idle.result +++ b/mysql-test/suite/perfschema/r/start_server_disable_idle.result @@ -129,7 +129,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 170 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_disable_stages.result b/mysql-test/suite/perfschema/r/start_server_disable_stages.result index 2ef68328144ff..30ce9d12e56a0 100644 --- a/mysql-test/suite/perfschema/r/start_server_disable_stages.result +++ b/mysql-test/suite/perfschema/r/start_server_disable_stages.result @@ -129,7 +129,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 170 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_disable_statements.result b/mysql-test/suite/perfschema/r/start_server_disable_statements.result index 0ece2a0c52ed1..4bacdbdfedf68 100644 --- a/mysql-test/suite/perfschema/r/start_server_disable_statements.result +++ b/mysql-test/suite/perfschema/r/start_server_disable_statements.result @@ -129,7 +129,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 170 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_disable_transactions.result b/mysql-test/suite/perfschema/r/start_server_disable_transactions.result index ededc09aac95d..3a6831eb2c7ff 100644 --- a/mysql-test/suite/perfschema/r/start_server_disable_transactions.result +++ b/mysql-test/suite/perfschema/r/start_server_disable_transactions.result @@ -129,7 +129,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 170 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_disable_waits.result b/mysql-test/suite/perfschema/r/start_server_disable_waits.result index 23db9362161e4..a864576dfa979 100644 --- a/mysql-test/suite/perfschema/r/start_server_disable_waits.result +++ b/mysql-test/suite/perfschema/r/start_server_disable_waits.result @@ -129,7 +129,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 170 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_innodb.result b/mysql-test/suite/perfschema/r/start_server_innodb.result index cff87457b3bef..09cba65c57ce0 100644 --- a/mysql-test/suite/perfschema/r/start_server_innodb.result +++ b/mysql-test/suite/perfschema/r/start_server_innodb.result @@ -129,7 +129,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 170 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_low_index.result b/mysql-test/suite/perfschema/r/start_server_low_index.result index 11cade0a2132f..dbd36d2eaa92d 100644 --- a/mysql-test/suite/perfschema/r/start_server_low_index.result +++ b/mysql-test/suite/perfschema/r/start_server_low_index.result @@ -129,7 +129,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 170 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_low_table_lock.result b/mysql-test/suite/perfschema/r/start_server_low_table_lock.result index 484550095202e..fab64f49d45e6 100644 --- a/mysql-test/suite/perfschema/r/start_server_low_table_lock.result +++ b/mysql-test/suite/perfschema/r/start_server_low_table_lock.result @@ -129,7 +129,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 170 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_account.result b/mysql-test/suite/perfschema/r/start_server_no_account.result index aab8d3eba9caa..e2cccdba19b43 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_account.result +++ b/mysql-test/suite/perfschema/r/start_server_no_account.result @@ -129,7 +129,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 170 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_cond_class.result b/mysql-test/suite/perfschema/r/start_server_no_cond_class.result index 4dfdab9de9f30..44b114013ace2 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_cond_class.result +++ b/mysql-test/suite/perfschema/r/start_server_no_cond_class.result @@ -129,7 +129,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 170 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_cond_inst.result b/mysql-test/suite/perfschema/r/start_server_no_cond_inst.result index a8d0cbeca3855..27ecb59a40f17 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_cond_inst.result +++ b/mysql-test/suite/perfschema/r/start_server_no_cond_inst.result @@ -129,7 +129,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 170 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_file_class.result b/mysql-test/suite/perfschema/r/start_server_no_file_class.result index fcc01880a7107..5189d36618b05 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_file_class.result +++ b/mysql-test/suite/perfschema/r/start_server_no_file_class.result @@ -129,7 +129,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 170 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_file_inst.result b/mysql-test/suite/perfschema/r/start_server_no_file_inst.result index c56201e7d0a80..533c02383c7b8 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_file_inst.result +++ b/mysql-test/suite/perfschema/r/start_server_no_file_inst.result @@ -129,7 +129,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 170 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_host.result b/mysql-test/suite/perfschema/r/start_server_no_host.result index 662beb3b88a49..e328ea3d3c26a 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_host.result +++ b/mysql-test/suite/perfschema/r/start_server_no_host.result @@ -129,7 +129,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 170 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_index.result b/mysql-test/suite/perfschema/r/start_server_no_index.result index ccff0cb113faa..686f430cdc440 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_index.result +++ b/mysql-test/suite/perfschema/r/start_server_no_index.result @@ -129,7 +129,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 170 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_mdl.result b/mysql-test/suite/perfschema/r/start_server_no_mdl.result index ebe64409deb0c..c2b6dc3d9eb74 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_mdl.result +++ b/mysql-test/suite/perfschema/r/start_server_no_mdl.result @@ -129,7 +129,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 170 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_memory_class.result b/mysql-test/suite/perfschema/r/start_server_no_memory_class.result index 01a217c534bfd..2a4cdcf8ddd37 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_memory_class.result +++ b/mysql-test/suite/perfschema/r/start_server_no_memory_class.result @@ -129,7 +129,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 170 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_mutex_class.result b/mysql-test/suite/perfschema/r/start_server_no_mutex_class.result index 1b3efda5210a9..9b77c7b897c47 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_mutex_class.result +++ b/mysql-test/suite/perfschema/r/start_server_no_mutex_class.result @@ -129,7 +129,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 170 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_mutex_inst.result b/mysql-test/suite/perfschema/r/start_server_no_mutex_inst.result index 599498915f3f1..3a62653efc18f 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_mutex_inst.result +++ b/mysql-test/suite/perfschema/r/start_server_no_mutex_inst.result @@ -129,7 +129,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 170 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_prepared_stmts_instances.result b/mysql-test/suite/perfschema/r/start_server_no_prepared_stmts_instances.result index 73ac1acb9f145..7dd5842809135 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_prepared_stmts_instances.result +++ b/mysql-test/suite/perfschema/r/start_server_no_prepared_stmts_instances.result @@ -129,7 +129,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 170 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_rwlock_class.result b/mysql-test/suite/perfschema/r/start_server_no_rwlock_class.result index 04aa037b960e3..3598c722b9453 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_rwlock_class.result +++ b/mysql-test/suite/perfschema/r/start_server_no_rwlock_class.result @@ -129,7 +129,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 170 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_rwlock_inst.result b/mysql-test/suite/perfschema/r/start_server_no_rwlock_inst.result index e8156711eb3f2..7a60805d2d03f 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_rwlock_inst.result +++ b/mysql-test/suite/perfschema/r/start_server_no_rwlock_inst.result @@ -129,7 +129,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 170 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_setup_actors.result b/mysql-test/suite/perfschema/r/start_server_no_setup_actors.result index 2d17bd6f49203..76da2883ba6b2 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_setup_actors.result +++ b/mysql-test/suite/perfschema/r/start_server_no_setup_actors.result @@ -129,7 +129,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 170 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_setup_objects.result b/mysql-test/suite/perfschema/r/start_server_no_setup_objects.result index 16afee28ee70b..58ac763394a96 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_setup_objects.result +++ b/mysql-test/suite/perfschema/r/start_server_no_setup_objects.result @@ -129,7 +129,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 170 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_socket_class.result b/mysql-test/suite/perfschema/r/start_server_no_socket_class.result index 9de0006aa7abc..5a86d51a06231 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_socket_class.result +++ b/mysql-test/suite/perfschema/r/start_server_no_socket_class.result @@ -129,7 +129,7 @@ performance_schema_max_socket_classes 0 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 170 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_socket_inst.result b/mysql-test/suite/perfschema/r/start_server_no_socket_inst.result index bcef0e5c01b05..75ab84f4b3cb8 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_socket_inst.result +++ b/mysql-test/suite/perfschema/r/start_server_no_socket_inst.result @@ -129,7 +129,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 0 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 170 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_stage_class.result b/mysql-test/suite/perfschema/r/start_server_no_stage_class.result index 1dda39dc79e92..bb750ebd34a26 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_stage_class.result +++ b/mysql-test/suite/perfschema/r/start_server_no_stage_class.result @@ -129,7 +129,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 0 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_stages_history.result b/mysql-test/suite/perfschema/r/start_server_no_stages_history.result index 95584521eceb2..6cf3f740fb1be 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_stages_history.result +++ b/mysql-test/suite/perfschema/r/start_server_no_stages_history.result @@ -129,7 +129,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 170 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_stages_history_long.result b/mysql-test/suite/perfschema/r/start_server_no_stages_history_long.result index da6c54b6bbafa..1f2716410a9bc 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_stages_history_long.result +++ b/mysql-test/suite/perfschema/r/start_server_no_stages_history_long.result @@ -129,7 +129,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 170 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_statements_history.result b/mysql-test/suite/perfschema/r/start_server_no_statements_history.result index 09a9a544f5b13..f1078542f69ef 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_statements_history.result +++ b/mysql-test/suite/perfschema/r/start_server_no_statements_history.result @@ -129,7 +129,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 170 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_statements_history_long.result b/mysql-test/suite/perfschema/r/start_server_no_statements_history_long.result index 5ce9874799e8b..a6fc523df2c60 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_statements_history_long.result +++ b/mysql-test/suite/perfschema/r/start_server_no_statements_history_long.result @@ -129,7 +129,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 170 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_table_hdl.result b/mysql-test/suite/perfschema/r/start_server_no_table_hdl.result index a170fe097fd23..304732cb37927 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_table_hdl.result +++ b/mysql-test/suite/perfschema/r/start_server_no_table_hdl.result @@ -129,7 +129,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 170 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 0 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_table_inst.result b/mysql-test/suite/perfschema/r/start_server_no_table_inst.result index 3f009de021d1a..3ef43495d0f22 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_table_inst.result +++ b/mysql-test/suite/perfschema/r/start_server_no_table_inst.result @@ -129,7 +129,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 170 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 0 diff --git a/mysql-test/suite/perfschema/r/start_server_no_table_lock.result b/mysql-test/suite/perfschema/r/start_server_no_table_lock.result index db4fe3413106b..20cf1a531ca51 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_table_lock.result +++ b/mysql-test/suite/perfschema/r/start_server_no_table_lock.result @@ -129,7 +129,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 170 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_thread_class.result b/mysql-test/suite/perfschema/r/start_server_no_thread_class.result index b1b6e614c43ac..e8eb4589fce56 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_thread_class.result +++ b/mysql-test/suite/perfschema/r/start_server_no_thread_class.result @@ -129,7 +129,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 170 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_thread_inst.result b/mysql-test/suite/perfschema/r/start_server_no_thread_inst.result index e540f3ce78cbd..4dbf292fc3c10 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_thread_inst.result +++ b/mysql-test/suite/perfschema/r/start_server_no_thread_inst.result @@ -129,7 +129,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 170 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_transactions_history.result b/mysql-test/suite/perfschema/r/start_server_no_transactions_history.result index 80da238ba0ed7..09168c68f9fe8 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_transactions_history.result +++ b/mysql-test/suite/perfschema/r/start_server_no_transactions_history.result @@ -129,7 +129,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 170 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_transactions_history_long.result b/mysql-test/suite/perfschema/r/start_server_no_transactions_history_long.result index f5d32f0100968..9ec63e991deda 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_transactions_history_long.result +++ b/mysql-test/suite/perfschema/r/start_server_no_transactions_history_long.result @@ -129,7 +129,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 170 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_user.result b/mysql-test/suite/perfschema/r/start_server_no_user.result index cb249b4e242d3..861b7d897c9c5 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_user.result +++ b/mysql-test/suite/perfschema/r/start_server_no_user.result @@ -129,7 +129,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 170 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_waits_history.result b/mysql-test/suite/perfschema/r/start_server_no_waits_history.result index c419aad2995f3..710cef232da58 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_waits_history.result +++ b/mysql-test/suite/perfschema/r/start_server_no_waits_history.result @@ -129,7 +129,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 170 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_waits_history_long.result b/mysql-test/suite/perfschema/r/start_server_no_waits_history_long.result index 0b2bf39ff874f..df4351a8f358f 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_waits_history_long.result +++ b/mysql-test/suite/perfschema/r/start_server_no_waits_history_long.result @@ -129,7 +129,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 170 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_off.result b/mysql-test/suite/perfschema/r/start_server_off.result index 17db0395d894b..f3bf7bf7fb15e 100644 --- a/mysql-test/suite/perfschema/r/start_server_off.result +++ b/mysql-test/suite/perfschema/r/start_server_off.result @@ -129,7 +129,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 170 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_on.result b/mysql-test/suite/perfschema/r/start_server_on.result index cff87457b3bef..09cba65c57ce0 100644 --- a/mysql-test/suite/perfschema/r/start_server_on.result +++ b/mysql-test/suite/perfschema/r/start_server_on.result @@ -129,7 +129,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 170 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_variables.result b/mysql-test/suite/perfschema/r/start_server_variables.result index f25f9ab69d1c3..b86116e099cdd 100644 --- a/mysql-test/suite/perfschema/r/start_server_variables.result +++ b/mysql-test/suite/perfschema/r/start_server_variables.result @@ -129,7 +129,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 170 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/statement_program_lost_inst.result b/mysql-test/suite/perfschema/r/statement_program_lost_inst.result index 442e212557b55..3e8469b881801 100644 --- a/mysql-test/suite/perfschema/r/statement_program_lost_inst.result +++ b/mysql-test/suite/perfschema/r/statement_program_lost_inst.result @@ -129,7 +129,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 170 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 228 performance_schema_max_statement_stack 2 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/sys_vars/r/sysvars_innodb.result b/mysql-test/suite/sys_vars/r/sysvars_innodb.result index 808b95a712d83..1c99d71401803 100644 --- a/mysql-test/suite/sys_vars/r/sysvars_innodb.result +++ b/mysql-test/suite/sys_vars/r/sysvars_innodb.result @@ -1051,7 +1051,7 @@ NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 18446744073709551615 NUMERIC_BLOCK_SIZE 0 ENUM_VALUE_LIST NULL -READ_ONLY YES +READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME INNODB_LOG_RECOVERY_TARGET SESSION_VALUE NULL diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index 32af809849be1..b77ecff4017d7 100644 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -163,6 +163,7 @@ SET (SQL_SOURCE grant.cc sql_explain.cc sql_analyze_stmt.cc + sql_backup.cc sql_join_cache.cc create_options.cc multi_range_read.cc opt_histogram_json.cc diff --git a/sql/handler.h b/sql/handler.h index 354e5d6192d6a..2c96cdeab90fe 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -1892,9 +1892,41 @@ struct handlerton : public transaction_participant /********************************************************************* backup **********************************************************************/ + + /** BACKUP STAGE START */ void (*prepare_for_backup)(void); + /** BACKUP STAGE END */ void (*end_backup)(void); + /** + Start of BACKUP SERVER: collect all files to be backed up + @param thd current session + @param target target directory + @return error code + @retval 0 on success + */ + int (*backup_start)(THD *thd, IF_WIN(const char*,int) target); + /** + Process a file that was collected in backup_start(). + @param thd current session + @return number of files remaining, or negative on error + @retval 0 on completion + */ + int (*backup_step)(THD *thd); + /** + Finish copying and determine the logical time of the backup snapshot. + @param thd current sesssion + @param abort whether BACKUP SERVER was aborted + @return error code + @retval 0 on success + */ + int (*backup_end)(THD *thd, bool abort); + /** + After a successful backup_end(), finalize the backup. + @param thd current sesssion + */ + void (*backup_finalize)(THD *thd); + /********************************************************************** WSREP specific **********************************************************************/ diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 869adb1fc349b..4cdf3a5e322c0 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -3502,6 +3502,7 @@ SHOW_VAR com_status_vars[]= { {"assign_to_keycache", STMT_STATUS(SQLCOM_ASSIGN_TO_KEYCACHE)}, {"backup", STMT_STATUS(SQLCOM_BACKUP)}, {"backup_lock", STMT_STATUS(SQLCOM_BACKUP_LOCK)}, + {"backup_server", STMT_STATUS(SQLCOM_BACKUP_SERVER)}, {"begin", STMT_STATUS(SQLCOM_BEGIN)}, {"binlog", STMT_STATUS(SQLCOM_BINLOG_BASE64_EVENT)}, {"call_procedure", STMT_STATUS(SQLCOM_CALL)}, diff --git a/sql/sql_backup.cc b/sql/sql_backup.cc new file mode 100644 index 0000000000000..39254b45de1f8 --- /dev/null +++ b/sql/sql_backup.cc @@ -0,0 +1,101 @@ +/* Copyright (c) 2026, MariaDB plc + + 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 */ + +#include "my_global.h" +#include "mysys_err.h" +#include "sql_class.h" +#include "sql_backup.h" +#include "sql_parse.h" + +static my_bool backup_start(THD *thd, plugin_ref plugin, void *dst) noexcept +{ + handlerton *hton= plugin_hton(plugin); + if (hton->backup_start) + return hton->backup_start(thd, + IF_WIN(static_cast(dst), + int(reinterpret_cast(dst)))); + return false; +} + +static my_bool backup_end(THD *thd, plugin_ref plugin, void *arg) noexcept +{ + handlerton *hton= plugin_hton(plugin); + if (hton->backup_end) + return hton->backup_end(thd, arg != nullptr); + return false; +} + +static my_bool backup_step(THD *thd, plugin_ref plugin, void *) noexcept +{ + handlerton *hton= plugin_hton(plugin); + int res= 0; + if (hton->backup_step) + while ((res= hton->backup_step(thd))) + if (res < 0) + break; + return res != 0; +} + +static my_bool backup_finalize(THD *thd, plugin_ref plugin, void *) noexcept +{ + handlerton *hton= plugin_hton(plugin); + if (hton->backup_step) + hton->backup_finalize(thd); + return 0; +} + +bool Sql_cmd_backup::execute(THD *thd) +{ + if (check_global_access(thd, RELOAD_ACL) || + check_global_access(thd, SELECT_ACL) || + error_if_data_home_dir(target.str, "BACKUP SERVER TO")) + return true; + + if (my_mkdir(target.str, 0755, MYF(MY_WME))) + return true; + +#ifndef _WIN32 + int dir= open(target.str, O_DIRECTORY); + if (dir < 0) + { + my_error(EE_CANT_MKDIR, MYF(ME_BELL), target.str, errno); + return true; + } +#endif + + bool fail= plugin_foreach_with_mask(thd, backup_start, + MYSQL_STORAGE_ENGINE_PLUGIN, + PLUGIN_IS_DELETED|PLUGIN_IS_READY, + IF_WIN(const_cast(target.str), + reinterpret_cast(dir))); + if (!fail) + fail= plugin_foreach_with_mask(thd, backup_step, + MYSQL_STORAGE_ENGINE_PLUGIN, + PLUGIN_IS_DELETED|PLUGIN_IS_READY, nullptr); + + plugin_foreach_with_mask(thd, backup_end, MYSQL_STORAGE_ENGINE_PLUGIN, + PLUGIN_IS_DELETED|PLUGIN_IS_READY, + reinterpret_cast(fail)); + + plugin_foreach_with_mask(thd, backup_finalize, MYSQL_STORAGE_ENGINE_PLUGIN, + PLUGIN_IS_DELETED|PLUGIN_IS_READY, nullptr); +#ifndef _WIN32 + close(dir); +#endif + + if (!fail) + my_ok(thd); + return fail; +} diff --git a/sql/sql_backup.h b/sql/sql_backup.h new file mode 100644 index 0000000000000..9aba2404dac58 --- /dev/null +++ b/sql/sql_backup.h @@ -0,0 +1,36 @@ +/***************************************************************************** +Copyright (c) 2026 MariaDB plc. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA + +*****************************************************************************/ + +#pragma once + +/** BACKUP SERVER */ +class Sql_cmd_backup : public Sql_cmd +{ + /** target directory */ + const LEX_CSTRING target; + +public: + explicit Sql_cmd_backup(LEX_CSTRING target) : target(target) {} + ~Sql_cmd_backup() = default; + + bool execute(THD *thd) override; + + enum_sql_command sql_command_code() const override + { + return SQLCOM_BACKUP_SERVER; + } +}; diff --git a/sql/sql_command.h b/sql/sql_command.h index 9c9166706a034..b8903399711f0 100644 --- a/sql/sql_command.h +++ b/sql/sql_command.h @@ -103,6 +103,7 @@ enum enum_sql_command { SQLCOM_SHOW_PACKAGE_BODY_CODE, SQLCOM_BACKUP, SQLCOM_BACKUP_LOCK, SQLCOM_SHOW_CREATE_SERVER, + SQLCOM_BACKUP_SERVER, /* When a command is added here, be sure it's also added in mysqld.cc diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 6250b450cc82e..bc35da5af1ce1 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -776,6 +776,7 @@ void init_update_queries(void) sql_command_flags[SQLCOM_DROP_SERVER]= CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_BACKUP]= CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_BACKUP_LOCK]= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_BACKUP_SERVER]= CF_AUTO_COMMIT_TRANS; /* The following statements can deal with temporary tables, @@ -5899,6 +5900,7 @@ mysql_execute_command(THD *thd, bool is_called_from_prepared_stmt) case SQLCOM_CALL: case SQLCOM_REVOKE: case SQLCOM_GRANT: + case SQLCOM_BACKUP_SERVER: if (thd->variables.option_bits & OPTION_IF_EXISTS) lex->create_info.set(DDL_options_st::OPT_IF_EXISTS); DBUG_ASSERT(lex->m_sql_cmd != NULL); @@ -10246,7 +10248,7 @@ int test_if_data_home_dir(const char *dir) if (!dir) DBUG_RETURN(0); - (void) fn_format(path, dir, "", "", MY_RETURN_REAL_PATH); + (void) fn_format(path, dir, "", "", MY_RETURN_REAL_PATH|MY_RESOLVE_SYMLINKS); DBUG_RETURN(path_starts_from_data_home_dir(path)); } diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index c9826639eaaa6..ac78a64c5cd75 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -50,6 +50,7 @@ #include "sql_alter.h" // Sql_cmd_alter_table* #include "sql_truncate.h" // Sql_cmd_truncate_table #include "sql_admin.h" // Sql_cmd_analyze/Check..._table +#include "sql_backup.h" #include "sql_partition_admin.h" // Sql_cmd_alter_table_*_part. #include "sql_handler.h" // Sql_cmd_handler_* #include "sql_signal.h" @@ -15513,6 +15514,11 @@ backup_statements: /* Table list is empty for unlock */ Lex->sql_command= SQLCOM_BACKUP_LOCK; } + | SERVER_SYM TO_SYM TEXT_STRING_sys + { + Lex->sql_command= SQLCOM_BACKUP_SERVER; + Lex->m_sql_cmd= new (thd->mem_root) Sql_cmd_backup($3); + } ; opt_delete_gtid_domain: diff --git a/sql/sys_vars.inl b/sql/sys_vars.inl index 1c0984d596611..51c81bfceb397 100644 --- a/sql/sys_vars.inl +++ b/sql/sys_vars.inl @@ -2506,6 +2506,7 @@ public: bool session_update(THD *thd, set_var *var) override; }; +#ifdef HAVE_REPLICATION /* Class for replicate_events_marked_for_skip. We need a custom update function that ensures the slave is stopped when @@ -2639,6 +2640,7 @@ public: } const uchar *global_value_ptr(THD *thd, const LEX_CSTRING *base) const override; }; +#endif /* HAVE_REPLICATION */ /** diff --git a/storage/innobase/CMakeLists.txt b/storage/innobase/CMakeLists.txt index 9e3a23b34ab46..d63751a16af08 100644 --- a/storage/innobase/CMakeLists.txt +++ b/storage/innobase/CMakeLists.txt @@ -185,6 +185,8 @@ SET(INNOBASE_SOURCES handler/handler0alter.cc handler/innodb_binlog.cc handler/i_s.cc + handler/backup_innodb.h + handler/backup_innodb.cc ibuf/ibuf0ibuf.cc include/btr0btr.h include/btr0btr.inl diff --git a/storage/innobase/buf/buf0flu.cc b/storage/innobase/buf/buf0flu.cc index f3aa0de21b3ba..8adf74d0ee6a8 100644 --- a/storage/innobase/buf/buf0flu.cc +++ b/storage/innobase/buf/buf0flu.cc @@ -2007,7 +2007,10 @@ inline void log_t::write_checkpoint(lsn_t checkpoint, lsn_t end_lsn) noexcept last_checkpoint_lsn= checkpoint; this->end_lsn= end_lsn; if (!archive) + { + archived_checkpoint= checkpoint; archived_lsn= end_lsn; + } else if (archive_header_was_reset) { ut_ad(resize_log.m_file != log.m_file); diff --git a/storage/innobase/handler/backup_innodb.cc b/storage/innobase/handler/backup_innodb.cc new file mode 100644 index 0000000000000..88e639bf0e26e --- /dev/null +++ b/storage/innobase/handler/backup_innodb.cc @@ -0,0 +1,440 @@ +/* Copyright (c) 2026, MariaDB plc + + 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 */ + +#include "my_global.h" +#include "sql_class.h" +#include "backup_innodb.h" +#include "log0log.h" +#include "srw_lock.h" +#include "fil0fil.h" +#include + +#ifdef _WIN32 +#elif defined __APPLE__ +# include +# include +# include +#else +# ifdef __linux__ +# include +/* Copy a file to a stream or to a regular file. */ +static inline ssize_t +send_step(int in_fd, int out_fd, size_t count, off_t *offset) noexcept +{ + return sendfile(out_fd, in_fd, offset, count); +} +# endif +# if defined __linux__ || defined __FreeBSD__ +/* Copy between files in a single (type of) file system */ +static inline ssize_t +copy_step(int in_fd, int out_fd, size_t count, off_t *offset) noexcept +{ + return copy_file_range(in_fd, offset, out_fd, nullptr, count, 0); +} +# endif +# ifndef __linux__ +# include +/** Copy a file using a memory mapping. +@param in_fd source file +@param out_fd destination +@param count number of bytes to copy +@return error code +@retval 0 on success +@retval 1 if a memory mapping failed */ +static ssize_t mmap_copy(int in_fd, int out_fd, off_t count) +{ +#if SIZEOF_SIZE_T < 8 + if (count != ssize_t(count)) + return 1; +#endif +#if 1 + return 1; +#endif + void *p= mmap(nullptr, count, PROT_READ, MAP_SHARED, in_fd, 0); + if (p == MAP_FAILED) + return 1; + ssize_t ret; + size_t c= size_t(count); + for (const char *b= static_cast(p);; b+= ret) + { + ret= write(out_fd, b, std::min(c, size_t(INT_MAX >> 20 << 20))); + if (ret < 0) + break; + c-= ret; + if (!c) + { + ret= 0; + break; + } + if (!ret) + { + ret= -1; + break; + } + } + munmap(p, count); + return ret; +} + +static ssize_t pread_write(int in_fd, int out_fd, off_t count) noexcept +{ + constexpr size_t READ_WRITE_SIZE= 65536; + char *b= static_cast(aligned_malloc(READ_WRITE_SIZE, 4096)); + if (!b) + return -1; + ssize_t ret; + for (off_t o= 0;; o+= ret) + { + ret= pread(in_fd, b, ssize_t(std::min(count, off_t{READ_WRITE_SIZE})), o); + if (ret > 0) + ret= write(out_fd, b, ret); + if (ret < 0) + break; + count-= ret; + if (!count) + { + ret= 0; + break; + } + if (!ret) + { + ret= -1; + break; + } + } + aligned_free(b); + return ret; +} +# endif + +using copying_step= ssize_t(int,int,size_t,off_t*); +template +static ssize_t copy(int in_fd, int out_fd, off_t c) noexcept +{ + ssize_t ret; + for (off_t offset{0};;) + { + off_t count= c; + if (count > INT_MAX >> 20 << 20) + count = INT_MAX >> 20 << 20; + ret= step(in_fd, out_fd, size_t(count), &offset); + if (ret < 0) + break; + c-= ret; + if (!c) + return 0; + if (!ret) + return -1; + } + return ret; +} +#endif + +namespace +{ +class InnoDB_backup +{ + /** mutex protecting the queue */ + srw_mutex_impl mutex; + + /** the original state of innodb_log_archive */ + bool was_archived; + + /** collection of files to be copied */ + std::vector queue; + + /** target directory name or handle */ + IF_WIN(const char*,int) target; + + /** the checkpoint from which the backup starts */ + lsn_t checkpoint; + /** end_lsn of the checkpoint at the backup start */ + lsn_t checkpoint_end_lsn; +public: + /** + Start of BACKUP SERVER: collect all files to be backed up + @param thd current session + @param target target directory + @return error code + @retval 0 on success + */ + int init(THD *thd, IF_WIN(const char*,int) target) noexcept + { + mysql_mutex_lock(&LOCK_global_system_variables); + mutex.init(); + mutex.wr_lock(); + was_archived= log_sys.archive; + bool fail{log_sys.set_archive(true, thd)}; + mysql_mutex_unlock(&LOCK_global_system_variables); + + if (!fail) + { + log_sys.latch.wr_lock(); + fail= !log_sys.archive; + checkpoint= log_sys.archived_checkpoint; + checkpoint_end_lsn= log_sys.archived_lsn; + log_sys.latch.wr_unlock(); + } + + if (!fail) + { + this->target= target; + /* Collect all tablespaces that have been created before our + start checkpoint. Newer tablespaces will be recovered by the + innodb_log_archive=ON recovery. + + If a tablespace is deleted before step() is invoked, the file + will not be copied, and a FILE_DELETE record in the log will + ensure correct recovery. + + If a tablespace is renamed between this and end(), the recovery + of a FILE_RENAME record will ensure the correct file name, + no matter which name was used by step(). */ + mysql_mutex_lock(&fil_system.mutex); + for (fil_space_t &space : fil_system.space_list) + if (space.id < SRV_SPACE_ID_UPPER_BOUND && + /* FIXME: how to initialize create_lsn for old files, to + have efficient incremental backup? + fil_node_t::read_page0() cannot assign it from + FIL_PAGE_LSN because that would not reflect the file + creation but for example allocating or freeing a page. + + The easy parts of initializing space->create_lsn are + as follows: + (1) In log_parse_file() when processing FILE_CREATE + (2) In deferred_spaces.create() */ + space.get_create_lsn() < checkpoint) + queue.emplace_back(space.id); + mysql_mutex_unlock(&fil_system.mutex); + } + mutex.wr_unlock(); + return fail; + } + + /** + Process a file that was collected at init(). + This may be invoked from multiple concurrent threads. + @param thd current session + @return number of files remaining, or negative on error + @retval 0 on completion + */ + int step(THD *thd) noexcept + { + uint32_t id= FIL_NULL; + mutex.wr_lock(); + size_t size{queue.size()}; + if (size) + { + size--; + id= queue.back(); + queue.pop_back(); + } + mutex.wr_unlock(); + + if (fil_space_t *space= fil_space_t::get(id)) + { + for (fil_node_t *node= UT_LIST_GET_FIRST(space->chain); node; + node= UT_LIST_GET_NEXT(chain, node)) + if (int res= backup(node)) + { + space->release(); + return res; + } + space->release(); + } + + size= std::min(size_t{std::numeric_limits::max()}, size); + + return int(size); + } + + /** + Finish copying and determine the logical time of the backup snapshot. + @param thd current session + @param abort whether BACKUP SERVER was aborted + @return error code + @retval 0 on success + */ + int end(THD *thd, bool abort) noexcept + { + mutex.wr_lock(); + if (abort) + queue.clear(); + ut_ad(queue.empty()); + mutex.wr_unlock(); + return 0; + } + + /** + After a successful end(), finalize the backup. + @param thd current session + */ + void fini(THD *thd) noexcept + { + mysql_mutex_lock(&LOCK_global_system_variables); + ut_d(mutex.wr_lock()); + ut_ad(queue.empty()); + ut_d(mutex.wr_unlock()); + mutex.destroy(); + if (!was_archived) + log_sys.set_archive(false, thd); + mysql_mutex_unlock(&LOCK_global_system_variables); + } + +private: + /** + Back up a persistent InnoDB data file. + @param node InnoDB data file + */ + int backup(fil_node_t *node) noexcept + { + for (bool tried_mkdir{false};;) + { + /* TODO: copy the file to target safely, even when there may be + concurrent buf_page_t::flush() to this tablespace */ + +#ifdef _WIN32 + std::string path{target}; + path.push_back('/'); + path.append(node->name); + if (!CopyFileExA(node->name, path.c_str(), nullptr, nullptr, nullptr, + COPY_FILE_NO_BUFFERING)) + { + unsigned long err= GetLastError(); + if (err == ERROR_PATH_NOT_FOUND && !tried_mkdir && + node->space->id && !srv_is_undo_tablespace(node->space->id)) + { + tried_mkdir= true; + path= target; + const char *sep= strchr(node->name, '/'); + ut_ad(sep); + sep= strchr(sep + 1, '/'); + ut_ad(sep); + path.append(node->name, size_t(sep - node->name)); + if (CreateDirectory(path.c_str(), + my_dir_security_attributes.lpSecurityDescriptor + ? &my_dir_security_attributes : nullptr) || + (err= GetLastError()) == ERROR_ALREADY_EXISTS) + continue; + } + + my_osmaperr(err); + goto fail; + } + break; +#else + int f; +# ifdef __APPLE__ + if (!fclonefileat(node->handle, target, node->name, 0)) + break; + switch (errno) { + case ENOENT: + goto try_mkdir; + case ENOTSUP: + break; + default: + goto fail; + } +# endif + f= openat(target, node->name, + O_CREAT | O_EXCL | O_TRUNC | O_WRONLY, 0666); + if (f < 0) + { + if (errno == ENOENT) + { +# ifdef __APPLE__ + try_mkdir: +# endif + if (!tried_mkdir && node->space->id && + !srv_is_undo_tablespace(node->space->id)) + { + tried_mkdir= true; + const char *sep= strchr(node->name, '/'); + ut_ad(sep); + sep= strchr(sep + 1, '/'); + ut_ad(sep); + std::string dir{node->name, size_t(sep - node->name)}; + if (!mkdirat(target, dir.c_str(), 0777) || errno == EEXIST) + continue; + } + } + goto fail; + } +# ifdef __APPLE__ + int err= + fcopyfile(node->handle, f, nullptr, COPYFILE_ALL | COPYFILE_CLONE); + if (close(f) || err) + goto fail; +# else + do + { + const off_t size= off_t{node->size} * node->space->physical_size(); +# if defined __linux__ || defined __FreeBSD__ + if (!copy(node->handle, f, size)) + continue; +# ifdef __linux__ + if (errno == EOPNOTSUPP && !copy(node->handle, f, size)) + continue; +# endif +# endif +# ifndef __linux__ // starting with Linux 2.6.33, we can rely on sendfile(2) + ssize_t err= mmap_copy(node->handle, f, size); + if (err == 1) + err= pread_write(node->handle, f, size); + if (!err) + continue; +# endif + std::ignore= close(f); + goto fail; + } + while (false); + + if (close(f)) + goto fail; +# endif + break; +#endif + } + sql_print_information("BACKUP SERVER: copy %s", node->name); + return 0; + fail: + my_error(ER_CANT_CREATE_FILE, MYF(0), node->name, errno); + return -1; + } +}; + +/** The backup context */ +static InnoDB_backup backup; +} + +int innodb_backup_start(THD *thd, IF_WIN(const char*,int) target) noexcept +{ + return backup.init(thd, target); +} + +int innodb_backup_step(THD *thd) noexcept +{ + return backup.step(thd); +} + +int innodb_backup_end(THD *thd, bool abort) noexcept +{ + return backup.end(thd, abort); +} + +void innodb_backup_finalize(THD *thd) noexcept +{ + backup.fini(thd); +} diff --git a/storage/innobase/handler/backup_innodb.h b/storage/innobase/handler/backup_innodb.h new file mode 100644 index 0000000000000..372167ba90439 --- /dev/null +++ b/storage/innobase/handler/backup_innodb.h @@ -0,0 +1,46 @@ +/* Copyright (c) 2026, MariaDB plc + + 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 */ + +/** + Start of BACKUP SERVER: collect all files to be backed up + @param thd current session + @param target target directory + @return error code + @retval 0 on success +*/ +int innodb_backup_start(THD *thd, IF_WIN(const char*,int) target) noexcept; + +/** + Process a file that was collected in backup_start(). + @param thd current session + @return number of files remaining, or negative on error + @retval 0 on completion +*/ +int innodb_backup_step(THD *thd) noexcept; + +/** + Finish copying and determine the logical time of the backup snapshot. + @param thd current session + @param abort whether BACKUP SERVER was aborted + @return error code + @retval 0 on success +*/ +int innodb_backup_end(THD *thd, bool abort) noexcept; + +/** + After a successful backup_end(), finalize the backup. + @param thd current session +*/ +void innodb_backup_finalize(THD *thd) noexcept; diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index 418d8e26c8d63..f71da1e920fd9 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -151,6 +151,7 @@ void close_thread_tables(THD* thd); #include "ha_innodb.h" #include "i_s.h" +#include "backup_innodb.h" #include #include @@ -4182,6 +4183,10 @@ static int innodb_init(void* p) = innodb_prepare_commit_versioned; innobase_hton->update_optimizer_costs= innobase_update_optimizer_costs; + innobase_hton->backup_start = innodb_backup_start; + innobase_hton->backup_step = innodb_backup_step; + innobase_hton->backup_end = innodb_backup_end; + innobase_hton->backup_finalize = innodb_backup_finalize; innobase_hton->binlog_init= innodb_binlog_init; innobase_hton->set_binlog_max_size= ibb_set_max_size; innobase_hton->binlog_write_direct_ordered= @@ -19764,10 +19769,20 @@ static MYSQL_SYSVAR_UINT64_T(log_archive_start, innodb_log_archive_start, "initial value of innodb_lsn_archived; 0=auto-detect", nullptr, nullptr, 0, 0, std::numeric_limits::max(), 0); +static void innodb_log_recovery_start_update(THD *, st_mysql_sys_var*, + void *, const void *save) noexcept +{ + const lsn_t lsn{*static_cast(save)}; + recv_sys.recovery_start= lsn; + if (lsn && log_sys.archive) + log_sys.archived_checkpoint= lsn; +} + static MYSQL_SYSVAR_UINT64_T(log_recovery_start, recv_sys.recovery_start, - PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + PLUGIN_VAR_RQCMDARG, "LSN to start recovery from (0=automatic)", - nullptr, nullptr, 0, 0, std::numeric_limits::max(), 0); + nullptr, innodb_log_recovery_start_update, + 0, 0, std::numeric_limits::max(), 0); static MYSQL_SYSVAR_UINT64_T(log_recovery_target, recv_sys.rpo, PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, diff --git a/storage/innobase/include/log0log.h b/storage/innobase/include/log0log.h index 6469481127dc3..afa027beb5111 100644 --- a/storage/innobase/include/log0log.h +++ b/storage/innobase/include/log0log.h @@ -290,8 +290,9 @@ struct log_t Atomic_relaxed last_checkpoint_lsn; /** The log writer (protected by latch.wr_lock()) */ lsn_t (*writer)() noexcept; - /** end_lsn of the first available checkpoint, or 0; - protected by latch.wr_lock() */ + /** the earliest available checkpoint; protected by latch.wr_lock() */ + lsn_t archived_checkpoint; + /** end_lsn of archived_checkpoint; protected by latch.wr_lock() */ lsn_t archived_lsn; /** Log file */ @@ -401,8 +402,9 @@ struct log_t /** SET GLOBAL innodb_log_archive @param archive the new value of innodb_log_archive - @param thd SQL connection */ - void set_archive(my_bool archive, THD *thd) noexcept; + @param thd SQL connection + @return whether the operation failed */ + bool set_archive(my_bool archive, THD *thd) noexcept; private: /** Replicate a write to the log. diff --git a/storage/innobase/log/log0log.cc b/storage/innobase/log/log0log.cc index 658e969a4d37e..7499f9c1c1a3f 100644 --- a/storage/innobase/log/log0log.cc +++ b/storage/innobase/log/log0log.cc @@ -761,9 +761,11 @@ void log_t::header_rewrite(my_bool archive) noexcept /** SET GLOBAL innodb_log_archive @param archive the new value of innodb_log_archive -@param thd SQL connection */ -void log_t::set_archive(my_bool archive, THD *thd) noexcept +@param thd SQL connection +@return whether the operation failed */ +bool log_t::set_archive(my_bool archive, THD *thd) noexcept { + bool fail= false; thd_wait_begin(thd, THD_WAIT_DISKIO); tpool::tpool_wait_begin(); @@ -775,17 +777,19 @@ void log_t::set_archive(my_bool archive, THD *thd) noexcept my_printf_error(ER_WRONG_USAGE, "SET GLOBAL innodb_log_file_size is in progress", MYF(0)); + fail: + fail= true; break; } if (archive && file_size > ARCHIVE_FILE_SIZE_MAX) { my_printf_error(ER_WRONG_USAGE, "innodb_log_file_size>4G", MYF(0)); - break; + goto fail; } if (archive == this->archive) break; if (thd_kill_level(thd)) - break; + goto fail; lsn_t wait_lsn; @@ -894,7 +898,7 @@ void log_t::set_archive(my_bool archive, THD *thd) noexcept if (!log.is_opened()) { my_error(ER_ERROR_ON_READ, MYF(0), old_name, errno); - break; + goto fail; } } #endif @@ -919,7 +923,7 @@ void log_t::set_archive(my_bool archive, THD *thd) noexcept { my_error(ER_ERROR_ON_RENAME, MYF(0), old_name, new_name, my_errno); first_lsn= old_first_lsn; - break; + goto fail; } if (archive) @@ -937,6 +941,7 @@ void log_t::set_archive(my_bool archive, THD *thd) noexcept IF_WIN(log_resize_release(), latch.wr_unlock()); tpool::tpool_wait_end(); thd_wait_end(thd); + return fail; } /** Start resizing the log and release the exclusive latch. diff --git a/storage/innobase/log/log0recv.cc b/storage/innobase/log/log0recv.cc index 16b710e227855..19c1d79a2e7e9 100644 --- a/storage/innobase/log/log0recv.cc +++ b/storage/innobase/log/log0recv.cc @@ -2076,6 +2076,7 @@ dberr_t recv_sys_t::find_checkpoint() memset_aligned<4096>(const_cast(field_ref_zero), 0, 4096); /* Mark the redo log for upgrading. */ lsn= file_checkpoint= log_sys.last_checkpoint_lsn; + log_sys.archived_checkpoint= lsn; log_sys.set_recovered_lsn(lsn); if (rpo && rpo != lsn) { @@ -2154,12 +2155,14 @@ dberr_t recv_sys_t::find_checkpoint() log_sys.set_recovered_checkpoint(checkpoint_lsn, lsn= end_lsn, field == log_t::CHECKPOINT_1); } - if (!log_sys.last_checkpoint_lsn) + log_sys.archived_checkpoint= log_sys.last_checkpoint_lsn; + if (!log_sys.archived_checkpoint) goto got_no_checkpoint; else if (!log_sys.archived_lsn) log_sys.archived_lsn= lsn; if (recv_sys_invalid_rpo(lsn)) return DB_READ_ONLY; + if (!memcmp(creator, "Backup ", 7) && !recv_sys.rpo && !high_level_read_only) srv_start_after_restore= true; @@ -5691,6 +5694,8 @@ dberr_t recv_sys_t::find_checkpoint_archived(lsn_t first_lsn, bool silent) checkpoint= log_sys.last_checkpoint_lsn; ut_ad(checkpoint); ut_ad(checkpoint < lsn); + if (!log_sys.archived_checkpoint) + log_sys.archived_checkpoint= checkpoint; if (!log_sys.archived_lsn) log_sys.archived_lsn= parse_start; end_lsn= parse_start; @@ -5732,7 +5737,7 @@ dberr_t recv_sys_t::find_checkpoint_archived(lsn_t first_lsn, bool silent) return DB_CORRUPTION; } - if (recovery_start < log_sys.get_first_lsn()); + if (!recovery_start); else if (recovery_start_checkpoint) checkpoint= recovery_start_checkpoint, end_lsn= recovery_start; else From 02a3688fec7e7443c301ea7abde27dcb22c507a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Fri, 27 Mar 2026 15:47:32 +0200 Subject: [PATCH 2/7] Start implementing an API for write/backup page locking --- storage/innobase/buf/buf0flu.cc | 22 ++++++++++++ storage/innobase/handler/backup_innodb.cc | 28 +++++++++++---- storage/innobase/include/fil0fil.h | 44 +++++++++++++++++++++++ 3 files changed, 87 insertions(+), 7 deletions(-) diff --git a/storage/innobase/buf/buf0flu.cc b/storage/innobase/buf/buf0flu.cc index 8adf74d0ee6a8..d0543abf3d14f 100644 --- a/storage/innobase/buf/buf0flu.cc +++ b/storage/innobase/buf/buf0flu.cc @@ -954,6 +954,13 @@ uint32_t fil_space_t::flush_freed(bool writable) noexcept mysql_mutex_assert_not_owner(&buf_pool.flush_list_mutex); mysql_mutex_assert_not_owner(&buf_pool.mutex); + /* Note: There is no need to invoke start_writing() or + stop_writing() here, because we are only overwriting freed (garbage) + pages. If backup reads a torn page, it will also have copied a + corresponding FREE_PAGE record, which would be applied on recovery. + Besides, the freed page should never be reachable from other pages + that are part of the snapshot. */ + const bool punch_hole= chain.start->punch_hole == 1; if (!punch_hole && !srv_immediate_scrub_data_uncompressed) return 0; @@ -1327,6 +1334,9 @@ static void buf_flush_LRU_list_batch(ulint max, flush_counters_t *n, if (space) space->release(); auto p= buf_flush_space(space_id); +#if 0 // TODO + space->start_writing(); +#endif space= p.first; last_space_id= space_id; if (!space) @@ -1345,6 +1355,9 @@ static void buf_flush_LRU_list_batch(ulint max, flush_counters_t *n, } else if (space->is_stopping_writes()) { +#if 0 // TODO + space->stop_writing(); +#endif space->release(); space= nullptr; no_space: @@ -1644,6 +1657,10 @@ bool buf_flush_list_space(fil_space_t *space, ulint *n_flushed) noexcept mysql_mutex_lock(&buf_pool.mutex); if (written) buf_pool.stat.n_pages_written+= written; +#if 0 // TODO: do not call this from multiple threads! + /* TODO: check the page numbers */ + space->start_writing(); +#endif } mysql_mutex_lock(&buf_pool.flush_list_mutex); @@ -1731,7 +1748,12 @@ bool buf_flush_list_space(fil_space_t *space, ulint *n_flushed) noexcept *n_flushed= n_flush; if (acquired) + { +#if 0// TODO + space->stop_writing(); +#endif space->release(); + } if (space->is_being_imported()) os_aio_wait_until_no_pending_writes(true); diff --git a/storage/innobase/handler/backup_innodb.cc b/storage/innobase/handler/backup_innodb.cc index 88e639bf0e26e..4845e36cfb7c5 100644 --- a/storage/innobase/handler/backup_innodb.cc +++ b/storage/innobase/handler/backup_innodb.cc @@ -302,15 +302,16 @@ class InnoDB_backup { for (bool tried_mkdir{false};;) { - /* TODO: copy the file to target safely, even when there may be - concurrent buf_page_t::flush() to this tablespace */ - #ifdef _WIN32 + if (node->space->start_backup(node->space->size)) + os_aio_wait_until_no_pending_writes(false); std::string path{target}; path.push_back('/'); path.append(node->name); - if (!CopyFileExA(node->name, path.c_str(), nullptr, nullptr, nullptr, - COPY_FILE_NO_BUFFERING)) + bool ok= CopyFileExA(node->name, path.c_str(), nullptr, nullptr, nullptr, + COPY_FILE_NO_BUFFERING); + node->space->stop_backup(); + if (!ok) { unsigned long err= GetLastError(); if (err == ERROR_PATH_NOT_FOUND && !tried_mkdir && @@ -337,7 +338,11 @@ class InnoDB_backup #else int f; # ifdef __APPLE__ - if (!fclonefileat(node->handle, target, node->name, 0)) + if (node->space->start_backup(node->space->size)) + os_aio_wait_until_no_pending_writes(false); + f= fclonefileat(node->handle, target, node->name, 0); + node->space->stop_backup(); + if (!f) break; switch (errno) { case ENOENT: @@ -373,13 +378,20 @@ class InnoDB_backup goto fail; } # ifdef __APPLE__ + if (node->space->start_backup(node->space->size)) + os_aio_wait_until_no_pending_writes(false); int err= fcopyfile(node->handle, f, nullptr, COPYFILE_ALL | COPYFILE_CLONE); - if (close(f) || err) + f= close(f) || err; + node->space->stop_backup(); + if (f) goto fail; # else do { + /* FIXME: push down page-granularity locking */ + if (node->space->start_backup(node->space->size)) + os_aio_wait_until_no_pending_writes(false); const off_t size= off_t{node->size} * node->space->physical_size(); # if defined __linux__ || defined __FreeBSD__ if (!copy(node->handle, f, size)) @@ -396,11 +408,13 @@ class InnoDB_backup if (!err) continue; # endif + node->space->stop_backup(); std::ignore= close(f); goto fail; } while (false); + node->space->stop_backup(); if (close(f)) goto fail; # endif diff --git a/storage/innobase/include/fil0fil.h b/storage/innobase/include/fil0fil.h index 48697750c7dea..8e1b118b96bf4 100644 --- a/storage/innobase/include/fil0fil.h +++ b/storage/innobase/include/fil0fil.h @@ -408,6 +408,13 @@ struct fil_space_t final /** Whether any corruption of this tablespace has been reported */ mutable std::atomic_flag is_corrupted= ATOMIC_FLAG_INIT; + /** BACKUP SERVER flag in write_or_backup */ + static constexpr uint8_t BACKUP{128}; + /** whether there is a pending write or backup */ + std::atomic write_or_backup{0}; + /** last page number being backed up, or 0 if none */ + std::atomic backup_last_page{0}; + public: /** mutex to protect freed_ranges and last_freed_lsn */ std::mutex freed_range_mutex; @@ -1044,6 +1051,43 @@ struct fil_space_t final VALIDATE_IMPORT }; + /** Note that writes are being submitted to the tablespace. + @return whether a backup is pending */ + bool start_writing() noexcept + { + uint8_t wb{write_or_backup.fetch_add(1, std::memory_order_acq_rel)}; + ut_ad(!(wb & ~BACKUP)); + return wb & BACKUP; + } + /** Note that we there are no more pending writes to the tablespace. */ + void stop_writing() noexcept + { + ut_d(uint8_t wb=) write_or_backup.fetch_sub(1, std::memory_order_release); + ut_ad(wb & ~BACKUP); + } + + /** Note that we backing up some pages of the underlying files. + @param last_page the last page that is being backed up */ + bool start_backup(uint32_t last_page) noexcept + { + backup_last_page.store(last_page, std::memory_order_relaxed); + uint8_t wb{write_or_backup.fetch_add(BACKUP, std::memory_order_acq_rel)}; + ut_ad(!(wb & BACKUP)); + return wb & ~BACKUP; + } + /** Note that we are not currently backing up the underlying files. */ + void stop_backup() noexcept + { + backup_last_page.store(0, std::memory_order_relaxed); + ut_d(uint8_t wb=) + write_or_backup.fetch_sub(BACKUP, std::memory_order_release); + ut_ad(wb & BACKUP); + } + /** @return the last page that is being backed up + @retval 0 if no backup is in progress */ + uint32_t last_page_in_backup() const noexcept + { return backup_last_page.load(std::memory_order_relaxed); } + /** Update the data structures on write completion */ void complete_write() noexcept; From 70bc8e7a8c44639d37aa1a6ea1ffb1b395c582e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Mon, 30 Mar 2026 08:57:43 +0300 Subject: [PATCH 3/7] Do not hog LOCK_global_system_variables --- storage/innobase/handler/backup_innodb.cc | 4 ---- storage/innobase/handler/ha_innodb.cc | 2 ++ 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/storage/innobase/handler/backup_innodb.cc b/storage/innobase/handler/backup_innodb.cc index 4845e36cfb7c5..153a864f6c571 100644 --- a/storage/innobase/handler/backup_innodb.cc +++ b/storage/innobase/handler/backup_innodb.cc @@ -172,12 +172,10 @@ class InnoDB_backup */ int init(THD *thd, IF_WIN(const char*,int) target) noexcept { - mysql_mutex_lock(&LOCK_global_system_variables); mutex.init(); mutex.wr_lock(); was_archived= log_sys.archive; bool fail{log_sys.set_archive(true, thd)}; - mysql_mutex_unlock(&LOCK_global_system_variables); if (!fail) { @@ -283,14 +281,12 @@ class InnoDB_backup */ void fini(THD *thd) noexcept { - mysql_mutex_lock(&LOCK_global_system_variables); ut_d(mutex.wr_lock()); ut_ad(queue.empty()); ut_d(mutex.wr_unlock()); mutex.destroy(); if (!was_archived) log_sys.set_archive(false, thd); - mysql_mutex_unlock(&LOCK_global_system_variables); } private: diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index f71da1e920fd9..376893c074d01 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -19756,7 +19756,9 @@ static MYSQL_SYSVAR_BOOL(data_file_write_through, fil_system.write_through, static void innodb_log_archive_update(THD *thd, st_mysql_sys_var*, void *, const void *save) noexcept { + mysql_mutex_unlock(&LOCK_global_system_variables); log_sys.set_archive(*static_cast(save), thd); + mysql_mutex_lock(&LOCK_global_system_variables); } static MYSQL_SYSVAR_BOOL(log_archive, log_sys.archive, From 1b853724e7424cd6c5fc8a59076f2afafff605f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Mon, 30 Mar 2026 13:49:28 +0300 Subject: [PATCH 4/7] squash! 02a3688fec7e7443c301ea7abde27dcb22c507a1 Implement file-level InnoDB page locking for backup. buf_flush_list_space(): Check for concurrent backup before writing each page. This is inefficient, but this function may be invoked from multiple threads concurrently, and it cannot be changed easily, especially for fil_crypt_thread(). TODO: Implement finer-grained locking around copying page ranges. --- storage/innobase/buf/buf0flu.cc | 90 +++++++++++++---------- storage/innobase/handler/backup_innodb.cc | 33 +++++---- storage/innobase/include/fil0fil.h | 26 +++---- 3 files changed, 85 insertions(+), 64 deletions(-) diff --git a/storage/innobase/buf/buf0flu.cc b/storage/innobase/buf/buf0flu.cc index d0543abf3d14f..b272f9d766add 100644 --- a/storage/innobase/buf/buf0flu.cc +++ b/storage/innobase/buf/buf0flu.cc @@ -954,8 +954,8 @@ uint32_t fil_space_t::flush_freed(bool writable) noexcept mysql_mutex_assert_not_owner(&buf_pool.flush_list_mutex); mysql_mutex_assert_not_owner(&buf_pool.mutex); - /* Note: There is no need to invoke start_writing() or - stop_writing() here, because we are only overwriting freed (garbage) + /* Note: There is no need to invoke writing_start() or + writing_stop() here, because we are only overwriting freed (garbage) pages. If backup reads a torn page, it will also have copied a corresponding FREE_PAGE record, which would be applied on recovery. Besides, the freed page should never be reachable from other pages @@ -1236,6 +1236,16 @@ ATTRIBUTE_COLD static size_t buf_flush_LRU_to_withdraw(size_t to_withdraw, return to_withdraw; } +/** Stop writing to a tablespace. +@param space tablespace +@return nullptr */ +static fil_space_t *writing_stop(fil_space_t *space) noexcept +{ + space->writing_stop(); + space->release(); + return nullptr; +} + /** Flush dirty blocks from the end buf_pool.LRU, and move clean blocks to buf_pool.free. @param max maximum number of blocks to flush @@ -1253,6 +1263,7 @@ static void buf_flush_LRU_list_batch(ulint max, flush_counters_t *n, ? 0 : buf_pool.flush_neighbors; fil_space_t *space= nullptr; uint32_t last_space_id= FIL_NULL; + uint32_t backup_page_end= 0; static_assert(FIL_NULL > SRV_TMP_SPACE_ID, "consistency"); static_assert(FIL_NULL > SRV_SPACE_ID_UPPER_BOUND, "consistency"); @@ -1332,11 +1343,8 @@ static void buf_flush_LRU_list_batch(ulint max, flush_counters_t *n, buf_pool.lru_hp.set(bpage); mysql_mutex_unlock(&buf_pool.mutex); if (space) - space->release(); + writing_stop(space); auto p= buf_flush_space(space_id); -#if 0 // TODO - space->start_writing(); -#endif space= p.first; last_space_id= space_id; if (!space) @@ -1344,6 +1352,10 @@ static void buf_flush_LRU_list_batch(ulint max, flush_counters_t *n, mysql_mutex_lock(&buf_pool.mutex); goto no_space; } + + backup_page_end= space->writing_start() + ? space->backup_page_end() : 0; + mysql_mutex_lock(&buf_pool.mutex); buf_pool.stat.n_pages_written+= p.second; } @@ -1355,11 +1367,7 @@ static void buf_flush_LRU_list_batch(ulint max, flush_counters_t *n, } else if (space->is_stopping_writes()) { -#if 0 // TODO - space->stop_writing(); -#endif - space->release(); - space= nullptr; + space= writing_stop(space); no_space: mysql_mutex_lock(&buf_pool.flush_list_mutex); buf_flush_discard_page(bpage); @@ -1376,7 +1384,8 @@ static void buf_flush_LRU_list_batch(ulint max, flush_counters_t *n, break; } - if (neighbors && space->is_rotational() && UNIV_LIKELY(!to_withdraw) && + if (neighbors && UNIV_LIKELY(!to_withdraw) && + UNIV_LIKELY(!backup_page_end) && space->is_rotational() && /* Skip neighbourhood flush from LRU list if we haven't yet reached half of the free page target. */ UT_LIST_GET_LEN(buf_pool.free) * 2 >= free_limit) @@ -1388,7 +1397,7 @@ static void buf_flush_LRU_list_batch(ulint max, flush_counters_t *n, flush: if (UNIV_UNLIKELY(to_withdraw != 0)) to_withdraw= buf_flush_LRU_to_withdraw(to_withdraw, *bpage); - if (bpage->flush(space)) + if (bpage->id().page_no() >= backup_page_end && bpage->flush(space)) ++n->flushed; else continue; @@ -1404,7 +1413,7 @@ static void buf_flush_LRU_list_batch(ulint max, flush_counters_t *n, buf_pool.lru_hp.set(nullptr); if (space) - space->release(); + writing_stop(space); if (scanned) { @@ -1451,6 +1460,7 @@ static ulint buf_do_flush_list_batch(ulint max_n, lsn_t lsn) noexcept ? 0 : buf_pool.flush_neighbors; fil_space_t *space= nullptr; uint32_t last_space_id= FIL_NULL; + uint32_t backup_page_end= 0; static_assert(FIL_NULL > SRV_TMP_SPACE_ID, "consistency"); static_assert(FIL_NULL > SRV_SPACE_ID_UPPER_BOUND, "consistency"); @@ -1522,10 +1532,12 @@ static ulint buf_do_flush_list_batch(ulint max_n, lsn_t lsn) noexcept mysql_mutex_unlock(&buf_pool.flush_list_mutex); mysql_mutex_unlock(&buf_pool.mutex); if (space) - space->release(); + writing_stop(space); auto p= buf_flush_space(space_id); space= p.first; last_space_id= space_id; + backup_page_end= space && space->writing_start() + ? space->backup_page_end() : 0; mysql_mutex_lock(&buf_pool.mutex); buf_pool.stat.n_pages_written+= p.second; mysql_mutex_lock(&buf_pool.flush_list_mutex); @@ -1534,10 +1546,7 @@ static ulint buf_do_flush_list_batch(ulint max_n, lsn_t lsn) noexcept ut_ad(!space); } else if (space->is_stopping_writes()) - { - space->release(); - space= nullptr; - } + space= writing_stop(space); if (!space) buf_flush_discard_page(bpage); @@ -1546,10 +1555,12 @@ static ulint buf_do_flush_list_batch(ulint max_n, lsn_t lsn) noexcept mysql_mutex_unlock(&buf_pool.flush_list_mutex); do { - if (neighbors && space->is_rotational()) + if (neighbors && UNIV_LIKELY(!backup_page_end) && + space->is_rotational()) count+= buf_flush_try_neighbors(space, page_id, bpage, neighbors == 1, count, max_n); - else if (bpage->flush(space)) + else if (bpage->id().page_no() >= backup_page_end && + bpage->flush(space)) ++count; else continue; @@ -1567,7 +1578,7 @@ static ulint buf_do_flush_list_batch(ulint max_n, lsn_t lsn) noexcept buf_pool.flush_hp.set(nullptr); if (space) - space->release(); + writing_stop(space); if (scanned) { @@ -1657,11 +1668,8 @@ bool buf_flush_list_space(fil_space_t *space, ulint *n_flushed) noexcept mysql_mutex_lock(&buf_pool.mutex); if (written) buf_pool.stat.n_pages_written+= written; -#if 0 // TODO: do not call this from multiple threads! - /* TODO: check the page numbers */ - space->start_writing(); -#endif } + mysql_mutex_lock(&buf_pool.flush_list_mutex); for (buf_page_t *bpage= UT_LIST_GET_LAST(buf_pool.flush_list); bpage; ) @@ -1704,17 +1712,28 @@ bool buf_flush_list_space(fil_space_t *space, ulint *n_flushed) noexcept acquired= false; goto was_freed; } + mysql_mutex_unlock(&buf_pool.flush_list_mutex); - if (bpage->flush(space)) + + if (UNIV_UNLIKELY(space->writing_start()) && + space->backup_page_end() < bpage->id().page_no()) + { + space->writing_stop(); + skip: + mysql_mutex_lock(&buf_pool.mutex); + mysql_mutex_lock(&buf_pool.flush_list_mutex); + may_have_skipped= true; + goto done; + } + + const bool written{bpage->flush(space)}; + space->writing_stop(); + + if (written) { ++n_flush; if (!--max_n_flush) - { - mysql_mutex_lock(&buf_pool.mutex); - mysql_mutex_lock(&buf_pool.flush_list_mutex); - may_have_skipped= true; - goto done; - } + goto skip; mysql_mutex_lock(&buf_pool.mutex); } } @@ -1748,12 +1767,7 @@ bool buf_flush_list_space(fil_space_t *space, ulint *n_flushed) noexcept *n_flushed= n_flush; if (acquired) - { -#if 0// TODO - space->stop_writing(); -#endif space->release(); - } if (space->is_being_imported()) os_aio_wait_until_no_pending_writes(true); diff --git a/storage/innobase/handler/backup_innodb.cc b/storage/innobase/handler/backup_innodb.cc index 153a864f6c571..410aa6ebe861e 100644 --- a/storage/innobase/handler/backup_innodb.cc +++ b/storage/innobase/handler/backup_innodb.cc @@ -203,6 +203,7 @@ class InnoDB_backup mysql_mutex_lock(&fil_system.mutex); for (fil_space_t &space : fil_system.space_list) if (space.id < SRV_SPACE_ID_UPPER_BOUND && + !space.is_being_imported() && /* FIXME: how to initialize create_lsn for old files, to have efficient incremental backup? fil_node_t::read_page0() cannot assign it from @@ -290,6 +291,16 @@ class InnoDB_backup } private: + /** Safely start backing up a tablespace file */ + static void backup_start(fil_space_t *space) noexcept + { + if (space->backup_start(space->size)) + os_aio_wait_until_no_pending_writes(false); + } + /* Stop backing up a tablespace */ + static void backup_stop(fil_space_t *space) noexcept + { space->backup_stop(); } + /** Back up a persistent InnoDB data file. @param node InnoDB data file @@ -299,14 +310,13 @@ class InnoDB_backup for (bool tried_mkdir{false};;) { #ifdef _WIN32 - if (node->space->start_backup(node->space->size)) - os_aio_wait_until_no_pending_writes(false); + backup_start(node->space); std::string path{target}; path.push_back('/'); path.append(node->name); bool ok= CopyFileExA(node->name, path.c_str(), nullptr, nullptr, nullptr, COPY_FILE_NO_BUFFERING); - node->space->stop_backup(); + backup_end(node->space); if (!ok) { unsigned long err= GetLastError(); @@ -334,10 +344,9 @@ class InnoDB_backup #else int f; # ifdef __APPLE__ - if (node->space->start_backup(node->space->size)) - os_aio_wait_until_no_pending_writes(false); + backup_start(node->space); f= fclonefileat(node->handle, target, node->name, 0); - node->space->stop_backup(); + backup_stop(node->space); if (!f) break; switch (errno) { @@ -374,20 +383,18 @@ class InnoDB_backup goto fail; } # ifdef __APPLE__ - if (node->space->start_backup(node->space->size)) - os_aio_wait_until_no_pending_writes(false); + backup_start(node->space); int err= fcopyfile(node->handle, f, nullptr, COPYFILE_ALL | COPYFILE_CLONE); f= close(f) || err; - node->space->stop_backup(); + backup_stop(node->space); if (f) goto fail; # else do { /* FIXME: push down page-granularity locking */ - if (node->space->start_backup(node->space->size)) - os_aio_wait_until_no_pending_writes(false); + backup_start(node->space); const off_t size= off_t{node->size} * node->space->physical_size(); # if defined __linux__ || defined __FreeBSD__ if (!copy(node->handle, f, size)) @@ -404,13 +411,13 @@ class InnoDB_backup if (!err) continue; # endif - node->space->stop_backup(); + backup_stop(node->space); std::ignore= close(f); goto fail; } while (false); - node->space->stop_backup(); + backup_stop(node->space); if (close(f)) goto fail; # endif diff --git a/storage/innobase/include/fil0fil.h b/storage/innobase/include/fil0fil.h index 8e1b118b96bf4..54f1a2bb2513c 100644 --- a/storage/innobase/include/fil0fil.h +++ b/storage/innobase/include/fil0fil.h @@ -412,8 +412,8 @@ struct fil_space_t final static constexpr uint8_t BACKUP{128}; /** whether there is a pending write or backup */ std::atomic write_or_backup{0}; - /** last page number being backed up, or 0 if none */ - std::atomic backup_last_page{0}; + /** first page number that is not being backed up */ + std::atomic backup_end{0}; public: /** mutex to protect freed_ranges and last_freed_lsn */ @@ -1053,14 +1053,15 @@ struct fil_space_t final /** Note that writes are being submitted to the tablespace. @return whether a backup is pending */ - bool start_writing() noexcept + bool writing_start() noexcept { uint8_t wb{write_or_backup.fetch_add(1, std::memory_order_acq_rel)}; - ut_ad(!(wb & ~BACKUP)); + ut_ad(~wb & (BACKUP - 1)); return wb & BACKUP; } + /** Note that we there are no more pending writes to the tablespace. */ - void stop_writing() noexcept + void writing_stop() noexcept { ut_d(uint8_t wb=) write_or_backup.fetch_sub(1, std::memory_order_release); ut_ad(wb & ~BACKUP); @@ -1068,25 +1069,24 @@ struct fil_space_t final /** Note that we backing up some pages of the underlying files. @param last_page the last page that is being backed up */ - bool start_backup(uint32_t last_page) noexcept + bool backup_start(uint32_t last_page) noexcept { - backup_last_page.store(last_page, std::memory_order_relaxed); + backup_end.store(last_page, std::memory_order_relaxed); uint8_t wb{write_or_backup.fetch_add(BACKUP, std::memory_order_acq_rel)}; ut_ad(!(wb & BACKUP)); return wb & ~BACKUP; } /** Note that we are not currently backing up the underlying files. */ - void stop_backup() noexcept + void backup_stop() noexcept { - backup_last_page.store(0, std::memory_order_relaxed); + backup_end.store(0, std::memory_order_relaxed); ut_d(uint8_t wb=) write_or_backup.fetch_sub(BACKUP, std::memory_order_release); ut_ad(wb & BACKUP); } - /** @return the last page that is being backed up - @retval 0 if no backup is in progress */ - uint32_t last_page_in_backup() const noexcept - { return backup_last_page.load(std::memory_order_relaxed); } + /** @return the first page number that is not being backed up */ + uint32_t backup_page_end() const noexcept + { return backup_end.load(std::memory_order_relaxed); } /** Update the data structures on write completion */ void complete_write() noexcept; From 9871f9ca9aa06909739418489cc513c8abfa09b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Mon, 30 Mar 2026 14:04:06 +0300 Subject: [PATCH 5/7] fixup! 1b853724e7424cd6c5fc8a59076f2afafff605f5 --- storage/innobase/handler/backup_innodb.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/storage/innobase/handler/backup_innodb.cc b/storage/innobase/handler/backup_innodb.cc index 410aa6ebe861e..281656d075775 100644 --- a/storage/innobase/handler/backup_innodb.cc +++ b/storage/innobase/handler/backup_innodb.cc @@ -316,7 +316,7 @@ class InnoDB_backup path.append(node->name); bool ok= CopyFileExA(node->name, path.c_str(), nullptr, nullptr, nullptr, COPY_FILE_NO_BUFFERING); - backup_end(node->space); + backup_stop(node->space); if (!ok) { unsigned long err= GetLastError(); From 9ba0ea7cee9cc2c607f5ffc97afebd1003cc3af8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Tue, 31 Mar 2026 15:19:35 +0300 Subject: [PATCH 6/7] Fix conflicts between backup and innodb_log_file_size, innodb_log_archive --- .../suite/backup/backup_innodb.combinations | 4 + storage/innobase/handler/backup_innodb.cc | 14 +- storage/innobase/handler/ha_innodb.cc | 34 +---- storage/innobase/include/log0log.h | 39 +++++- storage/innobase/log/log0log.cc | 127 +++++++++++++++++- 5 files changed, 166 insertions(+), 52 deletions(-) create mode 100644 mysql-test/suite/backup/backup_innodb.combinations diff --git a/mysql-test/suite/backup/backup_innodb.combinations b/mysql-test/suite/backup/backup_innodb.combinations new file mode 100644 index 0000000000000..7fd419bba7692 --- /dev/null +++ b/mysql-test/suite/backup/backup_innodb.combinations @@ -0,0 +1,4 @@ +[archived] +innodb_log_archive=ON +[circular] +innodb_log_archive=OFF diff --git a/storage/innobase/handler/backup_innodb.cc b/storage/innobase/handler/backup_innodb.cc index 281656d075775..c5d46ae4861ed 100644 --- a/storage/innobase/handler/backup_innodb.cc +++ b/storage/innobase/handler/backup_innodb.cc @@ -149,8 +149,8 @@ class InnoDB_backup /** mutex protecting the queue */ srw_mutex_impl mutex; - /** the original state of innodb_log_archive */ - bool was_archived; + /** the original innodb_log_file_size, or 0 if innodb_log_archive=ON */ + uint64_t old_size; /** collection of files to be copied */ std::vector queue; @@ -174,20 +174,15 @@ class InnoDB_backup { mutex.init(); mutex.wr_lock(); - was_archived= log_sys.archive; - bool fail{log_sys.set_archive(true, thd)}; + const bool fail{log_sys.backup_start(&old_size, thd)}; if (!fail) { log_sys.latch.wr_lock(); - fail= !log_sys.archive; checkpoint= log_sys.archived_checkpoint; checkpoint_end_lsn= log_sys.archived_lsn; log_sys.latch.wr_unlock(); - } - if (!fail) - { this->target= target; /* Collect all tablespaces that have been created before our start checkpoint. Newer tablespaces will be recovered by the @@ -286,8 +281,7 @@ class InnoDB_backup ut_ad(queue.empty()); ut_d(mutex.wr_unlock()); mutex.destroy(); - if (!was_archived) - log_sys.set_archive(false, thd); + log_sys.backup_stop(old_size, thd); } private: diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index 376893c074d01..7f2d808797dac 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -18886,39 +18886,7 @@ static void innodb_log_file_size_update(THD *thd, st_mysql_sys_var*, ib_senderrf(thd, IB_LOG_LEVEL_ERROR, ER_CANT_CREATE_HANDLER_FILE); break; case log_t::RESIZE_STARTED: - for (timespec abstime;;) - { - if (thd_kill_level(thd)) - { - log_sys.resize_abort(thd); - break; - } - - set_timespec(abstime, 5); - mysql_mutex_lock(&buf_pool.flush_list_mutex); - lsn_t resizing= log_sys.resize_in_progress(); - if (resizing > buf_pool.get_oldest_modification(0)) - { - buf_pool.page_cleaner_wakeup(true); - my_cond_timedwait(&buf_pool.done_flush_list, - &buf_pool.flush_list_mutex.m_mutex, &abstime); - resizing= log_sys.resize_in_progress(); - } - mysql_mutex_unlock(&buf_pool.flush_list_mutex); - if (!resizing || !log_sys.resize_running(thd)) - break; - log_sys.latch.wr_lock(); - while (resizing > log_sys.get_lsn()) - { - ut_ad(!log_sys.is_mmap()); - /* The server is almost idle. Write dummy FILE_CHECKPOINT records - to ensure that the log resizing will complete. */ - mtr_t mtr{nullptr}; - mtr.start(); - mtr.commit_files(log_sys.last_checkpoint_lsn); - } - log_sys.latch.wr_unlock(); - } + log_sys.resize_finish(thd); } } mysql_mutex_lock(&LOCK_global_system_variables); diff --git a/storage/innobase/include/log0log.h b/storage/innobase/include/log0log.h index afa027beb5111..fd32b4f07abfb 100644 --- a/storage/innobase/include/log0log.h +++ b/storage/innobase/include/log0log.h @@ -223,6 +223,8 @@ struct log_t /** whether !archive log records may have been written with get_sequence_bit()==0 */ bool circular_recovery_from_sequence_bit_0:1; + /** whether we are between backup_start() and backup_stop() */ + bool backup:1; public: /** the default value of log_mmap */ static constexpr bool log_mmap_default= @@ -372,11 +374,24 @@ struct log_t RESIZE_NO_CHANGE, RESIZE_IN_PROGRESS, RESIZE_STARTED, RESIZE_FAILED }; +private: /** Start resizing the log and release the exclusive latch. + @param size requested new file_size + @param thd the current thread identifier + @param backup whether the caller is backup_start() or backup_stop() + @return whether the resizing was started successfully */ + resize_start_status resize_start(uint64_t size, void *thd, bool backup) + noexcept; +public: + /** Start resizing the log. @param size requested new file_size @param thd the current thread identifier @return whether the resizing was started successfully */ - resize_start_status resize_start(os_offset_t size, void *thd) noexcept; + resize_start_status resize_start(uint64_t size, void *thd) noexcept + { return resize_start(size, thd, false); } + + /** Wait for the completion of resize_start() == RESIZE_STARTED */ + void resize_finish(THD *thd) noexcept; /** Abort a resize_start() that we started. @param thd thread identifier that had been passed to resize_start() */ @@ -400,11 +415,31 @@ struct log_t resize_write_low(lsn, end, len, seq); } +private: + /** SET GLOBAL innodb_log_archive, or start/stop BACKUP SERVER + @param archive the new value of innodb_log_archive + @param thd SQL connection + @param backup whether the caller is backup_start() or backup_stop() + @return whether the operation failed */ + bool set_archive(my_bool archive, THD *thd, bool backup) noexcept; +public: /** SET GLOBAL innodb_log_archive @param archive the new value of innodb_log_archive @param thd SQL connection @return whether the operation failed */ - bool set_archive(my_bool archive, THD *thd) noexcept; + bool set_archive(my_bool archive, THD *thd) noexcept + { return set_archive(archive, thd, false); } + + /** Start BACKUP SERVER. + @param old_size the old file_size, or 0 on failure or when + already running innodb_log_archive=ON + @param thd SQL connection + @return whether the operation failed */ + bool backup_start(uint64_t *old_size, THD *thd) noexcept; + /** Stop BACKUP SERVER. + @param old_size the value returned by backup_start() + @param thd SQL connection */ + void backup_stop(uint64_t old_size, THD *thd) noexcept; private: /** Replicate a write to the log. diff --git a/storage/innobase/log/log0log.cc b/storage/innobase/log/log0log.cc index 065f84b8bfbc2..1f2e4cd2364f0 100644 --- a/storage/innobase/log/log0log.cc +++ b/storage/innobase/log/log0log.cc @@ -761,8 +761,9 @@ void log_t::header_rewrite(my_bool archive) noexcept /** SET GLOBAL innodb_log_archive @param archive the new value of innodb_log_archive @param thd SQL connection +@param backup whether the caller is backup_start() or backup_stop() @return whether the operation failed */ -bool log_t::set_archive(my_bool archive, THD *thd) noexcept +bool log_t::set_archive(my_bool archive, THD *thd, bool backup) noexcept { bool fail= false; thd_wait_begin(thd, THD_WAIT_DISKIO); @@ -787,8 +788,13 @@ bool log_t::set_archive(my_bool archive, THD *thd) noexcept } if (archive == this->archive) break; - if (thd_kill_level(thd)) + if ((!backup || archive) && thd_kill_level(thd)) + goto fail; + if (!backup && this->backup) + { + my_printf_error(ER_WRONG_USAGE, "BACKUP SERVER is in progress", MYF(0)); goto fail; + } lsn_t wait_lsn; @@ -943,12 +949,78 @@ bool log_t::set_archive(my_bool archive, THD *thd) noexcept return fail; } -/** Start resizing the log and release the exclusive latch. -@param size requested new file_size -@param thd the current thread identifier +bool log_t::backup_start(uint64_t *old_size, THD *thd) noexcept +{ + latch.wr_lock(); + ut_ad(!backup); + backup= true; + const bool was_archived= bool(archive); + const uint64_t old_file_size{file_size}; + latch.wr_unlock(); + if (was_archived) + { + *old_size= 0; + return false; + } + if (old_file_size > ARCHIVE_FILE_SIZE_MAX) + { + if (resize_start(ARCHIVE_FILE_SIZE_MAX, thd, true) == RESIZE_STARTED) + resize_finish(thd); + latch.wr_lock(); + if (file_size > ARCHIVE_FILE_SIZE_MAX) + goto too_big; + latch.wr_unlock(); + } + if (!set_archive(true, thd, true)) + { + *old_size= old_file_size; + return false; + } + latch.wr_lock(); + too_big: + ut_ad(backup); + backup= false; + const uint64_t new_file_size{file_size}; + latch.wr_unlock(); + if (old_file_size != new_file_size && old_file_size && + resize_start(old_file_size, thd) == RESIZE_STARTED) + resize_finish(thd); + *old_size= 0; + return true; +} + +void log_t::backup_stop(uint64_t old_size, THD *thd) noexcept +{ + /* We will be invoked with old_size=0 after a failed backup_start(), + or if innodb_log_archive=ON held during a successful backup_start(). */ + if (UNIV_LIKELY(old_size != 0)) + { + ut_d(latch.wr_lock()); + ut_ad(backup); + ut_ad(!resize_in_progress()); + ut_ad(archive); + ut_d(latch.wr_unlock()); + ut_d(bool fail=) set_archive(false, thd, true); + ut_ad(!fail); + } + latch.wr_lock(); + ut_ad(!old_size || !resize_in_progress()); + ut_ad(!old_size || backup); + backup= false; + const uint64_t new_size{file_size}; + latch.wr_unlock(); + if (old_size && old_size != new_size && + resize_start(old_size, thd) == RESIZE_STARTED) + resize_finish(thd); +} + +/** Start resizing the log. +@param size requested new file_size +@param thd the current thread identifier +@param backup whether the caller is backup_start() or backup_stop() @return whether the resizing was started successfully */ -log_t::resize_start_status log_t::resize_start(os_offset_t size, void *thd) - noexcept +log_t::resize_start_status log_t::resize_start(uint64_t size, void *thd, + bool backup) noexcept { ut_ad(size >= 4U << 20); ut_ad(!(size & 4095)); @@ -981,6 +1053,9 @@ log_t::resize_start_status log_t::resize_start(os_offset_t size, void *thd) resize_target= size; } } + else if (!backup && this->backup) + /* backup_start() or backup_stop() is running */ + status= RESIZE_FAILED; else { lsn_t start_lsn; @@ -1082,6 +1157,44 @@ log_t::resize_start_status log_t::resize_start(os_offset_t size, void *thd) return status; } +/** Wait for the completion of resize_start() == RESIZE_STARTED */ +void log_t::resize_finish(THD *thd) noexcept +{ + for (timespec abstime;;) + { + if (thd_kill_level(thd)) + { + resize_abort(thd); + break; + } + + set_timespec(abstime, 5); + mysql_mutex_lock(&buf_pool.flush_list_mutex); + lsn_t resizing= resize_in_progress(); + if (resizing > buf_pool.get_oldest_modification(0)) + { + buf_pool.page_cleaner_wakeup(true); + my_cond_timedwait(&buf_pool.done_flush_list, + &buf_pool.flush_list_mutex.m_mutex, &abstime); + resizing= resize_in_progress(); + } + mysql_mutex_unlock(&buf_pool.flush_list_mutex); + if (!resizing || !resize_running(thd)) + break; + latch.wr_lock(); + while (resizing > get_lsn()) + { + ut_ad(!is_mmap()); + /* The server is almost idle. Write dummy FILE_CHECKPOINT records + to ensure that the log resizing will complete. */ + mtr_t mtr{nullptr}; + mtr.start(); + mtr.commit_files(last_checkpoint_lsn); + } + latch.wr_unlock(); + } +} + /** Abort a resize_start() that we started. */ void log_t::resize_abort(void *thd) noexcept { From 531a30e8075816518e2638dd513e798d121977ca Mon Sep 17 00:00:00 2001 From: Andrzej Jarzabek Date: Wed, 1 Apr 2026 18:37:18 +0200 Subject: [PATCH 7/7] MDEV-39101 Make BACKUP SERVER mutually exclusive with itself and BACKUP STAGE BACKUP SERVER blocks when another BACKUp SERVER is in progress or a BACKUP STAGE has been started. Takes advantage of existing mechanism using MDL which BACKUP STAGE uses to block concurrent backups. --- mysql-test/main/backup_server_block.result | 32 ++++++++ mysql-test/main/backup_server_block.test | 86 ++++++++++++++++++++++ sql/debug_sync.h | 2 + sql/mdl.h | 14 ++-- sql/sql_backup.cc | 60 ++++++++++----- sql/sql_plist.h | 10 ++- 6 files changed, 174 insertions(+), 30 deletions(-) create mode 100644 mysql-test/main/backup_server_block.result create mode 100644 mysql-test/main/backup_server_block.test diff --git a/mysql-test/main/backup_server_block.result b/mysql-test/main/backup_server_block.result new file mode 100644 index 0000000000000..ce9b5582a68dd --- /dev/null +++ b/mysql-test/main/backup_server_block.result @@ -0,0 +1,32 @@ +connect con1,localhost,root,,test; +connect con2,localhost,root,,test; +connection con1; +SET DEBUG_SYNC='after_backup_server_lock_acquired SIGNAL in_progress WAIT_FOR finish'; +BACKUP SERVER TO '/some_directory'; +connection default; +SET DEBUG_SYNC='now WAIT_FOR in_progress'; +connection con2; +BACKUP SERVER TO '/other_directory'; +connection default; +SET @con2_id = ; +SELECT COMMAND, STATE, INFO FROM INFORMATION_SCHEMA.PROCESSLIST WHERE ID = @con2_id; +COMMAND STATE INFO +Query Waiting for backup lock BACKUP SERVER TO '/other_directory' +SET DEBUG_SYNC='now SIGNAL finish'; +connection con1; +connection default; +connection con2; +connection default; +SET DEBUG_SYNC='RESET'; +BACKUP STAGE START; +connection con2; +BACKUP SERVER TO '/another_directory'; +connection default; +SELECT COMMAND, STATE, INFO FROM INFORMATION_SCHEMA.PROCESSLIST WHERE ID = @con2_id; +COMMAND STATE INFO +Query Waiting for backup lock BACKUP SERVER TO '/another_directory' +BACKUP STAGE END; +connection con2; +connection default; +disconnect con1; +disconnect con2; diff --git a/mysql-test/main/backup_server_block.test b/mysql-test/main/backup_server_block.test new file mode 100644 index 0000000000000..8214367e0ea9e --- /dev/null +++ b/mysql-test/main/backup_server_block.test @@ -0,0 +1,86 @@ +# Check that BACKUP SERVER blocks when another BACKUP SERVER is in progress + +--source include/have_debug_sync.inc + +--connect (con1,localhost,root,,test) +--connect (con2,localhost,root,,test) + +--connection con1 +# Simulate long-running backup by blocking it on a debug sync point +SET DEBUG_SYNC='after_backup_server_lock_acquired SIGNAL in_progress WAIT_FOR finish'; +--replace_result $MYSQLTEST_VARDIR +--send_eval BACKUP SERVER TO '$MYSQLTEST_VARDIR/some_directory' + +--connection default +# Wait for the "long-running" backup to lock the system +SET DEBUG_SYNC='now WAIT_FOR in_progress'; + +--connection con2 +let $con2_id= `SELECT CONNECTION_ID()`; +# Attempt a backup while the other backup is running +--replace_result $MYSQLTEST_VARDIR +--send_eval BACKUP SERVER TO '$MYSQLTEST_VARDIR/other_directory' + +--connection default +# Check that the backup on con2 is waiting for the backup on con1 +--replace_result $con2_id +eval SET @con2_id = $con2_id; +let $wait_condition= + SELECT COUNT(*) = 1 FROM INFORMATION_SCHEMA.PROCESSLIST + WHERE ID = @con2_id AND State = 'Waiting for backup lock'; +--source include/wait_condition.inc +--replace_result $MYSQLTEST_VARDIR +SELECT COMMAND, STATE, INFO FROM INFORMATION_SCHEMA.PROCESSLIST WHERE ID = @con2_id; + +SET DEBUG_SYNC='now SIGNAL finish'; + +--connection con1 +--reap + +--connection default +# When "long running" backup finishes, check that the backup on con2 +# is also allowed to finish +let $wait_condition= + SELECT COUNT(*) = 0 FROM INFORMATION_SCHEMA.PROCESSLIST + WHERE Info LIKE 'BACKUP SERVER TO %' AND State = 'Waiting for backup lock'; +--source include/wait_condition.inc + +--connection con2 +--reap + +--connection default +SET DEBUG_SYNC='RESET'; + +# Test that BACKUP SERVER blocks when a BACKUP STAGE process is in progress + +BACKUP STAGE START; + +--connection con2 +--replace_result $MYSQLTEST_VARDIR +--send_eval BACKUP SERVER TO '$MYSQLTEST_VARDIR/another_directory' + +--connection default +let $wait_condition= + SELECT COUNT(*) = 1 FROM INFORMATION_SCHEMA.PROCESSLIST + WHERE ID = @con2_id AND State = 'Waiting for backup lock'; +--source include/wait_condition.inc +--replace_result $MYSQLTEST_VARDIR +SELECT COMMAND, STATE, INFO FROM INFORMATION_SCHEMA.PROCESSLIST WHERE ID = @con2_id; + +BACKUP STAGE END; + +# When "long running" backup finishes, check that the backup on con2 +# is also allowed to finish +let $wait_condition= + SELECT COUNT(*) = 0 FROM INFORMATION_SCHEMA.PROCESSLIST + WHERE Info LIKE 'BACKUP SERVER TO %' AND State = 'Waiting for backup lock'; +--source include/wait_condition.inc + +--connection con2 +--reap + +--connection default + +--disconnect con1 +--disconnect con2 + diff --git a/sql/debug_sync.h b/sql/debug_sync.h index f0897aae2ff8d..c885056637dba 100644 --- a/sql/debug_sync.h +++ b/sql/debug_sync.h @@ -22,6 +22,8 @@ Declarations for the Debug Sync Facility. See debug_sync.cc for details. */ +#include + class THD; #if defined(ENABLED_DEBUG_SYNC) diff --git a/sql/mdl.h b/sql/mdl.h index fee5cb3d3be77..99fe7c18198ff 100644 --- a/sql/mdl.h +++ b/sql/mdl.h @@ -569,7 +569,7 @@ class MDL_request /** Set type of lock request. Can be only applied to pending locks. */ inline void set_type(enum_mdl_type type_arg) { - DBUG_ASSERT(ticket == NULL); + DBUG_ASSERT(ticket == nullptr); type= type_arg; } void move_from(MDL_request &from) @@ -580,7 +580,7 @@ class MDL_request next_in_list= from.next_in_list; prev_in_list= from.prev_in_list; key.mdl_key_init(&from.key); - from.ticket= NULL; // that's what "move" means + from.ticket= nullptr; // that's what "move" means } /** @@ -612,17 +612,17 @@ class MDL_request MDL_request& operator=(const MDL_request &) { type= MDL_NOT_INITIALIZED; - ticket= NULL; + ticket= nullptr; /* Do nothing, in particular, don't try to copy the key. */ return *this; } /* Another piece of ugliness for TABLE_LIST constructor */ - MDL_request(): type(MDL_NOT_INITIALIZED), ticket(NULL) {} + MDL_request(): type(MDL_NOT_INITIALIZED), ticket(nullptr) {} MDL_request(const MDL_request *rhs) :type(rhs->type), duration(rhs->duration), - ticket(NULL), + ticket(nullptr), key(&rhs->key) {} }; @@ -1079,7 +1079,7 @@ class MDL_context void done_waiting_for() { mysql_prlock_wrlock(&m_LOCK_waiting_for); - m_waiting_for= NULL; + m_waiting_for= nullptr; mysql_prlock_unlock(&m_LOCK_waiting_for); } void lock_deadlock_victim() @@ -1107,7 +1107,7 @@ class MDL_context The coordinator thread holds the lock for the duration of worker's purge job, or longer, possibly reusing shared MDL for different workers and jobs. */ - MDL_context *lock_warrant= NULL; + MDL_context *lock_warrant= nullptr; inline bool is_lock_warrantee(MDL_key::enum_mdl_namespace ns, const char *db, const char *name, diff --git a/sql/sql_backup.cc b/sql/sql_backup.cc index 39254b45de1f8..52cf597fe949f 100644 --- a/sql/sql_backup.cc +++ b/sql/sql_backup.cc @@ -13,6 +13,8 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */ +#include "debug_sync.h" +#include "mdl.h" #include "my_global.h" #include "mysys_err.h" #include "sql_class.h" @@ -63,38 +65,58 @@ bool Sql_cmd_backup::execute(THD *thd) error_if_data_home_dir(target.str, "BACKUP SERVER TO")) return true; - if (my_mkdir(target.str, 0755, MYF(MY_WME))) + /* + Block concurrent BACKUP SERVER and BACKUP STAGE + */ + MDL_request mdl_request; + MDL_REQUEST_INIT(&mdl_request, MDL_key::BACKUP, "", "", MDL_BACKUP_START, + MDL_EXPLICIT); + if (thd->mdl_context.acquire_lock(&mdl_request, + thd->variables.lock_wait_timeout)) return true; + DEBUG_SYNC(thd, "after_backup_server_lock_acquired"); + + bool fail = my_mkdir(target.str, 0755, MYF(MY_WME)); + #ifndef _WIN32 - int dir= open(target.str, O_DIRECTORY); - if (dir < 0) + int dir; + if(!fail) { - my_error(EE_CANT_MKDIR, MYF(ME_BELL), target.str, errno); - return true; + dir = open(target.str, O_DIRECTORY); + if (dir < 0) + { + my_error(EE_CANT_MKDIR, MYF(ME_BELL), target.str, errno); + fail= true; + } } #endif - bool fail= plugin_foreach_with_mask(thd, backup_start, - MYSQL_STORAGE_ENGINE_PLUGIN, - PLUGIN_IS_DELETED|PLUGIN_IS_READY, - IF_WIN(const_cast(target.str), - reinterpret_cast(dir))); - if (!fail) - fail= plugin_foreach_with_mask(thd, backup_step, + if(!fail) + { + fail= plugin_foreach_with_mask(thd, backup_start, MYSQL_STORAGE_ENGINE_PLUGIN, - PLUGIN_IS_DELETED|PLUGIN_IS_READY, nullptr); + PLUGIN_IS_DELETED|PLUGIN_IS_READY, + IF_WIN(const_cast(target.str), + reinterpret_cast(dir))); + + if (!fail) + fail= plugin_foreach_with_mask(thd, backup_step, + MYSQL_STORAGE_ENGINE_PLUGIN, + PLUGIN_IS_DELETED|PLUGIN_IS_READY, nullptr); - plugin_foreach_with_mask(thd, backup_end, MYSQL_STORAGE_ENGINE_PLUGIN, - PLUGIN_IS_DELETED|PLUGIN_IS_READY, - reinterpret_cast(fail)); + plugin_foreach_with_mask(thd, backup_end, MYSQL_STORAGE_ENGINE_PLUGIN, + PLUGIN_IS_DELETED|PLUGIN_IS_READY, + reinterpret_cast(fail)); - plugin_foreach_with_mask(thd, backup_finalize, MYSQL_STORAGE_ENGINE_PLUGIN, - PLUGIN_IS_DELETED|PLUGIN_IS_READY, nullptr); + plugin_foreach_with_mask(thd, backup_finalize, MYSQL_STORAGE_ENGINE_PLUGIN, + PLUGIN_IS_DELETED|PLUGIN_IS_READY, nullptr); #ifndef _WIN32 - close(dir); + close(dir); #endif + } + thd->mdl_context.release_lock(mdl_request.ticket); if (!fail) my_ok(thd); return fail; diff --git a/sql/sql_plist.h b/sql/sql_plist.h index 0969fef07491a..b9c8ba71247e8 100644 --- a/sql/sql_plist.h +++ b/sql/sql_plist.h @@ -16,6 +16,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */ +#include + template class I_P_List_iterator; class I_P_List_null_counter; @@ -72,14 +74,14 @@ class I_P_List : public C, public I is a bad idea. */ public: - I_P_List() : I(&m_first), m_first(NULL) {}; + I_P_List() : I(&m_first), m_first(nullptr) {}; /* empty() is used in many places in the code instead of a constructor, to initialize a bzero-ed I_P_List instance. */ - inline void empty() { m_first= NULL; C::reset(); I::set_last(&m_first); } - inline bool is_empty() const { return (m_first == NULL); } + inline void empty() { m_first= nullptr; C::reset(); I::set_last(&m_first); } + inline bool is_empty() const { return (m_first == nullptr); } inline void push_front(T* a) { *B::next_ptr(a)= m_first; @@ -102,7 +104,7 @@ class I_P_List : public C, public I } inline void insert_after(T *pos, T *a) { - if (pos == NULL) + if (pos == nullptr) push_front(a); else {