如果没有提前做好备份规划,也许以后会发现已经错失了一些最佳的选择。例如,在服务器已经配置好以后,才想起应该使用LVM,以便可以获取文件系统的快照——但这时已经太迟了。在为备份配置系统参数时,可能没有注意到某些系统配置对性能有着重要影响。如果没有计划做定期的恢复演练,当真的需要恢复时,就会发现并没有那么顺利。
像这样的话题已经在许多书中涉及,例如W.CurtisPreston的Backup&Recouery(O’Reilly)。
另外两个让人困惑的词是还原和恢复。在本章中它们有其特定的含义。还原意味着从备份文件中获取数据,可以加载这些文件到MySQL里,也可以将这些文件放置到MySQL期望的路径中。恢复一般意味着当某些异常发生后对一个系统或其部分的拯救。包括从备份中还原数据,以及使服务器完全恢复功能的所有必要步骤,例如重启MySQL、改变配置和预热服务器的缓存等。
在很多人的概念中,恢复仅意味着修复崩溃后损坏的表。这与恢复一个完整的服务器是不同的。存储引擎的崩溃恢复要求数据和日志文件一致。要确保数据文件中只包含已经提交的事务所做的修改,恢复操作会将日志中还没有应用到数据文件的事务重新执行。这也许是恢复过程的一部分,甚至是备份的一部分。然而,这和一个意外的DROPTABLE事故后需要做的事是不一样的。
下面是备份非常重要的几个理由:
灾难恢复
灾难恢复是下列场景下需要做的事情:硬件故障、一个不经意的Bug导致数据损坏,或者服务器及其数据由于某些原因不可获取或无法使用等。你需要准备好应付很多问题:某人偶然连错服务器执行了一个ALTERTABLED的操作,机房大楼被烧毁,恶意的黑客攻击或MySQL的Bug等。尽管遭受任何一个特殊的灾难的几率都非常低,但所有的风险叠加在一起就很有可能会碰到。
人们改变想法
不必惊讶,很多人经常会在删除某些数据后又想要恢复这些数据。
审计
测试
一个最简单的基于实际数据来测试的方法是,定期用最新的生产环境数据更新测试服务器。如果使用备份的方案就非常简单:只要把备份文件还原到测试服务器上即可。
检查你的假设。例如,你认为共享虚拟主机供应商会提供MySQL服务器的备份?许多主机供应商根本不备份MySQL服务器,另外一些也仅仅在服务器运行时复制文件,这可能会创建一个损坏的没有用处的备份。
如果一切正常,那么永远也不需要考虑恢复。但是,一旦需要恢复,只有世界上最好的备份系统是没用的,还需要一个强大的恢复系统。
不幸的是,让备份系统平滑工作比构造良好的恢复过程和工具更容易。原因如下:
建议将上面这些问题的答案明确地用文档记录下来,同时还应该明确备份策略,以及备份过程。
讨论:备份误区1:“复制就是备份”
这是我们经常碰到的一个误区。复制不是备份,当然使用RAID阵列也不是备份。为什么这么说?可以考虑一下,如果意外地在生产库上执行了DROPDATABASE,它们是否可以帮你恢复所有的数据?RAID和复制连这个简单的测试都没法通过。它们不是备份,也不是备份的替代品。只有备份才能满足备份的要求。
备份MySQL比看起来难。最基本的,备份仅是数据的一个副本,但是受限于应用程序的要求、MySQL的存储引擎架构,以及系统配置等因素,会让复制一份数据都变得很困难。
在深入所有选项细节之前,先来看一下我们的建议:
如果可能,关闭MySQL做备份是最简单最安全的,也是所有获取一致性副本的方法中最好的,而且损坏或不一致的风险最小。如果关闭了MySQL,就根本不用关心InnoDB缓冲池中的脏页或其他缓存。也不需要担心数据在尝试备份的过程被修改,并且因为服务器不对应用提供访问,所以可以更快地完成备份。
避免使用FLUSHTABLESWITHREADLOCK的最好的方法是只使用InnoDB表。在权限和其他系统信息表中使用MyISAM表是不可避免的,但是如果数据改变量很少(正常情况下),你可以只刷新和锁住这些表,这不会有什么问题。
复制备份到目的地需要多久?
备份负载
在复制备份到目的地时对服务器性能的影响有多少?
把备份镜像从存储位置复制到MySQL服务器,重放二进制日志等,需要多久?
同样,也可以利用负载的特性来设计备份。例如,如果服务器在晚上的8小时内仅仅有50%的负载,那么可以尝试规划备份,使得服务器的负载低于50%且仍能在8小时内完成。可以采用许多方法来完成这个目标,例如,可以用ionice和nice来提髙复制或压缩操作的优先级,使用不同的压缩等级,或在备份服务器上压缩而不是在MySQL服务器上。甚至可以利用lzo或pigz以获取更快的压缩。也可以使用0_DIRECT或fadvise()在复制操作时绕开操作系统的缓存,以避免污染服务器的缓存。像PerccmaXtraBackup和MySQLEnterpriseBackup这样的工具都有限流选项,可在使用pv时加—rate-limit选项来限制备份脚本的吞吐量。
有两种主要的方法来备份MySQL数据:逻辑备份(也叫“导出”)和直接复制原始文件的物理备份。逻辑备份将数据包含在一种MySQL能够解析的格式中,要么是SQL,要么是以某个符号分隔的文本。原始文件是指存在于硬盘上的文件。
任何一种备份都有其优点和缺点。
逻辑备份
逻辑备份有如下优点:
尽管如此,逻辑备份也有它的缺点:
物理备份
物理备份有如下好处:
物理备份也有其缺点,比如:
除非经过测试,不要假定备份(特别是物理备份)是正常的。对InnoDB来说,这意味着需要启动一个MySQL实例,执行InnoDB恢复操作,然后运行CHECKTABLES。也可以跳过这一操作,仅对文件运行innochecksum,但我们不建议这样做。对于MyISAM,可以运行CHECKTABLES,或者使用mysqlcheck。使用mysqlcheck可以对所有的表执行CHECKTABLES操作。
建议混合使用物理和逻辑两种方式来做备份:先使用物理复制,以此数据启动MySQL服务器实例并运行mysqlcheck。然后,周期性地使用mysqldump执行逻辑备份。这样做可以获得两种方法的优点,不会使生产服务器在导出时有过度负担。如果能够方便地利用文件系统的快照,也可以生成一个快照,将该快照复制到另外一个服务器上并释放,然后测试原始文件,再执行逻辑备份。
恢复的需求决定需要备份什么。最简单的策略是只备份数据和表定义,但这是一个最低的要求。在生产环境中恢复数据库一般需要更多的工作。下面是MySQL备份需要考虑的几点。
非显著数据
不要忘记那些容易被忽略的数据:例如,二进制日志和InnoDB事务日志。
代码
现代的MySQL服务器可以存储许多代码,例如触发器和存储过程。如果备份了mysql数据库,那么大部分这类代码也备份了,但如果需要还原单个业务数据库会比较麻烦,因为这个数据库中的部分“数据”,例如存储过程,实际是存放在mysql数据库中的。
复制配置
服务器配置
假设要从一个实际的灾难中恢复,比如说,地震过后在一个新数据中心中构建服务器,如果备份中包含服务器配置,你一定会喜出望外。
选定的操作系统文件
对于服务器配置来说,备份中对生产服务器至关重要的任何外部配置,都十分重要。在UNIX服务器上,这可能包括cron任务、用户和组的配置、管理脚本,以及sudo似而规则。
这些建议在许多场景下会被当作“备份一切”。然而,如果有大量的数据,这样做的开销将非常髙,如何做备份,需要更加明智的考虑。特别是,可能需要在不同备份中备份不同的数据。例如,可以单独地备份数据、二进制日志和操作系统及系统配置。
增量备份和差异备份
当数据量很庞大时,一个常见的策略是做定期的增量或差异备份。它们之间的区别有点容易让人混淆,所以先来澄清这两个术语:差异备份是对自上次全备份后所有改变的部分而做的备份,而增量备份则是自从任意类型的上次备份后所有修改做的备份。
例如,假如在每周日做一个全备份。在周一,对自周日以来所有的改变做一个差异备份。
在周二,就有两个选择:备份自周日以来所有的改变(差异),或只备份自从周一备份后所有的改变(增量)。
不要因为会用髙级备份技术而自负,解决方案越复杂,可能面临的风险也越大。要注意分析隐藏的危险,如果多次迭代备份紧密地耦合在一起,则只要其中的一次迭代备份有损坏,就可能会导致所有的备份都无效。
下面有一些建议:
例如,如果有包含不同语种各个月的名称列表,或者州或区域的简写之类的“查找”表,将它们放在一个单独的数据库中是个好主意,这就不需要每次都备份这些表。
不管如何,还是需要经常做全备份——建议至少一周一次。你肯定不会希望使用一个月的所有增量备份来进行恢复。即使一周也还是有很多的工作和风险的。
MySQL对存储引擎的选择会导致备份明显更复杂。问题是,对于给定的存储引擎,如何得到一致的备份。
实际上有两类一致性需要考虑:数据一致性和文件一致性。
数据一致性
也可以用mysqldump来获得InnoDB表的一致性逻辑备份,采用—single-transaction选项可以按照我们所描述的那样工作。但是,这可能会导致一个非常长的事务,在某些负载下会导致开销大到不可接受。
文件一致性
对于非事务性存储引擎,例如MyISAM,可能的选项是锁住并刷新表。这意味着要么用LOCKTABLES和FLUSHTABLES结合的方法以使服务器将内存中的变更刷到磁盘上,要么用FLUSHTABLESWITHREADLOCK。一旦刷新完成,就可以安全地复制MyISAM的原始文件。
在复制数据文件到其他地方后,就可以释放锁以使MySQL服务器再次正常运行。
复制
从备库中备份最大的好处是可以不干扰主库,避免在主库上增加额外的负载。这是一个建立备库的好理由,即使不需要用它做负载均衡或髙可用。如果钱是个问题,也可以把备份用的备库用于其他用途,例如报表服务——只要不对其做写操作,以确保备份时不会修改数据。备库不必只用于备份的目的;只需要在下次备份时能及时跟上主库,即使有时因作为其他用途导致复制延时也没有关系。
提示:备库可能与主库数据不完全一样。许多人认为备库是主库完全一样的副本,但以我们的经验,主库与备库数据不匹配是很常见的,并且MySQL没有方法检测这个问题。检测这个问题的唯一方法是使用PerconaToolkit中的pt-table-checksum之类的工具。拥有一个复制的备库可能在诸如主库的硬盘烧坏时提供帮助,但却不能提供保证。复制不是备份。
MySQL复制也使用二进制日志。因此备份和恢复的策略经常和复制配置相互影响。
二进制日志很”特别”。如果丢失了数据,你一定不希望同时丢失了二进制日志。为了让这种情况发生的几率减少到最小,可以在不同的卷上保存数据和二进制日志。即使在LVM下生成二进制日志的快照,也是可以的。为了额外的安全起见,可以将它们保存在SAN上,或用DRBD复制到另外一个设备上。
经常备份二进制日志是个好主意。如果不能承受丢失超过30分钟数据的价值,至少要每30分钟就备份一次。也可以用一个配置--log_slave_update的只读备库,这样可以获得额外的安全性。备虑上日志位置与主库不匹配,但找到恢复时正确的位置并不难。最后,MySQL5.6版本的mysqlbinlog有一个非常方便的特性,可连接到服务器上来实时对二进制日志做镜像,比起运行一个mysqld实例要简单和轻便。它与老版本是向后兼容的。
1#at2772#07103010:47:21serverid3end_log_pos369Querythread_id=13exec_time=0error_code=03SETTIMESTAMP=1193755641/*!*/;4insertintotest(a)values(2)/*!*/;第一行包含日志文件内的偏移字节值(本例中为277)。
第二行包含如下几项。
提示:如果使用的是MySQL5.1中基于行的日志,事件将不再是SQL。而是可读性较差的由语句对表所做变更的“镜像”。
一个常见的设置是使用expire_log_days变量来告诉MySQL定期清理日志。这个变量直到MySQL4.1才引入;在此之前的版本,必须手动清理二进制日志。因此,你可能看到一些用类似下面的cron项来删除老的二进制日志的建议。
00***/usr/bin/find/var/log/mysql-mtime+N-name"mysql-bin.[0-9]*"|xargsrm尽管这是在MySQL4.1之前清除日志的唯一办法,但在新版本中不要这么做!用rm删除日志会导致状态文件与磁盘上的文件不一致,有些语句,例如SHOWMASTERLOGS可能会受到影响而悄然失败。手动修改mysql-bin.index文件也不会修复这个问题。应该用类似下面的cron命令。
大多数时候,生成备份有好的也有差的方法——有时候显而易见的方法并不是好方法。一个有用的技巧是应该最大化利用网络、磁盘和CPU的能力以尽可能快地完成备份。这是一个需要不断去平衡的事情,必须通过实验以找到“最佳平衡点”。
对于逻辑备份,首先要意识到的是它们并不是以同样方式创建的。实际上有两种类型的逻辑备份:SQL导出和符号分隔文件。
SQL导出
SQL导出是很多人所熟悉的,因为它们是mysqldump默认的方式。例如,用默认选项导出一个小表将产生如下(有删减)输出。
$mysqldumptestt1--[Versionandhostcomments]/*!40101SET@OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT*/;--[Moreversion-specificcommentstosaveoptionsforrestore]----Tablestructurefortable`t1`--DROPTABLEIFEXISTS`t1`;CREATETABLE`t1`(`a`int(11)NOTNULL,PRIMARYKEY(`a`))ENGINE=MyISAMDEFAULTCHARSET=latin1;----Dumpingdatafortable`t1`--LOCKTABLES`t1`WRITE;/*!40000ALTERTABLE`t1`DISABLEKEYS*/;INSERTINTO`t1`VALUES(1);/*!40000ALTERTABLE`t1`ENABLEKEYS*/;UNLOCKTABLES;/*!40103SETTIME_ZONE=@OLD_TIME_ZONE*/;/*!40101SETSQL_MODE=@OLD_SQL_MODE*/;--[Moreoptionrestoration]导出文件包含表结构和数据,均以有效的SQL命令形式写出。文件以设置MySQL各种选项的注释开始。这些要么是为了使恢复工作更高效,要么是因为兼容性和正确性。接下来可以看到表结构,然后是数据。最后,脚本重置在导出开始时变更的选项。
导出的输出对于还原操作来说是可执行的。这很方便,但mysqldump默认选项对于生成一个巨大的备份却不是太适合。
mysqldump不是生成SQL逻辑备份的唯一工具。例如,也可以用mydumper或phpMyAdmin工具来创建。我们想指出的是,不是某一个特定的工具有多大的问题,而是做SQL逻辑备份本身就有一些缺点。下面是主要问题点:
Schema和数据存储在一起
如果想从单个文件恢复这样做会非常方便,但如果只想恢复一个表或只想恢复数据就很困难了。可以通过导出两次的方法来减缓这个问题--次只导出数据,另外一次只导出Schema-但还是会有下一个麻烦。
巨大的语句
服务器分析和执行SQL语句的工作量非常大,所以加载数据时会非常慢。
单个巨大的文件
大部分文本编辑器不能编辑巨大的或者包含非常长的行的文件。尽管有时候可以用命令行的流编辑器——例如sed或grep--来抽出需要的数据,但保持文件小型化仍然是更合适的。
逻辑备份的成本很高
比起逻辑备份这种从存储引擎中读取数据然后通过客户端/服务器协议发送结果集的方式,还有其他更高效的方法。
这些限制意味着SQL导出在表变大时可能变得不可用。不过,还有另外一个选择:导出数据到符号分隔的文件中。
符号分隔文件备份
可以使用SQL命令SELECTINTOOUTFILE以符号分隔文件格式创建数据的逻辑备份。(可以用mysqldump的--tab选项导出到符号分隔文件中)。符号分隔文件包含以ASCII展示的原始数据,没有SQL、注释和列名。下面是一个导出为逗号分隔值(CVS)格式的例子,对于表格形式的数据来说这是一个很好的通用格式。
mysql>SELECT*INTOOUTFILE'/tmp/t1.txt'->FIELDSTERMINATEDBY','OPTIONALLYENCLOSEDBY'"'->LINESTERMINATEDBY'\n'->FROMtest.t1;比起SQL导出文件,符号分隔文件要更紧凑且更易于用命令行工具操作,这种方法最大的优点是备份和还原速度更快。可以和导出时使用一样的选项,用LOADDATAINFILE方法加载数据到表中:
mysql>LOADDATAINFILE'/tmp/t1.txt'->INTOTABLEtest.t1->FIELDSTERMINATEDBY','OPTIONALLYENCLOSEDBY'"'->LINESTERMINATEDBY'\n';下面这个非正式的测试演示了SQL文件和符号分隔文件在备份和还原上的速度差异。在测试中,我们对生产数据做了些修改。导出的表看起来像下面这样:
但是SELECTINTOOUTFILE方法也有一些限制。
文件系统快照是一种非常好的在线备份方法。支持快照的文件系统能够瞬间创建用来备份的内容一致的镜像。支持快照的文件系统和设备包括FreeBSD的文件系统、ZFS文件系统、GNU/Linux的逻辑卷管理(LVM),以及许多的SAN系统和文件存储解决方案,例如NetApp存储。
LVM快照是如何工作的
LVM使用写时复制(copy-on-write)的技术来创建快照-例如,对整个卷的某个瞬间的逻辑副本。这与数据库中的MVCC有点像,不同的是它只保留一个老的数据版本。
当原始卷中某些数据有变化时,LVM在任何变更写入之前,会复制受影响的块到快照预留的区域中。LVM不保留数据的多个“老版本”,因此对原始卷中变更块的额外写入并不需要对快照做其他更多的工作。换句话说,对每个块只有第一次写入才会导致写时复制到预留的区域。
快照会在/dev目录下创建一个新的逻辑卷,可以像挂载其他设备一样挂载它。
理论上讲,这种技术可以对一个非常大的卷做快照,而只需要非常少的物理存储空间。但是,必须设置足够的空间,保证在快照打开时,能够保存所有期望在原始卷上更新的块。如果不预留足够的写时复制空间,当快照用完所有的空间后,设备就会变得不可用。这个影响就像拔出一个外部设备:任何从设备上读的备份工作都会因I/O错误而失败。
先决条件和配置
创建一个快照的消耗几乎微不足道,但还是需要确保系统配置可以让你获取在备份瞬间的所有需要的文件的一致性副本。首先,确保系统满足下面这些条件。
LVM有卷组的概念,它包含一个或多个逻辑卷。可以按照如下的方式査看系统中的卷组:
#vgsVG#PV#LV#SNAttrVSizeVFreevg140wz--n-534.18G249.18G输出显示了一个分布在一个物理卷上的卷组,它有四个逻辑卷,大概有250GB空间空闲。如果需要,可用vgdisplay命令产生更详细的输出。现在让我们看一下系统上的逻辑卷:
#lvsLVVGAttrLSizeOriginSnap%MoveLogCopy%homevg-wi-ao40.00Gmysqlvg-wi-ao225.00Gtmpvg-wi-ao10.00Gvarvg-wi-ao10.00G输出显示mysql卷有225GB的空间。设备名是/dev/vg/mysql。这仅是个名字,尽管看起来像一个文件系统路径。更加让人困惑的是,还有个符号链接从相同名字的文件链到叫/dev/mapper/vg-mysql的设备节点,用ls和mount命令可以观察到。
#ls-l/dev/vg/mysqllrwxrwxrwx1rootroot20Sep1913:08/dev/vg/mysql->/dev/mapper/vg-mysql#mount|grepmysql/dev/mapper/vg-mysqlon/var/lib/mysql有了这个信息,就可以创建文件系统快照了。
创建、挂载和删除LVM快照
一条命令就能创建快照。只需要决定快照存放的位置和分配给写时复制的空间大小即可。不要纠结于是否使用比想象中的需求更多的空间。LVM不会马上使用完所有指定的空间,只是为后续使用预留而已。因此多预留一点空间并没有坏处,除非你必须同时为其他快照预留空间。
让我们来练习创建一个快照。我们给它16GB的写时复制空间,名字为backup_mysql。
#lvcreate--size16G--snapshot--namebackup_mysql/dev/vg/mysqlLogicalvolume"backup_mysql"created现在让我们看看新创建的卷的状态。
#lvsLVVGAttrLSizeOriginSnap%MoveLogCopy%backup_mysqlvgswi-a-16.00Gmysql0.01homevg-wi-ao40.00Gmysqlvgowi-ao225.00Gtmpvg-wi-ao10.00Gvarvg-wi-ao10.00G可以注意到,快照的属性与原设备不同,而且该输出还显示了一点额外的信息:原始卷组和分配了16GB的写时复制空间目前已经使用了多少。备份时对此进行监控是个非常好的主意,可以知道是否会因为设备写满而备份失败。可以交互地监控设备的状态,或使用诸如Nagios这样的监控系统。
#watch'lvs|grepbackup'从前面mount的输出可以看到,mysql卷包含一个文件系统。这意味着快照也同样如此,可以像其他文件系统一样挂载。
#mkdir/tmp/backup#mount/dev/mapper/vg-backup_mysql/tmp/backup#ls-l/tmp/backup/mysqltotal5336-rw-r-----1mysqlmysql0Nov172006columns_priv.MYD-rw-r-----1mysqlmysql1024Mar242007columns_priv.MYI-rw-r-----1mysqlmysql8820Mar242007columns_priv.frm-rw-r-----1mysqlmysql10512Jul1210:26db.MYD-rw-r-----1mysqlmysql4096Jul1210:29db.MYI-rw-r-----1mysqlmysql9494Mar242007db.frm...omitted...这里只是为了练习,因此我们卸载这个快照并用lvremove命令将其删除。
#umount/tmp/backup#rmdir/tmp/backup#lvremove--force/dev/vg/backup_mysqlLogicalvolume"backup_mysql"successfullyremoved
用于在线备份的LVM快照
现在已经知道如何创建、加载和删除快照,可以使用它们来进行备份了。首先看一下如何在不停止MySQL服务的情况下备份InnoDB数据库,这里需要使用一个全局的读锁。连接MySQL服务器并使用一个全局读锁将表刷到磁盘上,然后获取二进制日志的位置:
mysql>FLUSHTABLESWITHREADLOCK;SHOWMASTERSTATUS;记录SHOWMASTERSTATUS的输出,确保到MySQL的连接处于打开状态,以使读锁不被释放。然后获取LVM的快照并立刻释放该读锁,可以使用UNLOCKTABLES或者直接关闭连接来释放锁。最后,加载快照并复制文件到备份位置。
讨论:文件系统快照和InnoDB
即使锁住所有的表,InnoDB的后台线程仍会继续工作,因此,即使在创建快照时,仍然可以往文件中写入。并且,由于InnoDB没有执行关闭操作,如果服务器意外断电,快照中InnoDB的文件会和服务器意外掉电后文件的遭遇一样。
这不是什么问题,因为InnoDB是个ACID系统。任何时刻(例如快照时),每个提交的事务要么在InnoDB数据文件中要么在日志文件中。在还原快照后启动MySQL时,InnoDB将运行恢复进程,就像服务器断过电一样。它会查找事务曰志中任何提交但没有应用到数据文件中的事务然后应用,因此不会丢失任何事务。这正是要强制InnoDB数据文件和日志文件在一起快照的原因。
这也是在备份后需要测试的原因。启动一个MySQL实例,把它指向一个新备份,让InnoDB执行崩溃恢复过程,然后检测所有的表。通过这种方法,就不会备份损坏了却还不知道(文件可能由于任何原因损坏)。这么做的另外一个好处是,未来需要从备份中还原时会更快,因为已经在备份上运行过一遍恢复程序了。
甚至还可以在将快照复制到备份目的地之前,直接在快照上做上面的操作,但增加一点点额外开销。所以需要确保这是计划内的操作。(后面会有更多说明。)
使用LVM快照无锁InnoDB备份
无锁备份只有一点不同。区别是不需要执行FLUSHTABLESWITHREADLOCK。这意味着不能保证MyISAM文件在磁盘上一致,如果只使用InnoDB,这就不是问题。mysql系统数据库中依然有部分MyISAM表,但如果是典型的工作负载,在快照时这些表不太可能发生改变。
mysql>LOCKTABLESmysql.userREAD,mysql.dbREAD,...;mysql>FLUSHTABLESmysql.user,mysql.db,...;由于没有用全局读锁,因此不会从SHOWMASTERSTATUS中获取到任何有用的信息。尽管如此,基于快照启动MySQL(来验证备份的完整性)时,也将会在日志文件中看到像下面的内容。
使用快照进行无锁备份的方法在MySQL5.0或更新版本中有变动。这些MySQL版本使用XA来协调InnoDB和二进制日志。如果还原到一个与备份时server_id不同的服务器,服务器在准备事务阶段可能发现这是从另外一个与自己有不同ID的服务器来的。在这种情况下,服务器会变得困惑,恢复事务时可能会卡在PREPARED状态。这种情况很少发生,但是存在可能性。这也是只有经过验证才可以说备份成功的原因。有些备份也许是不能恢复的。
如果是在备库上获取快照,InnoDB恢复时还会打印如下几行日志。
InnoDB:InaMySQLreplicathelastmasterbinlogfileInnoDB:position0115,filenamemysql-bin.001717输出显示了InnoDB已经恢复的基于主库的二进制日志位置(相对于备库二进制日志位置),这对于基于备库备份或基于其他备库克隆备库来说非常有用。
规划LVM备份
LVM快照备份也是有开销的。服务器写到原始卷的越多,引发的额外开销也越多。当服务器随机修改许多不同块时,磁头需要自写时复制空间来来回回寻址,并且将数据的老版本写到写时复制空间。从快照中读取也有开销,因为LVM需要从原始卷中读取大部分数据。只有快照创建后修改过的数据从写时复制空间读取;因此,逻辑顺序读取快照数据实际上也可能导致磁头来回移动。
所以应该为此规划好快照。快照实际上会导致原始卷和快照都比正常的读/写性能要差——如果使用过多的写时复制空间,性能可能会差很多。这会降低MySQL服务器和复制文件进行备份的性能。我们做了基准测试,发现LVM快照的开销要远髙于它本应该有的——我们发现性能最多可能会慢5倍,具体取决于负载和文件系统。在规划备份时要记得这一点。
LVM规划中另外一个重要的事情是,为快照分配足够多的空间。我们一般采取下面的方法。
假设评估出有一半的写会导致往快照的写时复制空间的写操作,并且服务器支持10MB/s的写入。如果需要一个小时(3600s)将快照复制到另外一个服务器上,那么将需要1/2x10MBx3600即18GB的快照空间。考虑到容错,还要增加一些额外的空间。
讨论:备份误区2:“快照就是备份”
快照的其他用途和替代方案
文件系统快照不是取得数据瞬间副本的唯一方法。另外一个选择是RAID分裂:举个例子,如果有一个三磁盘的软RAID镜像,就可以从该RAID组中移出来一个磁盘单独加载。这样做没有写时复制的代价,并且需要时将此类“快照”提升为主副本的操作也很简单。不错,如果要将磁盘加回到RAID集合,就必须重新进行同步。当然,天下没有免费的午餐。
如何恢复数据取决于是怎么备份的。可能需要以下部分或全部步骤。
我们在接下来的章节中将演示这些步骤的具体操作。我们也会对本节及本章后面几节提及的一些特殊的备份方法和工具做一些解释。
在恢复过程中,保证MySQL除了恢复进程外不接受其他访问,这一点往往比较重要。我们喜欢以—skip-networking和—socket=/tmp/mysql_recover.sock选项来启动MySQL,以确保它对于已经存在的应用不可访问,直到我们检测完并重新提供服务。这对于按块加载的逻辑备份的恢复来说尤其重要。
恢复物理备份往往非常直接——换言之,没有太多的选项。这可能是好事,也可能是坏事,具体取决于恢复的需求。一般过程是简单地复制文件到正确位置。
是否需要关闭MySQL取决于存储引擎。MyISAM的文件一般相互独立,即使服务器正在运行,简单地复制每个表的.frm、MYI和.MYD文件也可以正常操作。一旦有任何对此表的查询,或者其他会导致服务器访问此表的操作(例如,执行SHOWTABLES),MySQL都会立刻找到这些表。如果在复制这些文件时表是打开的,可能会有麻烦,因此操作前要么删除或重命名该表,要么使用LOCKTABLES和FLUSHTABLES来关闭它。
InnoDB的情况有所不同。如果用传统的InnoDB的步骤来还原,即所有表都存储在单个表空间,就必须关闭MySQL,复制或移动文件到正确位置上,然后重启。同样也需要InnoDB的事务日志文件与表空间文件匹配。如果文件不匹配——例如,替换了表空间文件但没有替换事务日志文件——InnoDB将会拒绝启动。这也是将日志和数据文件一起备份非常关键的一个原因。
如果使用InnoDBfile-per-table特性(innodb_file_per_table),InnoDB会将每个表的数据和索引存储于一个.ibd文件中,这就像MyISAM的.MYI和.MYD文件合在一起。可以在服务器运行时通过复制这些文件来备份和还原单个表,但这并不像MyISAM中那样简单。这些文件并不完全独立于InnoDB。每个.ibd文件都有一些内部的信息,保存着它与主(共享)表空间之间的关系。在还原这样的文件时,需要让InnoDB先“导入”这个文件。
这个过程有许多的限制,如果有需要可以阅读MySQL用户手册中关于每个表使用独立表空间中的部分。最大的限制是只能在当初备份的服务器上还原单个表。用这种配置来备份和还原多个表不是不可能,但可能比想象的要更棘手。
所有这些复杂度意味着还原物理备份会非常乏味,并且容易出错。一个好的值得倡导的规则是,恢复过程越难越复杂,也就越需要逻辑备份的保护。为了防止一些无法意料的情况或者某些无法使用物理备份的场景,准备好逻辑备份总是值得推荐的。
还原物理备份后启动MySQL
在启动正在恢复的MySQL服务器之前,还有些步骤要做。
首先,最重要且最容易忘记的事情,是在启动MySQL服务器之前检查服务器的配置,确保恢复的文件有正确的归属和权限。这些属性必须完全正确,否则MySQL可能无法启动。这些属性因系统的不同而不同,因此要仔细检查是否和之前做的记录吻合。一般都需要mysql用户和组拥有这些文件和目录,并且只有这个用户和组拥有可读/写权限。
建议观察MySQL启动时的错误日志。在UNIX类系统上,可以如下观察文件。
$tail-f/var/log/mysql/mysql.err注意错误日志的准确位置会有所不同。一旦开始监测文件,就可以启动MySQL服务器并监测错误。如果一切进展顺利,MySQL启动后就有一个恢复好的数据库服务器了。
观察错误日志对于新的MySQL版本更为重要。老版本在InnoDB有错时不会启动,但新版本不管怎样都会启动,而只是让InnoDB失效。即使服务器看起来启动没有任何问题,也应该对每个数据库运行SHOWTABLESTATUS来再次检测错误日志。
如果还原的是逻辑备份而不是物理备份,则与使用操作系统简单地复制文件到适当位置的方式不同,需要使用MySQL服务器本身来加载数据到表中。
加载SQL文件
如果有一个SQL导出文件,它将包含可执行的SQL。需要做的就是运行这个文件。假设备份Sakila示例数据库和Schema到单个文件,下面是用来还原的常用命令。
$mysql mysql>SETSQL_LOG_BIN=0;mysql>SOURCEsakila-backup.sql;mysql>SETSQL_LOG_BIN=1;需要注意的是,如果使用SOURCE,当定向文件到mysql时,默认情况下,发生一个错误不会导致一批语句退出。 如果备份做过压缩,那么不要分别解压缩和加载。应该在单个操作中完成解压缩和加载。这样做会快很多。 $gunzip-csakila-backup.sql.gz|mysql如果想用SOURCE命令加载一个压缩文件,可参考下节中关于命名管道的讨论。 如果只想恢复单个表(例如,actor表),要怎么做呢?如果数据没有分行但有schema信息,那么还原数据并不难。 $grep'INSERTINTO`actor`'sakila-backup.sql|mysqlsakila或者,如果文件是压缩过的,那么命令如下。 $sed-e'/./{H;$!d;}'-e'x;/CREATETABLE`actor`/!d;q'sakila-backup.sql我们得承认这条命令非常隐晦。如果必须以这种方式还原数据,那只能说明备份设计非常糟糕。如果有一点规划,可能就不会需要痛苦地去尝试弄清楚sed如何工作了。只需要备份每个表到各自的文或者可以更进一步,分别备份数据和Schema。 加载符号分隔文件 如果是通过SELECTINTOOUTFILE导出的符号分隔文件,可以使用LOADDATAINFILE通过相同的参数来加载。也可以用mysqlimport,这是LOADDATAINFILE的一个包装。这种方式依赖命名约定决定从哪里加载一个文件的数据。 我们希望你导出了Schema,而不仅是数据。如果是这样,那应该是一个SQL导出,就可以使用上一节中描述的技术来加载。 使用LOADDATAINFILE有一个非常好的优化技巧。LOADDATAINFILE必须直接从文本文件中读取,因此,如果是压缩文件很多人会在加载前先解压缩,这是非常慢的磁盘密集型的操作。然而,在支持FIFO“命名管道”文件的系统如GNU/Linux上,对这种操作有个很好的方法。首先,创建一个命名管道并将解压缩数据流到它里面。 $mkfifo/tmp/backup/default/sakila/payment.fifo$chmod666/tmp/backup/default/sakila/payment.fifo$gunzip-c/tmp/backup/default/sakila/payment.txt.gz>/tmp/backup/default/sakila/payment.fifo注意我们使用了一个大于号字符(>)来重定向解压缩输出到payment.fifo文件中——而不是在不同程序之间创建匿名管道的管道符号。 管道会等待,直到其他程序打开它并从另外一端读取数据。简单一点说,MySQL服务器可以从管道中读取解压缩后的数据,就像其他文件一样。如果可能,不要忘记禁掉二进制日志。 mysql>SETSQL_LOG_BIN=0;--Optional->LOADDATAINFILE'/tmp/backup/default/sakila/payment.fifo'->INTOTABLEsakila.payment;QueryOK,16049rowsaffected(2.29sec)Records:16049Deleted:0Skipped:0Warnings:0一旦MySQL加载完数据,gunzip就会退出,然后可以删除该命令管道。在MySQL命令行客户端使用SOURCE命令加载压缩的文件也可以使用此技术。PerconaToolkit中的pt-fifo-split程序还可以帮助分块加载大文件,而不是在单个大事务中操作,这样效率更髙。 主要的缺点是二进制日志重放可能会是一个很慢的过程。它大体上等同于复制。如果有一个备库,并且已经测量到SQL线程的利用率有多高,那么对重放二进制日志会有多快就会心里有数了。例如,如果SQL线程约有50%被利用,则恢复一周二进制日志的工作可能在三到四天内完成。 一个典型场景是对有害的语句的结果做回滚操作,例如DROPTABLE。让我们看一个简化的例子,看只有MyISAM表的情况下该如何做。假如是在半夜,备份任务在运行与下面所列相当的语句,复制数据库到同一服务器上的其他地方。 mysql>USEsakila;mysql>DROPTABLEsakila.payment;为了便于说明,我们先假设可以单独地恢复这个数据库(即此库中的表不涉及跨库查询)。再假设是直到后来出问题才意识到这个有问题的语句。目标是恢复数据库中除了有问题的语句之外所有发生的事务。也就是说,其他表已经做的所有修改都必须保持,包括有问题的语句运行之后的修改。 这并不是很难做到。首先,停掉MySQL以阻止更多的修改,然后从备份中仅恢复sakila数据库。 server1#/etc/init.d/mysqlstopserver1#mv/var/lib/mysql/sakila/var/lib/mysql/sakila.tmpserver1#cp-a/backup/sakila/var/lib/mysql再到运行的服务器的中添加如下配置以禁止正常的连接。 skip-networkingsocket=/tmp/mysql_recover.sock现在可以安全地启动服务器了。 server1#/etc/init.d/mysqlstart下一个任务是从二进制日志中分出需要重放和忽略的语句。事发时,自半夜的备份以来,服务器只创建了一个二进制日志。我们可以用grep来检査二进制日志文件以找到问题语句。 server1#mysqlbinlog--database=sakila/var/log/mysql/mysql-bin.000215|grep-B3-i'droptablesakila.payment'#at352#07091916:11:23serverid1end_log_pos429Querythread_id=16exec_time=0error_code=0SETTIMESTAMP=1190232683/*!*/;DROPTABLEsakila.payment/*!*/;可以看到,我们想忽略的语句在日志文件中的352位置,下一个语句位置是429。可以用下面的命令重放日志直到352位置,然后从429继续。 server1#mysqlbinlog--database=sakila/var/log/mysql/mysql-bin.000215--stop-position=352|mysql-uroot-pserver1#mysqlbinlog--database=sakila/var/log/mysql/mysql-bin.000215--start-position=429|mysql-uroot-p接下来要做的是检测数据以确保没有问题,然后关闭服务器并撤消对my.cnf的改变,最后重启服务器。 用于快速恢复的延时复制 恢复的过程与本章前几节描述的有点不一样,但思路是相同的。停止备库,用STARTSLAVEUNTIL来重放事件直到要执行问题语句。接着,执行SETGLOBALSQL_SLAVE_SKIP_C0UNTER=1来跳过问题语句。如果想跳过多个事件,可以设置一个大于1的值(或简单地使用CHANGEMASTERTO来前移备库在日志中的位置)。 即使没有延时的备库来加速恢复,普通的备库也有好处,至少会把主库的二进制日志复制到另外的机器上。如果主库的磁盘坏了,备库上的中继日志可能就是唯一能够获取到的最接近主库二进制日志的东西了。 使用日志服务器进行恢复 还有另外一种使用复制来做恢复的方法:设置日志服务器。我们感觉复制比mysqlbinlog更可靠,mysqlbinlog可能会有一些导致异常行为的奇怪的Bug和不常见的情况。使用日志服务器进行恢复比mysqlbinlog更灵活更简单,不仅因为STARTSLAVEUNTIL选项,还因为那些可以采用的复制规则(例如repUcate-do-table)。使用日志服务器,相对其他的方式来说,可以做到更复杂的过滤。 例如,使用日志服务器可以轻松地恢复单个表。而用mysqlbinlog和命令行工具则要困难得多——事实上,这样做太复杂了,所以我们一般不建议进行尝试。 假设粗心的开发人员像前面的例子一样删除了同样的表,现在想恢复此误操作,但又不想让整个服务器退到昨晚的备份。下面是利用日志服务器进行恢复的步骤: 1.将需要恢复的服务器叫作server1. 2.在另外一台叫做server2的服务器上恢复昨晚的备份。在这台服务器上运行恢复进程,以免在恢复时犯错而导致事情更糟。 3.设置日志服务器来接收server1的二进制日志(复制日志到另外一个服务器并设置日志服务器是个好想法,但是要格外注意。) 4.改变server2的配置文件,增加如下内容。 replicate-do-table=sakila.payment5.重启server2,然后用CHANGEMASTERTO来让它成为日志服务器的备库。配置它从昨晚备份的二进制日志坐标读取。这时候切记不要运行STARTSLAVE。 6.检测server2上的SHOWSLAVESTATUS的输出,验证一切正常。要三思而行! 7.找到二进制日志中问题语句的位置,在server2上执行STARTSLAVEUNTIL来重放事件直到该位置。 9.将所需表从server2复制到server1。 只有没有任何多表的UPDATE、DELETE或INSERT语句操作这个表时,上述流程才是可行的。任何这样的多表操作语句在被记录的时候,可能是基于多个数据库的状态,而不仅仅是当前要恢复的这个数据库,所以这样恢复出来的数据可能和原始的有所不同。(只有在使用基于语句的二进制日志时才会有这个问题;如果使用的是基于行的日志,重放过程不会碰到这个错误。) InnoDB在每次启动时都会检测数据和日志文件,以确认是否需要执行恢复过程。而且,InnoDB的恢复过程与我们在本章之前谈论的不是一回事。它并不是恢复备份的数据;而是根据日志文件将事务应用到数据文件,将未提交的变更从数据文件中回滚。 大部分情况下InnoDB可以很好地解决问题。除非MySQL有Bug或硬件有问题,否则不需要做任何非常规的事情,哪怕是服务器意外断电。InnoDB会在启动时执行正常的恢复,然后就一切正常了。在日志文件中,可以看到如下信息。 InnoDB损坏的原因 InnoDB非常健壮且可靠,并且有许多的内建安全检测来防止、检测和修复损坏的数据一一比其他MySQL存储引擎要强很多。然而,InnoDB并不能保护自己避免一切错误。 最起码,InnoDB依赖于无缓存的I/O调用和fsync()调用,直到数据完全地写入到物理介质上才会返回。如果硬件不能保证写入的持久化,InnoDB也就不能保证数据的持久,崩溃就有可能导致数据损坏。 很多InnoDB损坏问题都是与硬件有关的(例如,因电力问题或内存损坏而导致损坏页的写入)。然而,在我们的经验中,错误配置的硬件是更多的问题之源。常见的错误配置包括打开了不包含电池备份单元的RAID卡的回写缓存,或打开了硬盘驱动器本身的回写缓存。这些错误将会导致控制器或驱动器“撒谎”,在数据实际上只写入到回写缓存上而不是磁盘上时,却说fsync()已经完成。换句话说,硬件没有提供保持InnoDB数据安全的保证。 有时候机器默认就会这样配置,因为这样做可以得到更好的性能——对于某些场景确实很好,但是对事务数据服务来说却是个大问题。 如果在网络附加存储(NAS)上运行InnoDB,也可能会遇到损坏,因为对NAS设备来说完成fsync()只是意味着设备接收到了数据。如果InnoDB崩溃,数据是安全的,但如果是NAS设备崩溃就不一定了。 严重的损坏会使InnoDB或MySQL崩溃,而不那么严重的损坏则可能只是由于日志文件未真正同步到磁盘而丢掉了某些事务。 如何恢复损坏的InnoDB数据 InnoDB损坏有三种主要类型,它们对数据恢复有着不同程度的要求。 二级索引损坏 一般可以用OPTIMIZETABLE来修复损坏的二级索引,此外,也可以用SELECTINT00UTFILE,删除和重建表,然后LOADDATAINFILE的方法。(也可以将表改为使用MyISAM再改回来。)这些过程都是通过构建一个新表重建受影响的索引,来修复损坏的索引数据。 聚簇索引损坏 如果是聚簇索引损坏,也许只能使用innodb_force_recovery选项来导出表(关于这点后续会讲更多)。有时导出过程会让InnoDB崩溃;如果出现这样的情况,或许需要跳过导致崩溃的损坏页以导出其他的记录。聚簇索引的损坏比二级索引要更难修复,因为它会影响数据行本身,但在多数场合下仍然只需要修复受影响的表。 损坏系统结构 系统结构包括InnoDB事务日志、表空间的撤销日志(undolog)区域和数据字典。这种损坏可能需要做整个数据库的导出和还原,因为InnoDB内部绝大部分的工作都可能受到影响。 一般可以修复损坏的二级索引而不丢失数据。然而,另外两种情形经常会引起数据的丢失。如果已经有备份,那最好还是从备份中还原,而不是试着从损坏的文件里去提取数据。 如果必须从损坏的文件里提取数据,那一般过程是先尝试让InnoDB运行起来,然后使用SELECTINTO0UTFILE导出数据。如果服务器已经崩溃,并且每次启动InnoDB都会崩溃,那么可以配置InnoDB停止常规恢复和后台进程的运行。这样也许可以启动服务器,然后在缺少或不做完整性检査的情况下做逻辑备份。 innodb_force_recovery参数控制着InnoDB在启动和常规操作时要做哪一种类型的操作。通常情况下这个值是0,可以增大到6。MySQL使用手册里记录了每个数值究竟会产生什么行为;在此我们不会重复这段信息,但是要告诉你:在有点危险的前提下,可以把这个数值调高到4。使用这个设置时,若有数据页损坏,将会丢失一些数据;如果将数值设得更高,可能会从损坏的页里提取到坏掉的数据,或者增加执行SELECTINTO0UTFILES时崩溃的风险。换句话说,这个值直到4都对数据没有损害,但可能丧失修复问题的机会;而到5和6会更主动地修复问题,但损害数据的风险也会很大。 当把innodb_force_recovery设为大于0的某个值时,InnoDB基本上是只读的,但是仍然可以创建和删除表。这可以阻止进一步的损坏,InnoDB会放松一些常规检査,以便在发现坏数据时不会特意崩溃。在常规操作中,这样做是有安全保障的,但是在恢复时,最好还是避免这样做。如果需要执行InnoDB强制恢复,有个好主意是配置MySQL,使它在操作完成之前不接受常规的连接请求。 有一些备份工具已经出现多年了,不幸的是有些已经过时。最明显的例子是Maatkit的mk-parallel-dump,它从没有正确运行,甚至被重新设计过好几次还是不行。另外一个工具是mysqlhotcopy,它适合于古老的MyISAM表。大部分场景下这两个工具都无法让人相信数据是安全的,它们会使人误以为备份了数据实际上却非如此。例如,当使用InnoDB的innodb_file_per_table时,mysqlhotcopy会复制.ibd文件,这会使一些人误以为InnoDB的数据已经备份完成。在某些场景下,这两个工具都对服务器有一些负面影响。 如果你在2008或2009年时在看MySQL的路线图,可能听说过MySQL在线备份。这是一个可以用SQL命令来开始备份和还原的特性。它原本是规划在MySQL5.2版本中,后来重新安排在了MySQL6.0中,再后来,据我们所知被永久取消了。 这个工具之前叫做InnoDBHotBackup或ibbackup是从Oracle购买的MySQLEnterprise中的一部分。使用此工具备份不需要停止MySQL,也不需要设置锁或中断正常的数据库活动(但是会对服务器造成一些额外的负载)。它支持类似压缩备份、增量备份和到其他服务器的流备份的特性。这是MySQL“官方”的备份工具。 PerconaXtraBackup与MySQLEnterpriseBackup在很多方面都非常类似,但它是开源并且免费的。除了核心备份工具外,还有一个用Perl写的封装脚本,可以提供更多高级功能。它支持类似流、增量、压缩和多线程(并行)备份操作。也有许多特别的功能,用以降低在髙负载的系统上备份的影响。 适用于MySQL的ZmandaRecoveryManager,或ZRM和商业两种版本。企业版提供基于网页图形接口的控制台,用来配置、备份、验证、恢复、报告和调度。开源的版本包含了所有核心功能,但缺少一些额外的特性,例如基于网页的控制台。 正如其名,ZRM实际上是一个备份和恢复管理器,而并非单一工具。它封装了自有的基于标准工具和技术,例如mysqldump、LVM快照和PerconaXtraBackup等之上的功能。它将许多冗长的备份和恢复工作进行了自动化。 大部分人在使用这个与MySQL—起发行的程序,因此,尽管它有缺点,但创建数据和Schema的逻辑备份最常见的选择还是mysqldump。这是一个通用工具,可以用于许多的任务,例如在服务器间复制表。 $mysqldump--host=server1testt1|mysql--host=server2test我们在本章中展示了几个用mysqldump创建逻辑备份的例子。该工具默认会输出包含创建表和填充数据的所有需要的命令;也有选项可以控制输出视图、存储代码和触发器。下面有一些典型的例子。 $mysqldump--all-databases>dump.sql$mysqldump--databasessakila>dump.sql$mysqldumpsakilaactor>dump.sql可以使用—result-file选项来指定输出文件,这可以帮助防止在Windows上发生换行符转换: $mysqldumpsakilaactor--result-file=dump.sqlmysqldump的默认选项对于大多数备份目的来说并不够好。多半要显式地指定某些选项以改变输出。下面是一些我们经常使用的选项,可以让mysqldump更加高效,输出更容易使用。 --opt 启用一组优化选项,包括关闭缓冲区(它会使服务器耗尽内存),导出数据时把更多的数据写在更少的SQL语句里,以便在加载的时候更有效率,以及做其他一些有用的事情。更多细节可以阅读帮助文件。如果关闭了这组选项,mysqldump会在把表写到磁盘之前,把它们都导出到内存里,这对于大型的表而言是不切实际的。 -allow-keywords,-quote-names 使用户在导出和恢复表时,可以使用保留字作为表的名字。 --complete-insert 使用户能在不完全相同列的表之间移动数据。 -tz-utc 使用户能在具有不同时区的服务器之间移动数据。 --lock-all-tables 使用FLUSHTABLEWITHREADLOCK来获取全局一致的备份。 --tab 用SELECTINTOOUTFILE导出文件。 --skip-extended-insert 使每一行数据都有自己的INSERT语句。必要时这可以用于有选择地还原某些行。它的代价是文件更大,导入到MySQL时开销会更大。因此,要确保只有在需要时才启用它。 为备份写一些脚本是标准做法。展示一个示例程序,其中必定有很多辅助内容,这只会增加篇幅,在这里我们更愿意列举一些典型的备份脚本功能,展示一些Perl脚本的代码片断。你可以把这些当作可重用的代码块,在创建自己的脚本时可以直接组合起来使用。下面将大致按照使用顺序来展示。 安全检测 安全检测可以让自己和同事的生活更简单点——打开严格的错误检测,并且使用英文变量名。 usestrict;usewarningsFATAL=>'all';useEnglishqw(-no_match_vars);如果是在Bash下使用脚本,还可以做更严格的变量检测。下面的设置会让替换中有未定义的变量或程序出错退出时产生一个错误。 set-u;set-e;命令行参数 增加命令行选项处理最好的方法是用标准库,它已包含在Perl标准安装中。 useGetopt::Long;Getopt::Long::Configure('no_ignore_case','bundling');GetOptions(....);连接MySQL useDBI;$dbh=DBI->connect('DBI:mysql:;host=localhost','user','p4ssw0rd',{RaiseError=>1});对于编写命令行脚本,请阅读标准mysql程序的--help参数的输出文本。它有许多选项可更友好地支持脚本。例如,在Bash中遍历数据库列表如下。 mysql-ss-e'SHOWDATABASES'|whilereadDB;doecho"${DB}"done停止和启动MySQL 停止和启动MySQL最好的方法是使用操作系统推荐的方法,例如运行/etc/init.d/mysqlinit脚本或通过服务控制(在Windows下)。然而这并不是唯一的方法。可以从Perl中用一个已存在的数据库连接来关闭数据库。 $dbh->func("shutdown",'admin');当这个命令完成时不要太指望MySQL已经被关闭一一它可能正在关闭的过程中。也可以通过命令行来停掉MySQL。 $mysqladminshutdown获取数据库和表的列表 每个备份脚本都会査询MySQL以获取数据库和表的列表。要注意那些实际上并不是数据库的条目,例如一些日志系统中的lost+found文件夹和INFORMATION_SCHEMA。也要确保脚本已经准备好应付视图,同时也要知道SHOWTABLESTATUS在InnoDB中有大量数据时可能耗时很长。 mysql>SHOWDATABASES;mysql>SHOW/*!50002FULL*/TABLESFROM 如果需要对一个或多个表加锁并且/或刷新,要么按名字锁住所需的表,要么使用全局锁锁住所有的表。 mysql>LOCKTABLES 刷新二进制日志 让服务器开始一个新的二进制日志非常简单(一般在锁住表后但在备份前做这个操作): mysql>FLUSHLOGS;这样做使得恢复和增量备份更简单,因为不需要考虑从一个日志文件中间开始操作。此操作会有一些副作用,比如刷新和重新打开错误日志,也可能销毁老的日志条目,因此,注意不要扔掉需要用到的数据。 获取二进制日志位置 脚本应该获取并记录主库和备库的状态——即使服务器仅是个主库或备库。 mysql>SHOWMASTERSTATUS\Gmysql>SHOWSLAVESTATUS\G执行这两条语句并忽略错误,以使脚本可以获取到所有可能的信息。 导出数据 最好的选择是使用mysqldump、mydumper或SELECTINTO0UTFILE。 复制数据 可以使用本章中演示的任何一个方法。 这些都是构造备份脚本的基础。比较困难的部分是将管理和恢复任务脚本化。如果想获得实现的灵感,可以看看ZRM的源码。 不要掉进备库就是备份的陷阱。备库对生成备份是一个干涉较少的源,但它不是备份本身。对于RAID卷、SAN和文件系统快照,也同样如此。确保备份可以通过DROPTABLE测试(或“遭受黑客攻击”的测试),也要能通过数据中心失败的测试。如果是基于备库生成备份,确保使用pt-table-checksum验证复制的完整性。 我们最喜欢的两种备份方式,一种是从文件系统或者SAN快照中直接复制数据文件,一种是使用PerconaXtraBackup做热备份。这两种方法都可以无侵入地实现二进制的原始数据备份,这样的备份可以通过启动mysqld实例检査所有的表进行验证。有时候甚至可以一石二鸟:可以在开发或者预发环境每天将备份进行还原来执行恢复测试,然后再将数据导出为逻辑备份。我们也建议备份二进制日志,并且尽可能久地保留多份备份的数据和二进制文件。这样即使最近的备份无法使用了,还可以使用较老的备份来执行恢复或者创建新的备库。 除了提到的许多开源工具,也有很多很好的商业备份工具,其中最重要的是MySQLEnterpriseBackup。对包括在GUISQL编辑器、服务器管理工具和类似工具中的“备份”工具要特别小心。同样地,有一些出品“一招吃遍天下”的备份工具的公司,对于它们宣称的支持MySQL的“MySQL备份插件”也要特别小心。我们需要的是主要为MySQL设计的优秀备份工具,而不是一个支持上百个其他数据库并恰巧支持MySQL的工具。有许多备份工具的供应者并不知道或明白诸如FLUSHTABLESWITHREADLOCK操作对数据库的影响。在我们看来,使用这种SQL命令的方案应该自动退出“热”备份的行列。如果只使用InnoDB表,就更加不需要这类工具。