MySQL常用备份工具流程解析

作为DBA都清楚,数据库备份是至关重要的,可以说是拯救数据库最后的灵丹妙药。所以生产系统的数据一定要有备份,当然备份工具和策略的选择也十分重要,直接影响到恢复时的效率。

下面我们就看一下常见的备份工具,以及目前最流行的Percona XtraBackup的备份流程。

一 备份工具介绍

1.1 常见备份工具介绍

MySQL常见的备份工具主要分为三种:

  • 逻辑备份,包含 mysqldump和Mydumper;
  • 物理备份,包含 Mysqlbackup和Percona XtraBackup;
  • binlog备份,包含mysqlbinlog。
    这里先说一下binlog备份,它只是把binlog又复制了一份,并且需要在逻辑备份或者物理备份的基础上才能进行数据恢复,无法单独进行数据恢复。

(1)逻辑备份:mysqldump

mysqldump备份出的文件就是sql文件,其核心就是对每个表执行select,然后转化成相应的insert语句。mysqldump的备份流程大致如下:

  1. 对某个库下所有表加读锁;
  2. 循环备份备份表数据;
  3. 释放读锁;
  4. 循环上面三个步骤;
  5. 备份完毕。

从上面可以看出在mysqldump备份期间,备份到某个数据库时,该数据库下的表都会处于只读状态,无法对表进行任何变更,直到该库下的表备份完毕,这对于线上环境一般是无法接受的。若是指定了--master-data或者 --dump-slave 则会在备份开始时加全局读锁(FLUSH TABLES WITH READ LOCK),直到备份结束。当然我们可以选一个从库进行备份,这样就不会影响线上业务。另外使用mysqldump备份还有一个最大的好处,因为备份出来的是sql语句,所以它支持跨平台和跨版本的数据迁移或者恢复,这是物理备份无法做到的。

但是也正是因为mysqldump备份出来的是sql语句,在使用时要更加注意,否则可能会酿成大祸。例如,使用mysqldump常见的问题有:

  • 本来迁移部分数据到新实例,结果把原有数据删除了;
  • 由于时区问题,发现恢复出来的表和时间相关的数据不对;
  • 在主库导入备份数据后,发现从库没有同步;
  • 由于字符集问题,发现恢复后数据出现了乱码;
  • ...

所以使用mysqldump时一定要了解各个选项的作用,以及确认备份出来的sql文件里会有什么操作,会对现有数据造成什么影响。

Mydumper原理与Mysqldump原理类似,最大的区别是引入了多线程备份,每个备份线程备份一部分表,当然并发粒度可以到行级,达到多线程备份的目的。这里不再单独介绍。

(2)物理备份:Percona XtraBackup

Percona XtraBackup是 Percona 公司开发的一个用于 MySQL 数据库物理热备的备份工具,是基于 InnoDB 的崩溃恢复功能来实现的。它的基本工作原理如下:

  1. 在启动时创建一个redo log拷贝进程,获取并记录当前的日志序列号 (LSN),从该位点开始持续拷贝有变化的redo log;
  2.  开启idb文件拷贝线程,拷贝 ibdata1,undo tablespaces及所有的ibd文件;
  3. ibd文件拷贝结束,通知调用FTWRL(或加备份锁);
  4.  备份非 InnoDB 数据(.frm、.MRG、.MYD、.MYI ...... 等文件);
  5. 备份slave和binlog相关信息;
  6. 刷新日志,拷贝最新的redo log完成后退出日志拷贝线程;
  7. 释放全局锁,记录备份元数据等,备份结束。

Percona XtraBackup在进行恢复时会应用拷贝的redo log,应用已提交的事务,回滚未提交的事物,将数据库恢复到一致性状态。因为Percona XtraBackup备份出来的是物理文件,所以在使用备份出的文件进行恢复或者迁移时,不会像mysqldump那样会存在很多问题。

使用XtraBackup备份时根据备份参数设置不同,对数据库的变更会造成不同程度的影响,具体影响会在下文分析。

(3)备份工具对比

 mysqldumpXtraBackup
库级别备份
表级别备份
备份部分记录 -
增量备份 - ● 
流式备份 -
是否锁表 -
跨版本迁移 -
恢复耗时 较长 较短

 

通过对比发现,XtraBackup具有对数据库影响小,且能快速恢复的优点,在日常备份中是首选;mysqldump使用相对更加灵活,但是使用是要注意对数据库原有数据的影响。

1.2 备份策略

备份策略主要有:全量备份和增量备份,再加上binlog备份。

 二 XtraBackup备份流程解析

Percona XtraBackup 是目前备份MySQL使用最广泛的工具。在备份过程中,数据库可以进行正常的读写或者其他变更操作,但是偶尔也会遇见备份引起的元数据锁,或提交事务时发现被binlog lock阻塞等情况。下面我们就看一下Percona XtraBackup的备份流程和加锁时机。

说明:以下对Percona XtraBackup 的分析都是基于2.4.23的版本,其他版本会略有差别,但是关键步骤基本相同。

2.1 拷贝redo日志

XtraBackup在备份开始时,会创建一个后台线程,专门用于拷贝数据库的redo log。首先XtraBackup会扫描每组redo log的头部,找出当前的checkpoint lsn,然后从该lsn后顺序拷贝所有的redo log,包括后续新产生的redo log。该线程会一直持续到将非事务表完全拷贝完成,才会安全退出。备份日志输出中会记录拷贝开始时的checkpoint lsn。日志输出如下:

 

1 InnoDB: Number of pools: 1
2 # 下面的3028328就是开始拷贝的lsn
3 211214 09:56:09 >> log scanned up to (3028328)
4 InnoDB: Opened 4 undo tablespaces
5 InnoDB: 0 undo tablespaces made active
6 xtrabackup: Generating a list of tablespaces

2.2 拷贝ibd文件

在拷贝ibd文件之前,会先扫描数据库的数据文件目录,获取ibdata1,undo tablespaces及所有的ibd文件列表,并会记录相应的 space id,因为在恢复时需要这些 space id来找到对应 doublewrite buffer里页面的内容,以及对应的redo log条目。然后开始循环拷贝ibdata1,undo tablespaces及所有的ibd文件。
这里可通过设置--parallel进行多线程备份,提高物理文件的拷贝效率。不设置则默认为1。

2.3 拷贝非ibd文件

在所有ibd文件拷贝完成后,XtraBackup开始备份非ibd文件。这一部分的逻辑比较复杂,因为备份非ibd文件前需要加锁,具体是否会加锁主要受到--no-lock 参数设置的影响。

2.3.1 no-lock 选项参数说明

若是设置了--no-lock为TRUE,则不会使用"FLUSH TABLES WITH READ LOCK"去加全局读锁,但是若备份过程中对non-InnoDB表执行了DDL或者DML操作, 这会导致备份的不一致,恢复出来的数据就会有问题。所以是不建议将--no-lock为TRUE,默认值是FALSE,也就是在不指定该选项的情况下会在备份非ibd文件前加全局读锁。

下面我们结合源码来看看判断是否加全局锁这部分的具体流程逻辑:

复制代码
 1 /* 备份非ibd文件函数入口 */
 2 backup_start()
 3 {
 4 /* opt_no_lock指的是--no-lock参数 */
 5 if (!opt_no_lock) {
 6 /* 如果设置--safe-slave-backup为TRUE,则会会执行"STOP SLAVE SQL_THREAD"关闭SQL线程,
 7 并等待Slave_open_temp_tables变量为0。*/
 8 if (opt_safe_slave_backup) {
 9 if (!wait_for_safe_slave(mysql_connection)) {
10 return(false);
11 }
12 }
13 /* 调用backup_files函数备份非ibd文件,加了全局读锁还会调用一次。
14 这一次,实际上针对的是--rsync方式 */
15 if (!backup_files(fil_path_to_mysql_datadir, true)) {
16 return(false);
17 }
18 
19 history_lock_time = time(NULL);
20 /* 加全局读锁 */
21 if (!lock_tables_maybe(mysql_connection,
22 opt_backup_lock_timeout,
23 opt_backup_lock_retry_count)) {
24 return(false);
25 }
26 }
27 /* 备份非ibd文件 */
28 if (!backup_files(fil_path_to_mysql_datadir, false)) {
29 return(false);
30 }
31 
32 ...
33 ...
复制代码


流程图如下:

 

 

总结来看:

1)若--no-lock为FALSE(默认值),则先施加全局读锁,然后再进行拷贝文件,另外若 --safe-slave-backup 设置为TRUE ,则会在加全局锁之前关闭SQL_THREAD线程;

2)若--no-lock为TRUE,则不会施加锁,直接进行拷贝文件。

2.3.2 加锁处理逻辑

加锁的逻辑主要由lock_tables_maybe实现,先看一下lock_tables_maybe源代码,如下:

复制代码
 1 /* 加全局锁程序入口 */
 2 lock_tables_maybe(MYSQL *connection, int timeout, int retry_count)
 3 {
 4 /* 若已经执行了 LOCK TABLES FOR BACKUP / FLUSH TABLES WITH READ LOCK ,
 5 或者指定了 --lock-ddl-per-table 选项,返回成功,*/
 6 if (tables_locked || opt_lock_ddl_per_table) {
 7 return(true);
 8 }
 9 /* 若支持备份锁则执行 "LOCK TABLES FOR BACKUP" 加锁 */
10 if (have_backup_locks) {
11 return lock_tables_for_backup(connection, timeout, retry_count);
12 }
13 
14 /* 设置加锁超时时间 */
15 if (have_lock_wait_timeout) {
16 char query[200];
17 
18 ut_snprintf(query, sizeof(query),
19 "SET SESSION lock_wait_timeout=%d", timeout);
20 
21 xb_mysql_query(connection, query, false);
22 }
23 /* flush tables */
24 if (!opt_lock_wait_timeout && !opt_kill_long_queries_timeout) {
25 msg_ts("Executing FLUSH NO_WRITE_TO_BINLOG TABLES...\n");
26 xb_mysql_query(connection,
27 "FLUSH NO_WRITE_TO_BINLOG TABLES", false);
28 }
29 /* 判断等待加锁时间是否超时,若超时返回FALSE。 */
30 if (opt_lock_wait_timeout) {
31 if (!wait_for_no_updates(connection, opt_lock_wait_timeout,
32 opt_lock_wait_threshold)) {
33 return(false);
34 }
35 }
36 
37 msg_ts("Executing FLUSH TABLES WITH READ LOCK...\n");
38 /* 若等待了 kill-long-queries-timeout 参数设置的时间后,阻塞加锁的事务还没结束,xtrbackup会将其kill */
39 if (opt_kill_long_queries_timeout) {
40 start_query_killer();
41 }
42 /* 判断galera-info选项设置 */
43 if (have_galera_enabled) {
44 xb_mysql_query(connection,
45 "SET SESSION wsrep_causal_reads=0", false);
46 }
47 /* 加全局读锁 */
48 xb_mysql_query(connection, "FLUSH TABLES WITH READ LOCK", false);
49 
50 if (opt_kill_long_queries_timeout) {
51 stop_query_killer();
52 }
53 
54 tables_locked = true;
55 
56 return(true);
57 }
复制代码


lock_tables_maybe函数简化处理流程如下:

 

 

1)若备份实例上已经加锁( LOCK TABLES FOR BACKUP / FLUSH TABLES WITH READ LOCK)或者设置lock-ddl-per-table 则直接返回;
2)若支持备份锁,则执行LOCK TABLES FOR BACKUP;
3)若不支持备份锁,则执行 FLUSH TABLES WITH READ LOCK。根据相应选项设置,在执行该操作前会判断是否有执行中的DDL/DML,以及等待超时时间,是否kill 对应的未结束的事务等。

从上文中我们还看到一个参数--safe-slave-backup ,该参数的主要作用是:
若是在从库执行的备份操作时设置了该参数,可以防止因从库同步主库操作,而导致XtraBackup长时间请求不到锁而造成备份失败。
--safe-slave-backup选项说明如下:

  若是设置了 --safe-slave-backup 为TRUE,那么会执行"STOP SLAVE SQL_THREAD",并等待Slave_open_temp_tables 为零才开始拷贝非ibd文件,Slave_open_temp_tables 为零说明SQL thread执行的事务都已经完成,这样就能保证备份的一致性。并且此时也不会有在执行的事务阻塞XtraBackup施加全局锁。

2.4 备份slave和binlog信息

备份完非ibd文件后,将会备份slave和binlog信息。
1. 如果命令行中指定了 --slave-info ,则会执行 SHOW SLAVE STATUS 获取复制的相关信息并记录到xtrabackup_slave_info文件中,主要包含从库同步到的主库的binlog位点(Relay_Master_Log_File,Exec_Master_Log_Pos)或者GTID值(Executed_Gtid_Set)等。下面是基于GTID复制备份时xtrabackup_slave_info文件记录的复制相关信息:

1 SET GLOBAL gtid_purged='6b7bda9f-15f0-11ec-ba14-fa163ea367a4:1-83, 9841546e-15f0-11ec-9557-fa163e736db4:1';
2 CHANGE MASTER TO MASTER_AUTO_POSITION=1

 


2. 如果命令行中指定了 --binlog-info ,则会执行 SHOW MASTER STATUS 获取 Binlog 的位置点信息,并记录到xtrabackup_binlog_info文件中。主要信息包含 当前的binlog文件名,binlog位点以及当前的GTID。binlog-info无需显式指定,因为它的默认值为AUTO,如果开启了Binlog,则为ON。xtrabackup_binlog_info文件内容如下:

mysql-bin.000004 2004 6b7bda9f-15f0-11ec-ba14-fa163ea367a4:1-83,9841546e-15f0-11ec-9557-fa163e736db4:1

 


需要注意,在支持备份锁的实例上备份,指定了 --slave-info 或--binlog-info 均会先施加binlog备份锁( LOCK BINLOG FOR BACKUP),这会阻塞任何会更改binlog 位点的操作。

2.5 备份结束

备份完数据库的所有文件和binlog等相关信息,备份工作就基本完成了,之后主要执行的操作如下:
1)执行"FLUSH NO_WRITE_TO_BINLOG ENGINE LOGS",将所有的redo log刷盘;
2)停止redo log复制线程
3)释放全局读锁(备份锁),binlog锁;
4)开启SQL_THREAD;
5)拷贝ib_buffer_pool和ib_lru_dump文件;
6)生成配置文件backup-my.cnf;
7)打印备份信息到xtrabackup_info文件,这些信息主要包含备份时使用的参数信息,备份起止时间,binlog位点信息,以及将会回到的lsn点。
下面是xtrabackup_info记录的部分内容:

复制代码
uuid = 057349b8-5c81-11ec-9595-fa163e736db4
name =
tool_name = xtrabackup
tool_command = --defaults-file=/home/3308/etc/my.cnf --user=ivan --password=... --host=127.0.0.1 --port=3308 --backup --binlog-info=ON --target-dir=/home/backup
tool_version = 2.4.7
ibbackup_version = 2.4.7
server_version = 5.7.26-29-log
#备份开始时间
start_time = 2021-12-14 09:56:09
#备份结束时间
end_time = 2021-12-14 09:56:15
lock_time = 0
#binlog,GTID信息
binlog_pos = filename 'mysql-bin.000004', position '71128', GTID of the last change '6b7bda9f-15f0-11ec-ba14-fa163ea367a4:1-83,
9841546e-15f0-11ec-9557-fa163e736db4:1-232'
innodb_from_lsn = 0
# 恢复时会恢复到该lsn的状态
innodb_to_lsn = 2941006
partial = N
incremental = N
format = file
compact = N
compressed = N
encrypted = N
复制代码

 


三 Percona XtraBackup和锁相关的参数与操作

3.1 相关参数

  • --lock-ddl:若在支持备份锁的实例上指定该选项,则会在备份开始时执行"LOCK TABLES FOR BACKUP",以阻止所有的DDL操作。加锁时间是在拷贝redo log的线程创建前,并持续加锁到dump buffer pool前释放锁。看一下相关的日志:

  

# 执行LOCK TABLES FOR BACKUP 施加备份锁,阻止DDL操作
18:00:33 Executing LOCK TABLES FOR BACKUP...
xtrabackup version 2.4.23 based on MySQL server 5.7.34 Linux (x86_64)

 


  • --lock-ddl-per-table:  在备份开始时对每个innodb表施加元数据锁,防止其上的DDL操作。加锁时间也是在拷贝redo log的线程创建前,持续到所有备份工作完成后才释放锁。看一下相关的备份日志:
复制代码
# 开始对所有innodb表施加元数据锁
17:17:21 Initializing MDL on all current tables.
17:17:21 Locking MDL for `mysql`.`engine_cost`
...
17:17:21 Locking MDL for `test`.`promotion_poi`
17:17:21 >> log scanned up to (17361493)
xtrabackup: Generating a list of tablespaces
## 开始备份innodb表
...
...
## 开始备份非innodb表
# 释放元数据锁
17:17:41 Unlocking MDL for all tables

17:17:41 completed OK!
复制代码

 

 

加锁对应的函数是mdl_lock_tables,释放锁对应的函数是mdl_unlock_all,主要是执行COMMIT,结束mdl_lock_tables中开启的显式事务,来释放MDL锁。

mdl_lock_tables流程如下:

 

 

  • --no-lock 详见 2.3.1 节内容


上面参数--lock-ddl和--lock-ddl-per-table是在Percona XtraBackup 2.4.8 之后添加的,因为MySQL 5.7新增了一个叫做 Sorted Index Builds 的功能,这会导致某些DDL操作不记录重做日志而导致备份失败。使用--lock-ddl或--lock-ddl-per-table就会在备份开始时施加锁,阻止DDL操作。
另外,若备份时指定了--lock-ddl或--lock-ddl-per-table,则在备份非ibd文件时就不是再有加锁操作。

3.2 数据库中执行的加锁操作

  • LOCK TABLES FOR BACKUP:使用新的MDL类型锁,用于阻止所有非事务表的DML,以及所有类型表的DDL操作,但是不影响无锁情况的select操作和对事务表的DML操作。在LOCK TABLES FOR BACKUP 下执行DDL或者对非事务表执行DML,则会被堵塞,执行show processlist 可以看到被阻塞线程状态为Waiting for backup lock。
  • LOCK BINLOG FOR BACKUP:使用的是另一种新的MDL类型锁,用于阻止所有可能更改二进制日志位置或 Exec_Master_Log_Pos 或 Exec_Gtid_Set的操作。在 LOCK BINLOG FOR BACKUP下执行任何会更改 binlog 位点的操作都会被阻塞,执行show processlist 可以看到被阻塞线程状态为Waiting for binlog lock。
  • FLUSH TABLES WITH READ LOCK简称(FTWRL):关闭所有打开的表并使用全局读锁锁定所有数据库的所有表,这时数据库处于**只读状态**,任何DDL/DML操作都会被阻塞。执行show processlist 可以看到被阻塞线程状态为Waiting for global read lock。另外,由于FTWRL需要关闭表,如有大查询,会导致FTWRL等待,进而导致DML/DDL堵塞的时间变长。即使是备库,也有SQL线程在复制来源于主库的更新,上全局锁时,会导致主备库延迟。

注意, LOCK TABLES FOR BACKUP和LOCK BINLOG FOR BACKUP 语句只有在支持备份锁的实例上才会执行,Percona Server for MySQL已经在5.6.16-64.0版本开始支持这种更加轻量的备份锁。

四 思考

Q1: 使用XtraBackup备份的文件进行恢复时,恢复到哪个时间点?
A:恢复到执行LOCK BINLOG FOR BACKUP或FLUSH TABLES WITH READ LOCK的时间点,因为这时任何改变binlog位点的操作都会被阻塞,redo log和binlog 是一致的。

Q2: 在开启binlog的情况下,MySQL的奔溃恢复是同时依赖binlog和redo log这两种日志的,为什么XtraBackup 不用备份binlog?
A:因为在备份中有执行LOCK BINLOG FOR BACKUP/FLUSH TABLES WITH READ LOCK,阻止了任何改变binlog位点的操作,这样只需要根据redo log将有commit log 的事务提交,没有commit log的事务进行回滚即可。

Q3: 使用Percona XtraBackup备份完成后redo的位点是和binlog是一样还是比binlog多一些?

A:通过分析备份流程可以发现备份binlog位点信息(加binlog锁)是发生在停止redo拷贝线程前,而释放锁是在停止redo拷贝线之后,所以redo log会多一些。锁住了binlog保证了在该binlog位点前已经提交的事务的redo log都有commit log的信息,未提交的事物也就没有对应的commit log的信息,即便在锁住binlog后有Innodb表新的DML产生的redo log,但是事务无法提交,也就没有commit log的信息的,最后在回放的过程中对没有commit log的事务进行回滚就可以了。

Q4:Percona XtraBackup什么时候会加锁,以及影响加锁时间长度的因素有哪些?
A:上面进行了分析,加锁操作只在备份非ibd文件时执行,加锁时长主要和非事务表的数量和大小有关,非事务表的数量越多,体积越大,拷贝文件所用的时间越长,那么加锁时间也就越长。也会和redo log生成的速度有关,只是redo log刷盘受到多个因素的影响,未及时刷盘的redo log一般很小。

Q5:Percona XtraBackup 和mysqldump选择哪个更好?

A:通过上面的的解析,若是整个实例备份,首先选择Percona XtraBackup,因为对数据库的影响最小。若只是备份某个库表,这个就要视数据量而定,若数据量不大可以使用mysqldump。注意,对数据库做备份时最好选择业务连接最少的从库,因为备份也会消耗一定的资源,避免影响业务,

 

posted @   心中云烟  阅读(552)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~
点击右上角即可分享
微信分享提示