2018年1月11日星期四

mysqldump与innobackupex备份过程


2.1. innobackupex备份过程解读

A库清空查询日志
为了更清晰地追踪innobackupex是如何拷贝redo log的,我们在A库新开一个ssh会话2,使用如下脚本持续对表t_luoxiaobo进行插入操作(该表为innodb表),限于篇幅,请到如下为知笔记链接获取

  • http://5d096a11.wiz03.com/share/s/1t2mEh0a-kl_2c2NZ33kSiac1Rgvxq1vgkhL21ibWU2cLidk
A库使用innobackupex执行备份,使用strace命令抓取备份过程中的调用栈
查看general_log日志中的记录(删掉了加压脚本中的语句)
从上面的记录中可以看到,与mysqldump相比,innobackupex备份时对数据库的操作多了一个FLUSH NO_WRITE_TO_BINLOG ENGINE LOGS,稍后对这句的作用进行解释
因为innobackupex是物理拷贝文件,数据并不像mysqldump那样通过对数据库表执行select语句查询进行备份,而是通过拷贝磁盘文件进行备份的,所以,主体的备份流程还需要看strace的调用栈,限于篇幅原因,详见为知笔记外链:http://5d096a11.wiz03.com/share/s/1t2mEh0a-kl_2c2NZ33kSiac2ZRJlK3qIAQr2LjYMx2xMkCD
通过备份输出日志和strace调用栈,整理的流程图如下(全备) :


2.2. innobackupex为什么需要这么做

nnobackupex备份时启动一个进程多个线程,通过拷贝磁盘文件实现物理备份,为了保证备份数据的一致性,需要在备份过程中恰当的时机发送一些加锁解锁语句与数据库实例进行交互,so…要了解innobackupex工具的整个备份过程中做了哪些事情,我们就需要查看general_log和备份过程中的日志输出(其实strace调用栈信息里就可以了解到innobackupex所做的所有事情,但是。。都是系统调用,看起来比较费劲),对于备份过程中的日志输出,这里就不再熬述,详见上文中的"全备流程图",本小节我们只介绍general_log中的输出重点语句,如下:
  • FLUSH NO_WRITE_TO_BINLOG TABLES、FLUSH TABLES WITH READ LOCK、SHOW MASTER STATUS、UNLOCK TABLES几个语句的作用与mysqldump备份过程中的这几个语句的作用一样
  • FLUSH NO_WRITE_TO_BINLOG ENGINE LOGS,该语句在mysqldump备份过程中没有 
    * 这句的作用是在所有的事务表和非事务表备份完成,获取了全局读锁,且使用SHOW MASTER STATUS语句获取了binlog pos之后,执行刷新redo log buffer中的日志到磁盘中,然后redo log copy线程拷贝这最后的redo log日志数据(为什么说是最后的redo log日志数据呢?因为此时使用FLUSH TABLES WITH READ LOCK加锁之后,使用UNLOCK TABLES释放全局读锁之前,不会再有新的请求进来,),拷贝完成之后就停止copy线程并关闭xtrabackup_logfile文件。然后再使用UNLOCK TABLES释放全局读锁。 
    * 详见姜承尧老师的推文:http://chuansong.me/n/372118651979


2.3. innobackupex有什么坑吗?

从上文中介绍的innobackupex的备份流程和原理上,我们可以得知,innobackupex工具备份过程中是不会出现前面提到的mysqldump备份工具的"坑一"的。因为innobackupex备份工具是在所有事务表和非事务表都备份完成之后才会执行UNLOCK TABLES释放全局读锁,so…从加锁之后,解锁之前不可能有任何其他的DML请求能够对数据做修改,从而保证的备份数据的一致性。
那么,mysqldump的"坑二"呢?我们来看下面请看演示过程
A库使用如下脚本持续对表t_luoxiaobo进行插入操作(该表为innodb表),限于篇幅,请到如下为知笔记链接获取(留意把program_name变量值改为"innobackupex")
http://5d096a11.wiz03.com/share/s/1t2mEh0a-kl_2c2NZ33kSiac1Rgvxq1vgkhL21ibWU2cLidk
A库新开一个ssh会话2,执行innobackupex备份,留意日志打印过程。从下面的结果中,我们可以看到报错终止了:
发生什么了?
  • 首先,我们知道,innobackupex在备份事务表时,是没有对数据库加锁的,so..这个时候,其实DDL是允许执行的,innobackupex持续在备份innodb事务表期间,如果被执行DDL的表是在innobackupex备份完成之后发起,那么在下一次scan lsn的时候innobackupex将发现DDL更改,报错终止,如果是在备份非事务表期间发起的DDL,那么将被FLUSH TABLE WITH READ LOCK语句阻塞。所以,对于使用innobackupex备份的生产环境,要执行DDL语句,也需要避开备份时间
那么,除了这个,还有其他坑吗?
  • 前面在介绍mysqldump备份过程中的FLUSH TABLES和FLUSH TABLES WITH READ LOCK语句的时候,提到过三个注意事项,innobackupex备份过程中为了获得一个一致性备份,仍然会使用这两个语句对数据库进行刷新表缓存、加全局读锁,也就是说,mysqldump使用这两个语句可能会踩到的坑,在innobackupex中也会碰到,如下: 
    * 1)、如果一个会话中使用LOCK TABLES语句对某表加了表锁,在该表锁未释放前,那么另外一个会话如果执行FLUSH TABLES和FLUSH TABLES WITH READ LOCK语句会被阻塞,而如果数据库中lock_wait_timeout参数设置时间太短,innobackupex将会因为执行FLUSH TABLES WITH READ LOCK语句获取全局读锁超时而导致备份失败退出 
    * 2)、如果一个会话正在执行DDL语句,那么另外一个会话如果执行FLUSH TABLES和FLUSH TABLES WITH READ LOCK语句会被阻塞,而如果数据库中lock_wait_timeout参数设置时间太短,innobackupex将会因为执行FLUSH TABLES WITH READ LOCK语句获取全局读锁超时而导致备份失败退出 
    * 3)、如果一个会话正在执行DML大事务(DML语句正在执行,数据正在发生修改,而不是使用lock in share mode和for update语句来显式加锁),那么另外一个会话如果执行FLUSH TABLES和FLUSH TABLES WITH READ LOCK语句会被阻塞,而如果数据库中lock_wait_timeout参数设置时间太短,innobackupex将会因为执行FLUSH TABLES WITH READ LOCK语句获取全局读锁超时而导致备份失败退出
  • 但是,细心的童鞋可能已经发现了,innobackupex备份时的general_log中执行FLUSH NO_WRITE_TO_BINLOG TABLES语句之前,有这样一句语句:SET SESSION lock_wait_timeout=31536000,备份时会在session级别把锁超时时间改了,so…除了加表锁忘记释放之外,其他两种情况估计不太可能碰到锁超时的情况!!
当然,如果每天备份一次,那么我们不太可能让innobackupex在备份时,获取全局读锁时等待31536000秒,so……我们可以使用innobackupex的选项--kill-long-queries-timeout,来再获取全局读锁时,如果某查询阻塞了获得该FLUSH TABLE WITH READ LOCK语句时间超过这个阀值,那么就对该会话执行kill,杀掉这个连接,当然,你也许会说对数据做修改的不能杀,只能杀查询的,那么我们可以使用--kill-long-query-type=all|select选项。下面列出这俩选项的含义:
  • --kill-long-query-type=all|select 
    * 该选项指定哪些类型的查询在指定的查询时间之后还没有执行完成时被kill掉,以释放阻塞加全局读锁的锁,默认值为all,有效值有:all和select 
    * 执行该选项需要有process和super权限
  • --kill-long-queries-timeout=SECONDS
    * 该选项指定innobackupex在执行FLUSH TABLES WITH READ LOCK时碰到阻塞其获得锁的查询时,等待该参数指定的秒数之后,如果仍然有查询在运行,则执行kill掉这些查询 
    * 默认值为0,表示innobackupex 不启用尝试kill掉任何查询的功能
PS:
  • 很多人喜欢在备份前先flush binary logs一把,其实在有大事务对数据进行修改时,一不小心可能就会出现数据库hang死,所以不建议这么做
  • innobackupex备份期间,在数据库中创建的连接不要误杀,否则备份失败

3、总  结

作为专职的DBA:
  • 我们一定一定要保持一种高度谨慎的态度,在数据库备份方案选型时,一定要根据自己的业务场景充分测试,校验,尽可能地把可能出现的深坑挖出来。
  • 除了寻找适合自己的,可行的备份方案之外,更应该做好备份校验(备份是否成功完成、备份文件是否损坏)、备份恢复演练(备份文件是否可以正常恢复数据),以备不时之需。
  • 对生产库的DDL操作、大事务、或者长时间锁表的操作,一定要避开备份时间,否则,你懂的……
注:
本小节演示的xtrabackup版本基于2.4.4,如果xtrabackup版本小于2.3,备份过程中的系统调用有一些不太一样,详情请参考链接:http://mysql.taobao.org/monthly/2016/03/07/
全文参考链接:
  • https://dev.mysql.com/doc/refman/5.7/en/innodb-consistent-read.html
  • https://dev.mysql.com/doc/refman/5.7/en/mysqldump.html#option_mysqldump_single-transaction
  • https://dev.mysql.com/doc/refman/5.7/en/mysqldump.html#option_mysqldump_master-data
  • https://dev.mysql.com/doc/refman/5.7/en/commit.html
  • https://dev.mysql.com/doc/refman/5.7/en/flush.html
  • https://dev.mysql.com/doc/refman/5.7/en/log-destinations.html
  • https://dev.mysql.com/doc/refman/5.7/en/savepoint.html
  • https://www.percona.com/doc/percona-xtrabackup/LATEST/innobackupex/innobackupex_option_reference.html
现在看innobackupex

2.1. innobackupex备份过程解读

A库清空查询日志
为了更清晰地追踪innobackupex是如何拷贝redo log的,我们在A库新开一个ssh会话2,使用如下脚本持续对表t_luoxiaobo进行插入操作(该表为innodb表),限于篇幅,请到如下为知笔记链接获取
  • http://5d096a11.wiz03.com/share/s/1t2mEh0a-kl_2c2NZ33kSiac1Rgvxq1vgkhL21ibWU2cLidk
A库使用innobackupex执行备份,使用strace命令抓取备份过程中的调用栈
查看general_log日志中的记录(删掉了加压脚本中的语句)
从上面的记录中可以看到,与mysqldump相比,innobackupex备份时对数据库的操作多了一个FLUSH NO_WRITE_TO_BINLOG ENGINE LOGS,稍后对这句的作用进行解释
因为innobackupex是物理拷贝文件,数据并不像mysqldump那样通过对数据库表执行select语句查询进行备份,而是通过拷贝磁盘文件进行备份的,所以,主体的备份流程还需要看strace的调用栈,限于篇幅原因,详见为知笔记外链:http://5d096a11.wiz03.com/share/s/1t2mEh0a-kl_2c2NZ33kSiac2ZRJlK3qIAQr2LjYMx2xMkCD
通过备份输出日志和strace调用栈,整理的流程图如下(全备) :

2.2. innobackupex为什么需要这么做

nnobackupex备份时启动一个进程多个线程,通过拷贝磁盘文件实现物理备份,为了保证备份数据的一致性,需要在备份过程中恰当的时机发送一些加锁解锁语句与数据库实例进行交互,so…要了解innobackupex工具的整个备份过程中做了哪些事情,我们就需要查看general_log和备份过程中的日志输出(其实strace调用栈信息里就可以了解到innobackupex所做的所有事情,但是。。都是系统调用,看起来比较费劲),对于备份过程中的日志输出,这里就不再熬述,详见上文中的"全备流程图",本小节我们只介绍general_log中的输出重点语句,如下:
  • FLUSH NO_WRITE_TO_BINLOG TABLES、FLUSH TABLES WITH READ LOCK、SHOW MASTER STATUS、UNLOCK TABLES几个语句的作用与mysqldump备份过程中的这几个语句的作用一样
  • FLUSH NO_WRITE_TO_BINLOG ENGINE LOGS,该语句在mysqldump备份过程中没有 
    * 这句的作用是在所有的事务表和非事务表备份完成,获取了全局读锁,且使用SHOW MASTER STATUS语句获取了binlog pos之后,执行刷新redo log buffer中的日志到磁盘中,然后redo log copy线程拷贝这最后的redo log日志数据(为什么说是最后的redo log日志数据呢?因为此时使用FLUSH TABLES WITH READ LOCK加锁之后,使用UNLOCK TABLES释放全局读锁之前,不会再有新的请求进来,),拷贝完成之后就停止copy线程并关闭xtrabackup_logfile文件。然后再使用UNLOCK TABLES释放全局读锁。 
    * 详见姜承尧老师的推文:http://chuansong.me/n/372118651979

2.3. innobackupex有什么坑吗?

从上文中介绍的innobackupex的备份流程和原理上,我们可以得知,innobackupex工具备份过程中是不会出现前面提到的mysqldump备份工具的"坑一"的。因为innobackupex备份工具是在所有事务表和非事务表都备份完成之后才会执行UNLOCK TABLES释放全局读锁,so…从加锁之后,解锁之前不可能有任何其他的DML请求能够对数据做修改,从而保证的备份数据的一致性。
那么,mysqldump的"坑二"呢?我们来看下面请看演示过程
A库使用如下脚本持续对表t_luoxiaobo进行插入操作(该表为innodb表),限于篇幅,请到如下为知笔记链接获取(留意把program_name变量值改为"innobackupex")
http://5d096a11.wiz03.com/share/s/1t2mEh0a-kl_2c2NZ33kSiac1Rgvxq1vgkhL21ibWU2cLidk
A库新开一个ssh会话2,执行innobackupex备份,留意日志打印过程。从下面的结果中,我们可以看到报错终止了:
发生什么了?
  • 首先,我们知道,innobackupex在备份事务表时,是没有对数据库加锁的,so..这个时候,其实DDL是允许执行的,innobackupex持续在备份innodb事务表期间,如果被执行DDL的表是在innobackupex备份完成之后发起,那么在下一次scan lsn的时候innobackupex将发现DDL更改,报错终止,如果是在备份非事务表期间发起的DDL,那么将被FLUSH TABLE WITH READ LOCK语句阻塞。所以,对于使用innobackupex备份的生产环境,要执行DDL语句,也需要避开备份时间
那么,除了这个,还有其他坑吗?
  • 前面在介绍mysqldump备份过程中的FLUSH TABLES和FLUSH TABLES WITH READ LOCK语句的时候,提到过三个注意事项,innobackupex备份过程中为了获得一个一致性备份,仍然会使用这两个语句对数据库进行刷新表缓存、加全局读锁,也就是说,mysqldump使用这两个语句可能会踩到的坑,在innobackupex中也会碰到,如下: 
    * 1)、如果一个会话中使用LOCK TABLES语句对某表加了表锁,在该表锁未释放前,那么另外一个会话如果执行FLUSH TABLES和FLUSH TABLES WITH READ LOCK语句会被阻塞,而如果数据库中lock_wait_timeout参数设置时间太短,innobackupex将会因为执行FLUSH TABLES WITH READ LOCK语句获取全局读锁超时而导致备份失败退出 
    * 2)、如果一个会话正在执行DDL语句,那么另外一个会话如果执行FLUSH TABLES和FLUSH TABLES WITH READ LOCK语句会被阻塞,而如果数据库中lock_wait_timeout参数设置时间太短,innobackupex将会因为执行FLUSH TABLES WITH READ LOCK语句获取全局读锁超时而导致备份失败退出 
    * 3)、如果一个会话正在执行DML大事务(DML语句正在执行,数据正在发生修改,而不是使用lock in share mode和for update语句来显式加锁),那么另外一个会话如果执行FLUSH TABLES和FLUSH TABLES WITH READ LOCK语句会被阻塞,而如果数据库中lock_wait_timeout参数设置时间太短,innobackupex将会因为执行FLUSH TABLES WITH READ LOCK语句获取全局读锁超时而导致备份失败退出
  • 但是,细心的童鞋可能已经发现了,innobackupex备份时的general_log中执行FLUSH NO_WRITE_TO_BINLOG TABLES语句之前,有这样一句语句:SET SESSION lock_wait_timeout=31536000,备份时会在session级别把锁超时时间改了,so…除了加表锁忘记释放之外,其他两种情况估计不太可能碰到锁超时的情况!!
当然,如果每天备份一次,那么我们不太可能让innobackupex在备份时,获取全局读锁时等待31536000秒,so……我们可以使用innobackupex的选项--kill-long-queries-timeout,来再获取全局读锁时,如果某查询阻塞了获得该FLUSH TABLE WITH READ LOCK语句时间超过这个阀值,那么就对该会话执行kill,杀掉这个连接,当然,你也许会说对数据做修改的不能杀,只能杀查询的,那么我们可以使用--kill-long-query-type=all|select选项。下面列出这俩选项的含义:
  • --kill-long-query-type=all|select 
    * 该选项指定哪些类型的查询在指定的查询时间之后还没有执行完成时被kill掉,以释放阻塞加全局读锁的锁,默认值为all,有效值有:all和select 
    * 执行该选项需要有process和super权限
  • --kill-long-queries-timeout=SECONDS
    * 该选项指定innobackupex在执行FLUSH TABLES WITH READ LOCK时碰到阻塞其获得锁的查询时,等待该参数指定的秒数之后,如果仍然有查询在运行,则执行kill掉这些查询 
    * 默认值为0,表示innobackupex 不启用尝试kill掉任何查询的功能
PS:
  • 很多人喜欢在备份前先flush binary logs一把,其实在有大事务对数据进行修改时,一不小心可能就会出现数据库hang死,所以不建议这么做
  • innobackupex备份期间,在数据库中创建的连接不要误杀,否则备份失败

3、总  结

作为专职的DBA:
  • 我们一定一定要保持一种高度谨慎的态度,在数据库备份方案选型时,一定要根据自己的业务场景充分测试,校验,尽可能地把可能出现的深坑挖出来。
  • 除了寻找适合自己的,可行的备份方案之外,更应该做好备份校验(备份是否成功完成、备份文件是否损坏)、备份恢复演练(备份文件是否可以正常恢复数据),以备不时之需。
  • 对生产库的DDL操作、大事务、或者长时间锁表的操作,一定要避开备份时间,否则,你懂的……
注:
本小节演示的xtrabackup版本基于2.4.4,如果xtrabackup版本小于2.3,备份过程中的系统调用有一些不太一样,详情请参考链接:http://mysql.taobao.org/monthly/2016/03/07/
全文参考链接:
  • https://dev.mysql.com/doc/refman/5.7/en/innodb-consistent-read.html
  • https://dev.mysql.com/doc/refman/5.7/en/mysqldump.html#option_mysqldump_single-transaction
  • https://dev.mysql.com/doc/refman/5.7/en/mysqldump.html#option_mysqldump_master-data
  • https://dev.mysql.com/doc/refman/5.7/en/commit.html
  • https://dev.mysql.com/doc/refman/5.7/en/flush.html
  • https://dev.mysql.com/doc/refman/5.7/en/log-destinations.html
  • https://dev.mysql.com/doc/refman/5.7/en/savepoint.html
  • https://www.percona.com/doc/percona-xtrabackup/LATEST/innobackupex/innobackupex_option_reference.html
地点: 中国江苏省苏州市