MySQL 逻辑备份与恢复测试的相关总结
一、什么样的备份是数据库逻辑备份呢?
大家都知道,数据库在返回数据给我们使用的时候都是按照我们最初所设计期望的具有一定逻辑关联格式的形式一条一条数据来展现的,具有一定的商业逻辑属性,而在物理存储的层面上数据库软件却是按照数据库软件所设计的某种特定格式经过一定的处理后存放。
数据库逻辑备份就是备份软件按照我们最初所设计的逻辑关系,以数据库的逻辑结构对象为单位,将数据库中的数据按照预定义的逻辑关联格式一条一条生成相关的文本文件,以达到备份的目的。
二、常用的逻辑备份
逻辑备份可以说是最简单,也是目前中小型系统最常使用的备份方式。 在 MySQL 中我们常用的逻辑备份主要就是两种,一种是将数据生成可以完全重现当前数据库中数据的 INSERT 语句,另外一种就是将数据通过逻辑备份软件,将我们数据库表数据以特定分隔符进行分隔后记录在文本文件中。
①生成 INSERT 语句备份
两种逻辑备份各有优劣,所针对的使用场景也会稍有差别,我们先来看一下生成 INSERT 语句的逻辑备份。
在 MySQL 数据库中,我们一般都是通过 MySQL 数据库软件自带工具程序中的 mysqldump 来实现声称 INSERT 语句的逻辑备份文件。其使用方法基本如下:
Dumping definition and data mysql database or table Usage: mysqldump [OPTIONS] database [tables] OR mysqldump [OPTIONS] --databases [OPTIONS] DB1 [DB2 DB3...] OR mysqldump [OPTIONS] --all-databases [OPTIONS]
由于 mysqldump 的使用方法比较简单,大部分需要的信息都可以通过运行“mysqldump --help”而获得。这里我只想结合 MySQL 数据库的一些概念原理和大家探讨一下当我们使用 mysqldump 来做数据库逻辑备份的时候有些什么技巧以及需要注意一些什么内容。
我们都知道,对于大多数使用数据库的软件或者网站来说,都希望自己数据库能够提供尽可能高的可用性,而不是时不时的就需要停机停止提供服务。因为一旦数据库无法提供服务,系统就无法再通过存取数据来提供一些动态功能。
所以对于大多数系统来说如果要让每次备份都停机来做可能都是不可接受的,可是 mysqldump 程序的实现原理是通过我们给的参数信息加上数据库中的系统表信息来一个表一个表获取数据然后生成 INSERT 语句再写入备份文件中的。这样就出现了一个问题,在系统正常运行过程中,很可能会不断有数据变更的请求正在执行,这样就可能造成在 mysqldump 备份出来的数据不一致。
也就是说备份数据很可能不是同一个时间点的数据,而且甚至可能都没办法满足完整性约束。这样的备份集对于有些系统来说可能并没有太大问题,但是对于有些对数据的一致性和完整性要求比较严格系统来说问题就大了,就是一个完全无效的备份集。
对于如此场景,我们该如何做?我们知道,想数据库中的数据一致,那么只有两种情况下可以做到。
- 第一、同一时刻取出所有数据;
- 第二、数据库中的数据处于静止状态。
对于第一种情况,大家肯定会想,这可能吗?
不管如何,只要有两个以上的表,就算我们如何写程序,都不可能昨晚完全一致的取数时间点啊。是的,我们确实无法通过常规方法让取数的时间点完全一致,但是大家不要忘记,在同一个事务中,数据库是可以做到所读取的数据是处于同一个时间点的。
所以,对于事务支持的存储引擎,如 Innodb 或者 BDB 等 ,我们就可以通过控制将整个备份过程控制在同一个事务中,来达到备份数据的一致性和完整性,而且 mysqldump 程序也给我们提供了相关的参数选项来支持该功能,就是通过“--single-transaction”选项,可以不影响数据库的任何正常服务。
对于第二种情况我想大家首先想到的肯定是将需要备份的表锁定,只允许读取而不允许写入。
是的,我们确实只能这么做。我们只能通过一个折衷的处理方式,让数据库在备份过程中仅提供数据的查询服务,锁定写入的服务,来使数据暂时处于一个一致的不会被修改的状态,等 mysqldump 完成备份后再取消写入锁定,重新开始提供完整的服务。
mysqldump 程序自己也提供了相关选项如“--lock-tables”和“--lock-all-tables”,在执行之前会锁定表,执行结束后自动释放锁定。
这里有一点需要注意的就是,“--lock-tables”并不是一次性将需要 dump 的所有表锁定,而是每次仅仅锁定一个数据库的表,如果你需要 dump 的表分别在多个不同的数据库中,一定要使用“--lock-all-tables”才能确保数据的一致完整性。
当通过 mysqldump 生成 INSERT 语句的逻辑备份文件的时候,有一个非常有用的选项可以供我们使用,那就是“--master-data[=value]”。当添加了“--master-data=1”的时候, mysqldump 会将当前 MySQL 使用到 binlog 日志的名称和位置记录到 dump 文件中,并且是被 以 CHANGE_MASTER 语句的形式记录,如果仅仅只是使用“--master-data”或者“--master-data=2”,则 CHANGE_MASTER 语句会以注释的形式存在。这个选项在实施 slave 的在线搭建的时候是非常有用的,即使不是进行在线搭建 slave,也可以在某些情况下做恢复的过程中通过备份的 binlog 做进一步恢复操作。
在某些场景下,我们可能只是为了将某些特殊的数据导出到其他数据库中,而又不希望通过先建临时表的方式来实现,我们还可以在通过 mysqldump 程序的“—where='where-condition'”来实现,但只能在仅 dump 一个表的情况下使用。
其实除了以上一些使用诀窍之外,mysqldump 还提供了其他很多有用的选项供大家在不同的场景下只用,如通过“--no-data”仅仅 dump 数据库结构创建脚本,通过“--no-create-info”去掉 dump 文件中创建表结构的命令等等,感兴趣的读者朋友可以详细阅读 mysqldump 程序的使用介绍再自行测试。
②生成特定格式的纯文本备份数据文件备份
除了通过生成 INSERT 命令来做逻辑备份之外,我们还可以通过另外一种方式将数据库中的数据以特定分隔字符将数据分隔记录在文本文件中,以达到逻辑备份的效果。这样的备份数据与 INSERT 命令文件相比,所需要使用的存储空间更小,数据格式更加清晰明确,编辑方便。但是缺点是在同一个备份文件中不能存在多个表的备份数据,没有数据库结构的重建命令。对于备份集需要多个文件,对我们产生的影响无非就是文件多了维护和恢复成本增加,但这些基本上都可以通过编写一些简单的脚本来实现。
那我们一般可以使用什么方法来生成这样的备份集文件呢,其实 MySQL 也已经给我们实现的相应的功能。
在 MySQL 中一般都使用以下两种方法来获得可以自定义分隔符的纯文本备份文件。
1.通过执行 SELECT ... TO OUTFILE FROM ...命令来实现
在 MySQL 中提供了一种 SELECT 语法,专供用户通过 SQL 语句将某些特定数据以指定格式输出到文本文件中,同时也提供了实用工具和相关的命令可以方便的将导出文件原样再导入到数据库中。这不正是我们做备份所需要的么?
该命令有几个需要注意的参数如下:
- 实现字符转义功能的“FIELDS ESCAPED BY ['name']” 将 SQL 语句中需要转义的字符进行转义;
- 可以将字段的内容“包装”起来的“FIELDS [OPTIONALLY] ENCLOSED BY 'name'”,如果不使用“OPTIONALLY”则包括数字类型的所有类型数据都会被“包装”,使 用“OPTIONALLY”之后,则数字类型的数据不会被指定字符“包装”。
- 通过"FIELDS TERMINATED BY"可以设定每两个字段之间的分隔符;
- 而通过“LINES TERMINATED BY”则会告诉 MySQL 输出文件在每条记录结束的时候需要添加什么字符。
如以下示例:
root@localhost : test 10:02:02> SELECT * INTO OUTFILE '/tmp/dump.text' -> FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' -> LINES TERMINATED BY '\n' -> FROM test_outfile limit 100; Query OK, 100 rows affected (0.00 sec) root@localhost : test 10:02:11> exit Bye root@sky:/tmp# cat dump.text 350021,21,"A","abcd" 350022,22,"B","abcd" 350023,23,"C","abcd" 350024,24,"D","abcd" 350025,25,"A","abcd" ... ...
2.通过 mysqldump 导出
可能我们都知道 mysqldump 可以将数据库中的数据以 INSERT 语句的形式生成相关备份文件,其实除了生成 INSERT 语句之外,mysqldump 还同样能实现上面“SELECT ... TO OUTFILE FROM ...”所实现的功能,而且同时还会生成一个相关数据库结构对应的创建脚本 。
如以下示例:
root@sky:~# ls -l /tmp/mysqldump total 0 root@sky:~# mysqldump -uroot -T/tmp/mysqldump test test_outfile --fieldsenclosed-by=\" --fields-terminated-by=, root@sky:~# ls -l /tmp/mysqldump total 8 -rw-r--r-- 1 root root 1346 2021-4-20 22:18 test_outfile.sql -rw-rw-rw- 1 mysql mysql 2521 2021-4-20 22:18 test_outfile.txt root@sky:~# cat /tmp/mysqldump/test_outfile.txt 350021,21,"A","abcd" 350022,22,"B","abcd" 350023,23,"C","abcd" 350024,24,"D","abcd" 350025,25,"A","abcd" ... ... root@sky:~# cat /tmp/mysqldump/test_outfile.sql -- MySQL dump 10.11 -- -- Host: localhost Database: test -- ------------------------------------------------------ -- Server version 5.0.51a-log /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; /*!40101 SET NAMES utf8 */; /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; /*!40103 SET TIME_ZONE='+00:00' */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; -- -- Table structure for table `test_outfile` -- DROP TABLE IF EXISTS `test_outfile`; SET @saved_cs_client = @@character_set_client; SET character_set_client = utf8; CREATE TABLE `test_outfile` ( `id` int(11) NOT NULL default '0', `t_id` int(11) default NULL, `a` char(1) default NULL, `mid` varchar(32) default NULL ) ENGINE=MyISAM DEFAULT CHARSET=utf8; SET character_set_client = @saved_cs_client; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; -- Dump completed on 2021-4-20 14:18:23
这样的输出结构对我们做为备份来使用是非常合适的,当然如果一次有多个表需要被 dump,就会针对每个表都会生成两个相对应的文件。
三、逻辑备份恢复方法
仅仅有了备份还是不够啊,我们得知道如何去使用这些备份,现在我们就看看上面所做的逻辑备份的恢复方法:
由于所有的备份数据都是以我们最初数据库结构的设计相关的形式所存储,所以逻辑备份的恢复也相对比较简单。当然,针对两种不同的逻辑备份形式,恢复方法也稍有区别。下面我们就分别针对这两种逻辑备份文件的恢复方法做一个简单的介绍。
①INSERT 语句文件的恢复
对于 INSERT 语句形式的备份文件的恢复是最简单的,我们仅仅只需要运行该备份文件中的所有(或者部分)SQL 命令即可。首先,如果需要做完全恢复,那么我们可以通过使用 “mysql < backup.sql”直接调用备份文件执行其中的所有命令,将数据完全恢复到备份时候的状态。如果已经使用 mysql 连接上了 MySQL,那么也可以通过在 mysql 中执行“source/path/backup.sql”或者“\. /path/backup.sql”来进行恢复。
②纯数据文本备份的恢复
如果是上面第二中形式的逻辑备份,恢复起来会稍微麻烦一点,需要一个表一个表通过相关命令来进行恢复,当然如果通过脚本来实现自动多表恢复也是比较方便的。恢复方法也有两个,一是通过 MySQL 的“LOAD DATA INFILE”命令来实现,另一种方法就是通过 MySQL 提供的使用工具 mysqlimport 来进行恢复。
逻辑备份能做什么?不能做什么?
在清楚了如何使用逻辑备份进行相应的恢复之后,我们需要知道我们可以利用这些逻辑备份做些什么。
- 通过逻辑备份,我们可以通过执行相关 SQL 或者命令将数据库中的相关数据完全恢复到备份时候所处的状态,而不影响不相关的数据;
- 通过全库的逻辑备份,我们可以在新的 MySQL 环境下完全重建出一个于备份时候完全一样的数据库,并且不受 MySQL 所处的平台类型限制;
- 通过特定条件的逻辑备份,我们可以将某些特定数据轻松迁移(或者同步)到其他的 MySQL 或者另外的数据库环境;
- 通过逻辑备份,我们可以仅仅恢复备份集中的部分数据而不需要全部恢复。
在知道了逻辑备份能做什么之后,我们必须还要清楚他不能做什么,这样我们自己才能清楚的知道这样的一个备份能否满足自己的预期,是否确实是自己想要的。
逻辑备份无法让数据恢复到备份时刻以外的任何一个时刻;
四、逻辑备份恢复测试
有时听到某某的数据库出现问题,而当其信心十足的准备拿之前所做好的数据库进行恢复的时候才发现自己的备份集不可用,或者并不能达到自己做备份时候所预期的恢复效果。遇到这种情景的时候,恐怕每个人都会郁闷至极的。数据库备份最重要最关键的一个用途就是当我们的数据库出现某些异常状况,需要对数据进行恢复的时候使用的。
作为一个维护人员,我们是绝对不应该出现此类低级错误的。那我们到底该如何避免此类问题呢?
只有一个办法,那就是周期性的进行模拟恢复测试,校验我们的备份集是否真的有效,是否确实能够按照我们的备份预期进行相应的恢复。
到这里可能有人会问,恢复测试又该如何做呢,我们总不能真的将线上环境的数据进行恢复啊?
是的,线上环境的数据确实不能被恢复,但是我们为什么不能在测试环境或者其他的地方做呢?
做恢复测试只是为了验证我们的备份是否有效,是否能达到我们的预期。
所以在做恢复测试之前我们一定要先清楚的知道我们所做的备份到底是为了应用于什么样的场景的。
就比如我们做了一个全库的逻辑备份,目的可能是为了当数据库出现逻辑或者物理异常的时候能够恢复整个数据库的数据到备份时刻,那么我们恶的恢复测试就只需要将整个逻辑备份进行全库恢复,看是否能够成功的重建一个完整的数据库。
至于恢复的数据是否和备份时刻一致,就只能依靠我们自己来人工判断比较。
此外我们可能还希望当某一个数据库对象,比如某个表出现问题之后能够尽快的恢复该表数据到备份时刻。那么我们就可以针对单个指定表进行抽样恢复测试。
下面我们就假想数据库主机崩溃,硬件损坏,造成数据库数据全部丢失,来做一次全库恢复的测试示例:
当我们的数据库出现硬件故障,数据全部丢失之后,我们必须尽快找到一台新的主机以顶替损坏的主机来恢复相应的服务。在恢复服务之前,我们首先需要重建损坏的数据库。假设我们已经拿到了一台新的主机,MySQL 软件也已经安装就位,相关设置也都已经调整好,就等着恢复数据库了。
我们需要取回离崩溃时间最近的一次全库逻辑备份文件,复制到准备的新主机上,启动已经安装好的 MySQL。
由于我们有两种逻辑备份格式,每种格式的恢复方法并不一样,所以这里将对两种格式的逻辑备份的恢复都进行示例。
①如果是 INSERT 语句的逻辑备份
a、准备好备份文件,copy 到某特定目录,如“/tmp”下;
b、通过执行如下命令执行备份集中的相关命令:
mysql -uusername -p < backup.sql
或者先通过 mysql 登录到数据库中,然后再执行如下命令:
root@localhost : (none) 09:59:40> source /tmp/backup.sql
c、再到数据库中检查相应的数据库对象,看是否已经齐全;
d、抽查几个表中的数据进行人工校验,并通知开启应用内部测试校验,当所有校验都通过之后,即可对外提供服务了。
当然上面所说的步骤都是在默认每一步都正常的前提下进行的,如果发现某一步有问题。假若在 b 步骤出现异常,无法继续进行下去,我们首先需要根据出现的错误来排查是否是我们恢复命令有错?是否我们的环境有问题等?等等。
如果我们确认是备份文件的问题,那么说明我们的这个备份是无效的,说明测试失败了。如果我们恢复过程很正常,但是在校验的时候发现缺少数据库对象,或者某些对象中的数据不正确,或者根本没有数据。同样说明我们的备份级无法满足预期,备份失败。
当然,如果我们是在实际工作的恢复过程中遇到类似情况的时候,如果还有更早的备份集,我们必须退一步使用更早的备份集做相同的恢复操作。虽然更早的备份集中的数据可能会有些失真,但是至少可以部分恢复,而不至于丢失所有数据。
②如果我们是备份的以特殊分隔符分隔的纯数据文本文件
a、第一步和 INSERT 备份文件没有区别,就是将最接近崩溃时刻的备份文件准备好;
b、通过特定工具或者命令将数据导入如到数据库中:
由于数据库结构创建脚本和纯文本数据备份文件分开存放,所以我们首先需要执行数据库结构创建脚本,然后再导入数据。结构创建脚本的方法和上面第一种备份的恢复测试中的 b 步骤完全一样。
有了数据库结构之后,我们就可以导入备份数据了,如下:
mysqlimport --user=name --password=pwd test --fields-enclosed-by=\" --fields-terminated-by=, /tmp/test_outfile.txt
或者
LOAD DATA INFILE '/tmp/test_outfile.txt' INTO TABLE test_outfile FIELDS TERMINATED BY '"' ENCLOSED BY ',';
后面的步骤就和备份文件为 INSERT 语句备份的恢复完全一样了,这里就不再累述。
以上就是MySQL 逻辑备份与恢复测试的相关总结的详细内容,更多关于MySQL 逻辑备份与恢复测试的资料请关注我们其它相关文章!