innobackupex备份源码解析
目前MySQL的物理备份大多数采用xtrabackupex进行,其备份过程如下图所示,这里通过解析 xtrabackup 的源码来详细看看其是如何进行备份的,xtrabackup 版本为 2.4.26。
这里只解析其全量备份的过程,通过源码可以发现很多细节,其核心详细的备份流程如下:
1. 从 log group 的 log header 中获取 lastest checkpoint lsn.
2. 从 lastest checkpoint lsn 开始从 log group 中拷贝 redo log,直到没有 redo log为止,此时拷贝的 redo log 点位记为 log_copy_scanned_lsn
3. 启动后台的 redo log copy 线程,循环从 log_copy_scanned_lsn 点位开始拷贝 redo log 。
4. 获取 ibdata1, undo tablespace 和所有的 ibd 文件,根据指定的 --parallel 参数创建并发的拷贝线程,拷贝数据文件。
5. 待数据拷贝结束,则加全局读锁。这里有一个小细节,如果 MySQL 支持备份锁,且没有指定 --no-backup-locks,则会优先使用备份锁:LOCK TABLES FOR BACKUP;
如果不能使用备份锁,则走正常的加锁流程,这其中包含 xtrabackupex 防止阻塞逻辑。
5.1. 如果没有指定 kill-long-queries-timeout & ftwrl-wait-timeout,则首先执行 FLUSH NO_WRITE_TO_BINLOG TABLES;这是为了防止因为存在 long update 操作而导致 FTWRL操作阻塞整个MySQL服务。当存在 long update操作,FLUSH TABLES 将被阻塞,但是整个 mysql 服务不会被阻塞。当 long update结束,FTWRL操作会很快结束。
5.2. 接着,针对 ftwrl-wait-timeout & ftwrl-wait-threshold 参数,ftwrl-wait-threshold 为认定操作未 long update 操作的阈值,针对 long update操作,xtrabackup 最多再等待 ftwrl-wait-timeout,如果超时 long update操作尚未结束,则 xtrabackup 直接退出。
5.3. 紧接着处理 kill-long-queries-timeout 参数,启动一个后台线程,监控当前数据库的所有操作;该参数为开始 FTWRL 到 KILL 掉阻塞该操作的 query之间的耗时。
5.4. FLUSH TABLES WITH READ LOCK。
5.5. 关闭处理 kill-long-queries-timeout 的后台线程
6. 开始拷贝非 ibd 文件。
7. 如果设置了 slave_info,则会将SHOW SLAVE STATUS的相关信息,记录在xtrabackup_slave_info中;如果之前使用了备份锁,这里会锁定 BINLOG: LOCK BINLOG FOR BACKUP;
8. 输出 SHOW MASTER STATUS 信息到 xtrabackup_binlog_info中;
9. 执行 FLUSH NO_WRITE_TO_BINLOG ENGINE LOGS,将 redo log持久化到磁盘供 redo log copy 线程拷贝
10. 读取最新的 checkpoint lsn,用于后续的增量备份。
11. 停止redo log拷贝线程. 将备份的元数据信息记录在XTRABACKUP_METADATA_FILENAME中,即xtrabackup_checkpoints。这里停止之后,redo log copy 线程会做最后一次 copy, 而后停止。
12. 释放全局读锁,UNLOCK BINLOG(如果之前锁定了BINLOG) & UNLOCK TABLES,生成back-my.cnf 配置文件,将备份相关信息记录到 xtrabackup_info 文件中。
13. 结束。
其核心代码如下:
/** * xtrabackup 入口函数 */ int main(int argc, char **argv) { ... /* --backup backup 逻辑, 这里主要关注 backup */ if (xtrabackup_backup) { xtrabackup_backup_func(); } /* --stats */ if (xtrabackup_stats) { xtrabackup_stats_func(server_argc, server_defaults); } /* --prepare */ if (xtrabackup_prepare) { xtrabackup_prepare_func(server_argc, server_defaults); } ... }
/** * backup 逻辑 */ void xtrabackup_backup_func(void) { /* start back ground thread to copy newer log 创建 redo log 后台拷贝线程。可以看到,其首先在主线程中从 lastest chechkpoint lsn 点位开始拷贝 log group 中的 redo log,直到拷贝完成,拷贝完成的点位记为log_copy_scanned_lsn
,而后再启动后台的 redo log copy 线程,从log_copy_scanned_lsn位置继续拷贝 redo log。
*/ os_thread_id_t log_copying_thread_id; datafiles_iter_t *it; log_hdr_buf_ = static_cast<byte *> (ut_malloc_nokey(LOG_FILE_HDR_SIZE + UNIV_PAGE_SIZE_MAX)); log_hdr_buf = static_cast<byte *> (ut_align(log_hdr_buf_, UNIV_PAGE_SIZE_MAX)); /* get current checkpoint_lsn 获取当前的 checkpoint_lsn */ /* Look for the latest checkpoint from any of the log groups 获取最新的 checkpoint lsn */ mutex_enter(&log_sys->mutex); err = recv_find_max_checkpoint(&max_cp_group, &max_cp_field); if (err != DB_SUCCESS) { ut_free(log_hdr_buf_); exit(EXIT_FAILURE); } // 读取 log group header page log_group_header_read(max_cp_group, max_cp_field); buf = log_sys->checkpoint_buf; // 读取 checkpoint_lsn 和 checkpoint_no checkpoint_lsn_start = mach_read_from_8(buf + LOG_CHECKPOINT_LSN); checkpoint_no_start = mach_read_from_8(buf + LOG_CHECKPOINT_NO); mutex_exit(&log_sys->mutex); reread_log_header: fil_io(IORequest(IORequest::READ), true, page_id_t(max_cp_group->space_id, 0), univ_page_size, 0, LOG_FILE_HDR_SIZE, log_hdr_buf, max_cp_group); /* check consistency of log file header to copy */ mutex_enter(&log_sys->mutex); err = recv_find_max_checkpoint(&max_cp_group, &max_cp_field); if (err != DB_SUCCESS) { ut_free(log_hdr_buf_); exit(EXIT_FAILURE); } log_group_header_read(max_cp_group, max_cp_field); buf = log_sys->checkpoint_buf; if(checkpoint_no_start != mach_read_from_8(buf + LOG_CHECKPOINT_NO)) { checkpoint_lsn_start = mach_read_from_8(buf + LOG_CHECKPOINT_LSN); checkpoint_no_start = mach_read_from_8(buf + LOG_CHECKPOINT_NO); mutex_exit(&log_sys->mutex); goto reread_log_header; } mutex_exit(&log_sys->mutex); xtrabackup_init_datasinks(); if (!select_history()) { exit(EXIT_FAILURE); } /* open the log file 打开 xtrabackup_logfile */ memset(&stat_info, 0, sizeof(MY_STAT)); dst_log_file = ds_open(ds_redo, XB_LOG_FILENAME, &stat_info); if (dst_log_file == NULL) { msg("xtrabackup: error: failed to open the target stream for " "'%s'.\n", XB_LOG_FILENAME); ut_free(log_hdr_buf_); exit(EXIT_FAILURE); } /* label it 在 xtrabackup_logfile 头部写入 lobel 信息: */ strcpy((char*) log_hdr_buf + LOG_HEADER_CREATOR, "xtrabkup "); ut_sprintf_timestamp( (char*) log_hdr_buf + (LOG_HEADER_CREATOR + (sizeof "xtrabkup ") - 1)); if (ds_write(dst_log_file, log_hdr_buf, LOG_FILE_HDR_SIZE)) { msg("xtrabackup: error: write to logfile failed\n"); ut_free(log_hdr_buf_); exit(EXIT_FAILURE); } ut_free(log_hdr_buf_); /* start flag */ log_copying = TRUE; /* start io throttle */ if(xtrabackup_throttle) { os_thread_id_t io_watching_thread_id; io_ticket = xtrabackup_throttle; wait_throttle = os_event_create("wait_throttle"); os_thread_create(io_watching_thread, NULL, &io_watching_thread_id); } mutex_enter(&log_sys->mutex); xtrabackup_choose_lsn_offset(checkpoint_lsn_start); mutex_exit(&log_sys->mutex); if (opt_lock_ddl_per_table) { mdl_lock_tables(); } /* copy log file by current position 从最新的 checkpoint_lsn 开始拷贝 redo log; 拷贝到最新 redo log 结束, 停止拷贝, 停止拷贝点位: log_copy_scanned_lsn */ if(xtrabackup_copy_logfile(checkpoint_lsn_start, FALSE)) exit(EXIT_FAILURE); /* * From this point forward, recv_parse_or_apply_log_rec_body should fail if * MLOG_INDEX_LOAD event is parsed as its not safe to continue the backup * in any situation (with or without --lock-ddl-per-table). */ mdl_taken = true; log_copying_stop = os_event_create("log_copying_stop"); debug_sync_point("xtrabackup_pause_after_redo_catchup"); // 创建 redo log 后台拷贝线程 os_thread_create(log_copying_thread, NULL, &log_copying_thread_id); /* Populate fil_system with tablespaces to copy 获取 ibdata1, undo tablespace 和所有的 ibd 文件 */ err = xb_load_tablespaces(); if (err != DB_SUCCESS) { msg("xtrabackup: error: xb_load_tablespaces() failed with" "error code %lu\n", err); exit(EXIT_FAILURE); } /* FLUSH CHANGED_PAGE_BITMAPS call */ if (!flush_changed_page_bitmaps()) { exit(EXIT_FAILURE); } debug_sync_point("xtrabackup_suspend_at_start"); if (xtrabackup_incremental) { if (!xtrabackup_incremental_force_scan && have_changed_page_bitmaps) { changed_page_bitmap = xb_page_bitmap_init(); } if (!changed_page_bitmap) { msg("xtrabackup: using the full scan for incremental " "backup\n"); } else if (incremental_lsn != checkpoint_lsn_start) { /* Do not print that bitmaps are used when dummy bitmap is build for an empty LSN range. */ msg("xtrabackup: using the changed page bitmap\n"); } } ut_a(xtrabackup_parallel > 0); if (xtrabackup_parallel > 1) { msg("xtrabackup: Starting %u threads for parallel data " "files transfer\n", xtrabackup_parallel); } it = datafiles_iter_new(f_system); if (it == NULL) { msg("xtrabackup: Error: datafiles_iter_new() failed.\n"); exit(EXIT_FAILURE); } /* Create data copying threads 创建数据拷贝线程 */ data_threads = (data_thread_ctxt_t *) ut_malloc_nokey(sizeof(data_thread_ctxt_t) * xtrabackup_parallel); count = xtrabackup_parallel; mutex_create(LATCH_ID_XTRA_COUNT_MUTEX, &count_mutex); // 拷贝物理文件, 其中, xtrabackup_parallel 是拷贝并发线程数, 由 --parallel 参数指定 for (i = 0; i < (uint) xtrabackup_parallel; i++) { data_threads[i].it = it; data_threads[i].num = i+1; data_threads[i].count = &count; data_threads[i].count_mutex = &count_mutex; data_threads[i].error = &data_copying_error; // 创建数据拷贝线程 os_thread_create(data_copy_thread_func, data_threads + i, &data_threads[i].id); } /* Wait for threads to exit 循环等待, 直到数据拷贝结束 */ while (1) { os_thread_sleep(1000000); mutex_enter(&count_mutex); if (count == 0) { mutex_exit(&count_mutex); break; } mutex_exit(&count_mutex); } mutex_free(&count_mutex); ut_free(data_threads); datafiles_iter_free(it); if (data_copying_error) { exit(EXIT_FAILURE); } if (changed_page_bitmap) { xb_page_bitmap_deinit(changed_page_bitmap); } } // 调用 backup_start() 函数, 这个函数会加全局读锁, 拷贝非 ibd 文件 if (!backup_start()) { exit(EXIT_FAILURE); } if(opt_lock_ddl_per_table && opt_debug_sleep_before_unlock){ msg_ts("Debug sleep for %u seconds\n", opt_debug_sleep_before_unlock); os_thread_sleep(opt_debug_sleep_before_unlock * 1000000); } /* read the latest checkpoint lsn 读取最新的 checkpoint lsn, 用于后续的增量备份 */ latest_cp = 0; { log_group_t* max_cp_group; ulint max_cp_field; ulint err; mutex_enter(&log_sys->mutex); err = recv_find_max_checkpoint(&max_cp_group, &max_cp_field); if (err != DB_SUCCESS) { msg("xtrabackup: Error: recv_find_max_checkpoint() failed.\n"); mutex_exit(&log_sys->mutex); goto skip_last_cp; } log_group_header_read(max_cp_group, max_cp_field); xtrabackup_choose_lsn_offset(checkpoint_lsn_start); latest_cp = mach_read_from_8(log_sys->checkpoint_buf + LOG_CHECKPOINT_LSN); mutex_exit(&log_sys->mutex); msg("xtrabackup: The latest check point (for incremental): " "'" LSN_PF "'\n", latest_cp); } skip_last_cp: /* stop log_copying_thread 停止redo log拷贝线程. 将备份的元数据信息记录在XTRABACKUP_METADATA_FILENAME中,即xtrabackup_checkpoints。
log_copying = FALSE 后, 后台的 redo log copy 线程会做最后一次 copy。 */ log_copying = FALSE; os_event_set(log_copying_stop); msg("xtrabackup: Stopping log copying thread.\n"); while (log_copying_running) { msg("."); os_thread_sleep(200000); /*0.2 sec*/ } msg("\n"); os_event_destroy(log_copying_stop); if (ds_close(dst_log_file)) { exit(EXIT_FAILURE); } if (!validate_missing_encryption_tablespaces()) { exit(EXIT_FAILURE); } if(!xtrabackup_incremental) { strcpy(metadata_type, "full-backuped"); metadata_from_lsn = 0; } else { strcpy(metadata_type, "incremental"); metadata_from_lsn = incremental_lsn; } metadata_to_lsn = latest_cp; metadata_last_lsn = log_copy_scanned_lsn; if (!xtrabackup_stream_metadata(ds_meta)) { msg("xtrabackup: Error: failed to stream metadata.\n"); exit(EXIT_FAILURE); } /* 调用backup_finish函数,这个函数会释放全局读锁 */ if (!backup_finish()) { exit(EXIT_FAILURE); } if (xtrabackup_extra_lsndir) { char filename[FN_REFLEN]; sprintf(filename, "%s/%s", xtrabackup_extra_lsndir, XTRABACKUP_METADATA_FILENAME); if (!xtrabackup_write_metadata(filename)) { msg("xtrabackup: Error: failed to write metadata " "to '%s'.\n", filename); exit(EXIT_FAILURE); } sprintf(filename, "%s/%s", xtrabackup_extra_lsndir, XTRABACKUP_INFO); if (!xtrabackup_write_info(filename)) { msg("xtrabackup: Error: failed to write info " "to '%s'.\n", filename); exit(EXIT_FAILURE); } } if (opt_lock_ddl_per_table) { mdl_unlock_all(); } if (opt_transition_key != NULL || opt_generate_transition_key) { if (!xb_tablespace_keys_dump(ds_data, opt_transition_key, opt_transition_key != NULL ? strlen(opt_transition_key) : 0)) { msg("xtrabackup: Error: failed to dump " "tablespace keys.\n"); exit(EXIT_FAILURE); } } xtrabackup_destroy_datasinks(); if (wait_throttle) { /* wait for io_watching_thread completion */ while (io_watching_thread_running) { os_thread_sleep(1000000); } os_event_destroy(wait_throttle); wait_throttle = NULL; } msg("xtrabackup: Transaction log of lsn (" LSN_PF ") to (" LSN_PF ") was copied.\n", checkpoint_lsn_start, log_copy_scanned_lsn); xb_filters_free(); xb_data_files_close(); recv_sys_debug_free(); log_shutdown(); trx_pool_close(); lock_sys_close(); os_thread_free(); row_mysql_close(); sync_check_close(); xb_keyring_shutdown(); /* Make sure that the latest checkpoint made it to xtrabackup_logfile */ if (latest_cp > log_copy_scanned_lsn) { msg("xtrabackup: error: last checkpoint LSN (" LSN_PF ") is larger than last copied LSN (" LSN_PF ").\n", latest_cp, log_copy_scanned_lsn); exit(EXIT_FAILURE); } }
static #ifndef __WIN__ void* #else ulint #endif log_copying_thread( void* arg __attribute__((unused))) { /* Initialize mysys thread-specific memory so we can use mysys functions in this thread. */ my_thread_init(); ut_a(dst_log_file != NULL); log_copying_running = TRUE; while(log_copying) { os_event_reset(log_copying_stop); os_event_wait_time_low(log_copying_stop, xtrabackup_log_copy_interval * 1000ULL, 0); if (log_copying) { if(xtrabackup_copy_logfile(log_copy_scanned_lsn, FALSE)) { exit(EXIT_FAILURE); } } } /* last copying */ if(xtrabackup_copy_logfile(log_copy_scanned_lsn, TRUE)) { exit(EXIT_FAILURE); } log_copying_running = FALSE; my_thread_end(); os_thread_exit(); return(0); }
/** * 调用 backup_start() 函数, 这个函数会加全局读锁, 拷贝非 ibd 文件 */ bool backup_start() { // opt_no_lock 指的是 no-lock 参数 if (!opt_no_lock) { /* 如果指定了--safe-slave-backup,会关闭SQL线程,等待Slave_open_temp_tables变量为0。 如果使用的是statement格式,且使用了临时表,建议设置--safe-slave-backup。 对于row格式,无需指定该选项 */ if (opt_safe_slave_backup) { if (!wait_for_safe_slave(mysql_connection)) { return(false); } } // 调用 backup_files 备份非 ibd 文件, 加了全局读锁还会调用一次. // 这一次, 实际上针对的是 --rsync 方式。 这里不做深入研究。 if (!backup_files(fil_path_to_mysql_datadir, true)) { return(false); } history_lock_time = time(NULL); // 加全局读锁, 如果支持备份锁, 且没有设置 --no-backup-locks, 会优先使用备份锁。 if (!lock_tables_maybe(mysql_connection, opt_backup_lock_timeout, opt_backup_lock_retry_count)) { return(false); } } // 备份非 ibd 文件 if (!backup_files(fil_path_to_mysql_datadir, false)) { return(false); } // There is no need to stop slave thread before coping non-Innodb data when // --no-lock option is used because --no-lock option requires that no DDL or // DML to non-transaction tables can occur. if (opt_no_lock) { if (opt_safe_slave_backup) { if (!wait_for_safe_slave(mysql_connection)) { return(false); } } } // 如果设置了 slave_info, 会将SHOW SLAVE STATUS的相关信息,记录在xtrabackup_slave_info中 if (opt_slave_info) { /* 如果之前使用了备份锁,这里会先锁定Binlog(LOCK BINLOG FOR BACKUP)*/ lock_binlog_maybe(mysql_connection, opt_backup_lock_timeout, opt_backup_lock_retry_count); if (!write_slave_info(mysql_connection)) { return(false); } } /* The only reason why Galera/binlog info is written before wait_for_ibbackup_log_copy_finish() is that after that call the xtrabackup binary will start streamig a temporary copy of REDO log to stdout and thus, any streaming from innobackupex would interfere. The only way to avoid that is to have a single process, i.e. merge innobackupex and xtrabackup. */ if (opt_galera_info) { if (!write_galera_info(mysql_connection)) { return(false); } write_current_binlog_file(mysql_connection); } /* 如果--binlog-info设置的是ON(默认是AUTO),则会将SHOW MASTER STATUS的相关信息,记录在xtrabackup_binlog_info中 */ if (opt_binlog_info == BINLOG_INFO_ON) { lock_binlog_maybe(mysql_connection, opt_backup_lock_timeout, opt_backup_lock_retry_count); write_binlog_info(mysql_connection); } // 执行 FLUSH NO_WRITE_TO_BINLOG ENGINE LOGS, 将 redo log 持久化到磁盘共 redo log copy 线程拷贝。 if (have_flush_engine_logs) { msg_ts("Executing FLUSH NO_WRITE_TO_BINLOG ENGINE LOGS...\n"); xb_mysql_query(mysql_connection, "FLUSH NO_WRITE_TO_BINLOG ENGINE LOGS", false); } return(true); }
/*********************************************************************//** 加全局读锁, 如果支持备份锁, 且没有设置 --no-backup-locks, 会优先使用备份锁 Function acquires either a backup tables lock, if supported by the server, or a global read lock (FLUSH TABLES WITH READ LOCK) otherwise. @returns true if lock acquired */ bool lock_tables_maybe(MYSQL *connection, int timeout, int retry_count) { if (tables_locked || opt_lock_ddl_per_table) { return(true); } // 如果指定了 --no-backup-locks, 则使用备份锁。目前, mysql5.7.27 不支持备份锁 if (have_backup_locks) { return lock_tables_for_backup(connection, timeout, retry_count); } // lock_wait_timeout if (have_lock_wait_timeout) { char query[200]; ut_snprintf(query, sizeof(query), "SET SESSION lock_wait_timeout=%d", timeout); xb_mysql_query(connection, query, false); } // 没有 lock_wait_timeout & kill_long_query_timeout if (!opt_lock_wait_timeout && !opt_kill_long_queries_timeout) { /* 首先 FLUSH TABLES. 如果 long update 正在进行, 那么 FLUSH TABLES 将等待, 但不会暂停整个 mysqld 服务, 当 long update 完成时, FLUSH TABLES WITH READ LOCK 将启动被很快成功。 因此, FLUSH TABLES 是为了降低 mysqldump 和大多数客户端连接都暂停的可能性。 FLUSH TABLES 语句被某个表阻塞不影响其他表的操作, 例如 table A 正在进行 long update, 那么 flush tables 将被该 long update 阻塞, 但是 FLUSH tables 阻塞过程中 table B 可以进行操作。 然而, 如果在两次刷新之间进行了 long update, 那将出现长时间的暂停。 lock_wait_timeout 选项具有相同的用途, 与该技巧不兼容。 */ msg_ts("Executing FLUSH NO_WRITE_TO_BINLOG TABLES...\n"); // 执行 FLUSH NO_WRITE_TO_BINLOG TABLES, 这是 FLUSH TABLES 操作, 该语句不写入 binlog // 关闭所有打开的表, 强制关闭所有打开正在使用的表, xb_mysql_query(connection, "FLUSH NO_WRITE_TO_BINLOG TABLES", false); } // opt_lock_wait_timeout: 对于 long query 最多再等待 opt_lock_wait_timeout 时间, 如果该时间内 long query 执行完成, // 则继续执行, 否则退出。 // opt_lock_wait_threshold: long query 的阈值, 对于 long query 最多再等待 lock_wait_timeout. if (opt_lock_wait_timeout) { if (!wait_for_no_updates(connection, opt_lock_wait_timeout, opt_lock_wait_threshold)) { return(false); } } msg_ts("Executing FLUSH TABLES WITH READ LOCK...\n"); // opt_kill_long_queries_timeout: 从 flush tables with read lock 到 kill 掉阻塞他的操作之前等待的秒数。 if (opt_kill_long_queries_timeout) { start_query_killer(); } if (have_galera_enabled) { xb_mysql_query(connection, "SET SESSION wsrep_causal_reads=0", false); } /** * FLUSH TABLES WITH READ LOCK. * 关闭所有被打开的表, 并且使用全局读锁锁住所有库的所有表。 * 如果有事务存在, 那么事务提交时将被 hang 住, 不会回滚。 */ xb_mysql_query(connection, "FLUSH TABLES WITH READ LOCK", false); if (opt_kill_long_queries_timeout) { stop_query_killer(); } tables_locked = true; return(true); }
/* 调用backup_finish函数,这个函数会释放全局读锁 */ bool backup_finish() { /* release all locks 释放所有锁, 如果锁定了 binlog, 还会解锁 binlog。 这里执行 UNLOCK BINLOG;& UNLOCK TABLES; */ if (!opt_no_lock) { unlock_all(mysql_connection); history_lock_time = time(NULL) - history_lock_time; } else { history_lock_time = 0; } /** * 如果设置了 --safe-slave-backup, 且 SQL 线程停止了, 则会开启 SQL 线程 */ if (opt_safe_slave_backup && sql_thread_started) { msg("Starting slave SQL thread\n"); xb_mysql_query(mysql_connection, "START SLAVE SQL_THREAD", false); } /* Copy buffer pool dump or LRU dump 拷贝 ib_buffer_pool 文件和 ib_lru_dump 文件 */ if (!opt_rsync) { if (opt_dump_innodb_buffer_pool) { check_dump_innodb_buffer_pool(mysql_connection); } if (buffer_pool_filename && file_exists(buffer_pool_filename)) { const char *dst_name; dst_name = trim_dotslash(buffer_pool_filename); copy_file(ds_data, buffer_pool_filename, dst_name, 0); } if (file_exists("ib_lru_dump")) { copy_file(ds_data, "ib_lru_dump", "ib_lru_dump", 0); } if (file_exists("ddl_log.log")) { copy_file(ds_data, "ddl_log.log", "ddl_log.log", 0); } } msg_ts("Backup created in directory '%s'\n", xtrabackup_target_dir); if (mysql_binlog_position != NULL) { msg("MySQL binlog position: %s\n", mysql_binlog_position); } if (!mysql_slave_position.empty() && opt_slave_info) { msg("MySQL slave binlog position: %s\n", mysql_slave_position.c_str()); } // 生成配置文件: back-my.cnf if (!write_backup_config_file()) { return(false); } // 将备份的相关信息记录在 xtrabackup_info 文件中 if (!write_xtrabackup_info(mysql_connection)) { return(false); } return(true); }