MySQL常用备份工具流程解析
作为DBA都清楚,数据库备份是至关重要的,可以说是拯救数据库最后的灵丹妙药。所以生产系统的数据一定要有备份,当然备份工具和策略的选择也十分重要,直接影响到恢复时的效率。
下面我们就看一下常见的备份工具,以及目前最流行的Percona XtraBackup的备份流程。
一 备份工具介绍
1.1 常见备份工具介绍
MySQL常见的备份工具主要分为三种:
- 逻辑备份,包含 mysqldump和Mydumper;
- 物理备份,包含 Mysqlbackup和Percona XtraBackup;
- binlog备份,包含mysqlbinlog。
这里先说一下binlog备份,它只是把binlog又复制了一份,并且需要在逻辑备份或者物理备份的基础上才能进行数据恢复,无法单独进行数据恢复。
(1)逻辑备份:mysqldump
mysqldump备份出的文件就是sql文件,其核心就是对每个表执行select,然后转化成相应的insert语句。mysqldump的备份流程大致如下:
- 对某个库下所有表加读锁;
- 循环备份备份表数据;
- 释放读锁;
- 循环上面三个步骤;
- 备份完毕。
从上面可以看出在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 的崩溃恢复功能来实现的。它的基本工作原理如下:
- 在启动时创建一个redo log拷贝进程,获取并记录当前的日志序列号 (LSN),从该位点开始持续拷贝有变化的redo log;
- 开启idb文件拷贝线程,拷贝 ibdata1,undo tablespaces及所有的ibd文件;
- ibd文件拷贝结束,通知调用FTWRL(或加备份锁);
- 备份非 InnoDB 数据(.frm、.MRG、.MYD、.MYI ...... 等文件);
- 备份slave和binlog相关信息;
- 刷新日志,拷贝最新的redo log完成后退出日志拷贝线程;
- 释放全局锁,记录备份元数据等,备份结束。
Percona XtraBackup在进行恢复时会应用拷贝的redo log,应用已提交的事务,回滚未提交的事物,将数据库恢复到一致性状态。因为Percona XtraBackup备份出来的是物理文件,所以在使用备份出的文件进行恢复或者迁移时,不会像mysqldump那样会存在很多问题。
使用XtraBackup备份时根据备份参数设置不同,对数据库的变更会造成不同程度的影响,具体影响会在下文分析。
(3)备份工具对比
mysqldump | XtraBackup | |
---|---|---|
库级别备份 | ● | ● |
表级别备份 | ● | ● |
备份部分记录 | ● | - |
增量备份 | - | ● |
流式备份 | - | ● |
是否锁表 | ● | - |
跨版本迁移 | ● | - |
恢复耗时 | 较长 | 较短 |
通过对比发现,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。注意,对数据库做备份时最好选择业务连接最少的从库,因为备份也会消耗一定的资源,避免影响业务,
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~