【转】mysqldump与innobackupex知多少

做者:罗小波html

【目录】mysql

1. 先看mysqldump 
1.1 mysqldump备份过程解读 
1.2 mysqldump备份过程当中的关键步骤 
1.2.1 FLUSH TABLES和FLUSH TABLES WITH READ LOCK的区别 
1.2.2 修改隔离级别的做用 
1.2.3 使用WITH CONSISTENT SNAPSHOT子句的做用 
1.2.4 使用savepoint来设置回滚点的做用 
1.3 mysqldump有什么坑吗? 
1.3.1. 坑一 
1.3.2. 坑二 
1.3.3. 有办法改善这这些问题吗? 
2. 如今看innobackupex 
2.1 innobackupex备份过程解读 
2.2 innobackupex为何须要这么作 
2.3 innobackupex有什么坑吗? 
3. 总结sql

导读

想必搞数据库的都知道:数据库

  • mysqldump优势:mysqldump的优势就是逻辑备份,把数据生成SQL形式保存,在单库,单表数据迁移,备份恢复等场景方便,SQL形式的备份文件通用,也方便在不一样数据库之间移植。对于InnoDB表能够在线备份。缓存

  • mysqldump缺点:mysqldump是单线程,数据量大的时候,备份时间长,甚至有可能在备份过程当中非事务表长期锁表对业务形成影响(SQL形式的备份恢复时间也比较长)。mysqldump备份时会查询全部的数据,这可能会把内存中的热点数据刷掉ruby

  • innobackupex优势:物理备份能够绕过MySQL Server层,加上自己就是文件系统级别的备份,备份速度块,恢复速度快,能够在线备份,支持并发备份,支持加密传输,支持备份限速服务器

  • innobackupex缺点:要提取部分库表数据比较麻烦,不能按照基于时间点来恢复数据,而且不能远程备份,只能本地备份,增量备份的恢复也比较麻烦。若是使用innobackupex的全备+binlog增量备份就能够解决基于时间点恢复的问题。微信

要查看备份过程当中这俩备份工具都对数据库作了什么操做,想必你们都知道:能够打开general_log来查。那么问题来了,general_log输出的信息都表明什么?若是不这样作会怎样?这两个备份工具会不会有什么平时被忽略的坑?请看下文分析,也许……你会发现原来以前对这俩备份工具好像也不是那么了解!markdown

环境信息session

  • 服务器配置:

    • CPU:4 vcpus
    • 内存:4G
    • 磁盘:250G SAS
    • 网卡:Speed: 1000Mb/s
  • 操做系统:CentOS release 6.5 (Final)

  • 数据库版本:MySQL 5.7.17

  • xtrabackup版本:2.4.4

  • 主从IP(文中一些演示步骤须要用到主备复制架构):

    • 主库:192.168.2.111(如下称为A库)
    • 从库:192.168.2.121(如下称为B库)
  • 数据库关键配置参数

    • 主库:双一log_slave_updateslog-binbinlog_rows_query_log_events=ONserver-id=3306111gtid_mode=ONenforce_gtid_consistency=ONauto_increment_increment=2auto_increment_offset=1
    • 备库:双一log_slave_updateslog-binbinlog_rows_query_log_events=ONserver-id=3306121gtid_mode=ONenforce_gtid_consistency=ONauto_increment_increment=2auto_increment_offset=2
  • 测试库表建立(这里在同一个库下建立两个表,一个表为InnoDB引擎,一个为MyISAM引擎)

root@localhost : (none) 04:21:27> create database luoxiaobo;
Query OK, 1 row affected (0.01 sec)
root@localhost : (none) 04:21:45> use luoxiaobo
Database changed
root@localhost : luoxiaobo 04:21:55> create table t_luoxiaobo(id int unsigned not null primary key auto_increment,test varchar(50),datet_time datetime) engine=innodb;
Query OK, 0 rows affected (0.05 sec)
root@localhost : luoxiaobo 04:23:00> insert into t_luoxiaobo(test,datet_time) values('1',now());
Query OK, 1 row affected (0.00 sec)
root@localhost : luoxiaobo 04:23:32> insert into t_luoxiaobo(test,datet_time) values('2',now());
Query OK, 1 row affected (0.01 sec)
root@localhost : luoxiaobo 04:23:36> insert into t_luoxiaobo(test,datet_time) values('3',now());
Query OK, 1 row affected (0.00 sec)
root@localhost : luoxiaobo 04:23:38> insert into t_luoxiaobo(test,datet_time) values('4',now());
Query OK, 1 row affected (0.00 sec)
root@localhost : luoxiaobo 04:23:41> select * from t_luoxiaobo;
+----+------+---------------------+
| id | test | datet_time          |
+----+------+---------------------+
|  1 | 1    | 2017-07-01 16:23:32 |
|  3 | 2    | 2017-07-01 16:23:36 |
|  5 | 3    | 2017-07-01 16:23:38 |
|  7 | 4    | 2017-07-01 16:23:41 |
+----+------+---------------------+
4 rows in set (0.00 sec)
root@localhost : luoxiaobo 04:24:51> create table t_luoxiaobo2(id int unsigned not null primary key auto_increment,test varchar(50),datet_time datetime) engine=myisam;
Query OK, 0 rows affected (0.04 sec)
root@localhost : luoxiaobo 05:38:19> insert into t_luoxiaobo2(test,datet_time) values('1',now());
Query OK, 1 row affected (0.01 sec)
root@localhost : luoxiaobo 05:38:29> insert into t_luoxiaobo2(test,datet_time) values('2',now());
Query OK, 1 row affected (0.00 sec)
root@localhost : luoxiaobo 05:38:32> insert into t_luoxiaobo2(test,datet_time) values('3',now());
Query OK, 1 row affected (0.01 sec)
root@localhost : luoxiaobo 05:38:35> insert into t_luoxiaobo2(test,datet_time) values('4',now());
Query OK, 1 row affected (0.00 sec)
root@localhost : luoxiaobo 05:38:37> select * from t_luoxiaobo2;
+----+------+---------------------+
| id | test | datet_time          |
+----+------+---------------------+
|  1 | 1    | 2017-07-01 17:38:29 |
|  3 | 2    | 2017-07-01 17:38:32 |
|  5 | 3    | 2017-07-01 17:38:35 |
|  7 | 4    | 2017-07-01 17:38:37 |
+----+------+---------------------+
4 rows in set (0.00 sec)

1. 先看mysqldump

1.1 mysqldump备份过程解读

一般,使用mysqldump备份期间,为了使得数据库中加锁时间尽可能短,会使用--single-transaction选项来开启一个一致性快照事务,为了使得备份期间可以得到一个与数据一致的binlog pos点,会使用--master-data选项,如今登陆A库主机,使用这俩选项执行备份演示。

  • 先在数据库中打开general_log
root@localhost : luoxiaobo 04:23:50> show variables like 'general_log';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| general_log   | OFF   |
+---------------+-------+
1 row in set (0.00 sec)
root@localhost : luoxiaobo 04:24:41> set global general_log=1;
Query OK, 0 rows affected (0.03 sec)
root@localhost : luoxiaobo 04:24:49> show variables like 'general_log';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| general_log   | ON    |
+---------------+-------+
1 row in set (0.01 sec)
  • 使用mysqldump备份(使用strace捕获执行过程当中的调用栈),这里紧以备份测试库luoxiaobo为例进行演示:
[root@localhost ~]# strace mysqldump -h 192.168.2.111 -uadmin -pletsg0 --single-transaction --master-data=2 --triggers --routines --events luoxiaobo > \
backup_`date +%F_%H_%M_%S`.sql 2> strace_mysqldump.txt
  • 备份完成以后,查看general_log中的内容(去掉了一些无用信息):
* 留意unlock tables语句的位置,是在show master status语句获取了binlog pos以后当即执行
[root@localhost ~]# cat /home/mysql/data/mysqldata1/mydata/localhost.log 
......
' #修改session级别的sql_mode为空,避免可能有些sql_mode值对备份产生影响'
2017-07-01T17:42:17.779564+08:00        6 Query /*!40100 SET @@SQL_MODE='' */  2017-07-01T17:42:17.779695+08:00        6 Query /*!40103 SET TIME_ZONE='+00:00' */
' #强制刷新表缓存到磁盘并关闭表(但已经加表锁的表会阻塞该语句)'
2017-07-01T17:42:17.779889+08:00        6 Query FLUSH /*!40101 LOCAL */ TABLES 
' # 对整个实例加全局读锁,若是存在表锁将阻塞加全局读锁语句'
2017-07-01T17:42:17.780047+08:00        6 Query FLUSH TABLES WITH READ LOCK
' #在session级别修改隔离级别为RR'
2017-07-01T17:42:17.780201+08:00        6 Query SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ
'# 开启一个一致性快照事务,必须在隔离级别RR下才能开启一个快照事务'
2017-07-01T17:42:17.780326+08:00        6 Query START TRANSACTION /*!40100 WITH CONSISTENT SNAPSHOT */ 
'#查看是否开启GTID'
2017-07-01T17:42:17.780452+08:00        6 Query SHOW VARIABLES LIKE 'gtid\_mode' 
'#若是开启GTID则查看当前的事务GTID集合'
2017-07-01T17:42:17.781867+08:00        6 Query SELECT @@GLOBAL.GTID_EXECUTED 
'#查看当前数据的binlog pos'
2017-07-01T17:42:17.781999+08:00        6 Query SHOW MASTER STATUS  
'#释放全局读锁,留意解锁的位置,下文会专门提到这个'
2017-07-01T17:42:17.782113+08:00        6 Query UNLOCK TABLES 
......
2017-07-01T17:42:17.786315+08:00        6 Init DB   luoxiaobo
'#在一个数据库开始备份以前,设置一个保存点(回滚点)'
2017-07-01T17:42:17.786428+08:00        6 Query SAVEPOINT sp  
'#查看库下有哪些表'
2017-07-01T17:42:17.786539+08:00        6 Query show tables  
' #查看这个表的状态'
2017-07-01T17:42:17.786710+08:00        6 Query show table status like 't\_luoxiaobo' 
'# 给每一个表的每一个字段加个反引号'
2017-07-01T17:42:17.786908+08:00        6 Query SET SQL_QUOTE_SHOW_CREATE=1 
'#表结构的备份都是binary格式,因此要先改这个'
2017-07-01T17:42:17.787023+08:00        6 Query SET SESSION character_set_results = 'binary' 
' #查看这个表的定义语句'
2017-07-01T17:42:17.787137+08:00        6 Query show create table `t_luoxiaobo` 
'# 修改session的数据结果返回字符集,备份数据须要使用数据本来的字符集,这里是utf8'
2017-07-01T17:42:17.787329+08:00        6 Query SET SESSION character_set_results = 'utf8' 
' #查看这个表的字段信息'
2017-07-01T17:42:17.787450+08:00        6 Query show fields from `t_luoxiaobo` 
2017-07-01T17:42:17.787715+08:00        6 Query show fields from `t_luoxiaobo`
' #查询表中的数据,结合show fields from `t_luoxiaobo`的字段信息生成insert into语句'
2017-07-01T17:42:17.787967+08:00        6 Query SELECT /*!40001 SQL_NO_CACHE */ * FROM `t_luoxiaobo` 
2017-07-01T17:42:17.788285+08:00        6 Query SET SESSION character_set_results = 'binary' 
2017-07-01T17:42:17.788411+08:00        6 Query use `luoxiaobo`
2017-07-01T17:42:17.788535+08:00        6 Query select @@collation_database
'#查看是否有这个表的触发器'
2017-07-01T17:42:17.788668+08:00        6 Query SHOW TRIGGERS LIKE 't\_luoxiaobo'  
2017-07-01T17:42:17.788926+08:00        6 Query SET SESSION character_set_results = 'utf8'
' #t_luoxiaobob表备份结束,回滚到保存点sp,以释放select *...语句产生的MDL锁,若是不回滚到sp,后续整个备份过程当中没法对该表执行DDL操做'
2017-07-01T17:42:17.789043+08:00        6 Query ROLLBACK TO SAVEPOINT sp  
2017-07-01T17:42:17.789191+08:00        6 Query show table status like 't\_luoxiaobo2'
2017-07-01T17:42:17.789399+08:00        6 Query SET SQL_QUOTE_SHOW_CREATE=1
2017-07-01T17:42:17.789510+08:00        6 Query SET SESSION character_set_results = 'binary'
2017-07-01T17:42:17.789625+08:00        6 Query show create table `t_luoxiaobo2`
2017-07-01T17:42:17.789753+08:00        6 Query SET SESSION character_set_results = 'utf8'
2017-07-01T17:42:17.789871+08:00        6 Query show fields from `t_luoxiaobo2`
2017-07-01T17:42:17.790123+08:00        6 Query show fields from `t_luoxiaobo2`
'#备份表t_luoxiaobo2'
2017-07-01T17:42:17.790486+08:00        6 Query SELECT /*!40001 SQL_NO_CACHE */ * FROM `t_luoxiaobo2`  
2017-07-01T17:42:17.790689+08:00        6 Query SET SESSION character_set_results = 'binary'
2017-07-01T17:42:17.790806+08:00        6 Query use `luoxiaobo`
2017-07-01T17:42:17.790923+08:00        6 Query select @@collation_database
2017-07-01T17:42:17.791053+08:00        6 Query SHOW TRIGGERS LIKE 't\_luoxiaobo2'
2017-07-01T17:42:17.791378+08:00        6 Query SET SESSION character_set_results = 'utf8'
'#备份t_luoxiaobo2表过程与t__luoxiaobo表彻底同样'
2017-07-01T17:42:17.791497+08:00        6 Query ROLLBACK TO SAVEPOINT sp  
'#整个luoxiaobo库备份完成以后,释放该保存点'
2017-07-01T17:42:17.791606+08:00        6 Query RELEASE SAVEPOINT sp  
' #查看是否有相关的events'
2017-07-01T17:42:17.791717+08:00        6 Query show events 
2017-07-01T17:42:17.792065+08:00        6 Query use `luoxiaobo`
2017-07-01T17:42:17.792323+08:00        6 Query select @@collation_database
2017-07-01T17:42:17.792489+08:00        6 Query SET SESSION character_set_results = 'binary'
'#查看luoxiaobo库是否有存储函数'
2017-07-01T17:42:17.792617+08:00        6 Query SHOW FUNCTION STATUS WHERE Db = 'luoxiaobo' 
' #查看luoxiaobo库是否有存储过程'
2017-07-01T17:42:17.793967+08:00        6 Query SHOW PROCEDURE STATUS WHERE Db = 'luoxiaobo'
2017-07-01T17:42:17.794952+08:00        6 Query SET SESSION character_set_results = 'utf8'
'#备份结束,退出链接'
2017-07-01T17:42:17.805746+08:00        6 Quit

查看strace抓取的调用栈信息,限于篇幅,详见为知笔记连接:

http://5d096a11.wiz03.com/share/s/1t2mEh0a-kl_2c2NZ33kSiac3oxBB40tGQNY2L6Z_M2LtLbG

上面的strace信息是否是看起来和general_log中的信息很像啊?由于general_log中记录的就是mysqldump发送过去的SQL语句:

从上面general_log和strace信息对比咱们能够知道,strace信息表明了mysqldump进程对数据库进程发送了哪些请求信息,general_log表明了数据库中全部的客户端SQL请求操做记录,这就是你们熟知的mysqldump备份过程当中的关键步骤,那么问题来了,mysqldump备份过程当中为何须要这些 步骤?不这么作会怎样?下面对这些步骤逐一使用演示步骤进行详细解释。

1.2 mysqldump备份过程当中的关键步骤

1.2.1 FLUSH TABLES和FLUSH TABLES WITH READ LOCK的区别

  • FLUSH TABLES

强制关闭全部正在使用的表,并刷新查询缓存,从查询缓存中删除全部查询缓存结果,相似RESET QUERY CACHE语句的行为

在MySQL 5.7官方文档描述中,当有表正处于LOCK TABLES … READ语句加锁状态时,不容许使用FLUSH TABLES语句(另一个会话执行FLUSH TABLES会被阻塞),若是已经使用LOCK TABLES … READ语句对某表加读锁的状况下要对另外的表执行刷新,能够在另一个会话中使用FLUSH TABLES tbl_name … WITH READ LOCK语句(稍后会讲到)

注意:

  • 若是一个会话中使用LOCK TABLES语句对某表加了表锁,在该表锁未释放前,那么另一个会话若是执行FLUSH TABLES语句会被阻塞
  • 若是一个会话正在执行DDL语句,那么另一个会话若是执行FLUSH TABLES 语句会被阻塞
  • 若是一个会话正在执行DML大事务(DML语句正在执行,数据正在发生修改,而不是使用lock in share mode和for update语句来显式加锁),那么另一个会话若是执行FLUSH TABLES语句会被阻塞
  • FLUSH TABLES WITH READ LOCK

关闭全部打开的表,并使用全局读锁锁定整个实例下的全部表。此时,你能够方便地使用支持快照的文件系统进行快照备份,备份完成以后,使用UNLOCK TABLES语句释放锁。

FLUSH TABLES WITH READ LOCK语句获取的是一个全局读锁,而不是表锁,所以表现行为不会像LOCK TABLES和UNLOCK TABLES语句,LOCK TABLES和UNLOCK TABLES语句在与事务混搭时,会出现一些相互影响的状况,以下:

  • 若是有表使用了LOCK TABLES语句加锁,那么开启一个事务会形成该表的表锁被释放(注意是任何表的表锁,只要存在表锁都会被释放,另外,必须是同一个会话中操做才会形成这个现象),就相似执行了UNLOCK TABLES语句同样,但使用FLUSH TABLES WITH READ LOCK语句加全局读锁,开启一个事务不会形成全局读锁被释放

  • 若是你开启了一个事务,而后在事务内使用LOCK TABLES语句加锁和FLUSH TABLES WITH READ LOCK语句加全局读锁(注意,是对任何表加表锁,只要使用了LOCK TABLES),会形成该事务隐式提交

  • 若是你开启了一个事务,而后在事务内使用UNLOCK TABLES语句,无效

  • 官方文档中还有一句:”若是有表使用LOCK TABLES语句加表锁,在使用UNLOCK TABLES语句解锁时会形成该表的全部事务隐式提交”,我的认为这是理论上的说法,或者说本人能力有限,暂未想到可能会形成这种状况的缘由,由于实际上使用LOCK TABLES语句语句时,开启一个事务会形成自动解锁(前面已经提到过),而若是在事务内使用LOCK TABLES语句会形成事务隐式提交(前面已经提到过),因此实际上不可能出如今事务内使用UNLOCK TABLES语句解锁LOCK TABLES语句的状况,而若是是使用FLUSH TABLES WITH READ LOCK语句,若是执行该语句以前存在LOCK TABLES加的表锁,则FLUSH TABLES WITH READ LOCK语句发生阻塞,若是是已经执行FLUSH TABLES WITH READ LOCK语句,LOCK TABLES语句发生阻塞,不会再有任何的表锁和互斥锁可以被获取到(新的非select和show的请求都会被阻塞)。因此不可能出现UNLOCK TABLES语句解锁时形成隐式提交

注:

  • FLUSH TABLES WITH READ LOCK语句不会阻塞日志表的写入,例如:查询日志,慢查询日志等
  • FLUSH TABLES WITH READ LOCK语句与XA协议不兼容
  • 若是一个会话中使用LOCK TABLES语句对某表加了表锁,在该表锁未释放前,那么另一个会话若是执行FLUSH TABLES WITH READ LOCK语句会被阻塞,而若是数据库中lock_wait_timeout参数设置时间过短,mysqldump将会由于执行FLUSH TABLES WITH READ LOCK语句获取全局读锁超时而致使备份失败退出
  • 若是一个会话正在执行DDL语句,那么另一个会话若是执行FLUSH TABLES WITH READ LOCK语句会被阻塞,若是数据库中lock_wait_timeout参数设置时间过短,mysqldump将会由于执行FLUSH TABLES WITH READ LOCK语句获取全局读锁超时而致使备份失败退出
  • 若是一个会话正在执行DML大事务(DML语句正在执行,数据正在发生修改,而不是使用lock in share mode和for update语句来显式加锁),那么另一个会话若是执行FLUSH TABLES WITH READ LOCK语句会被阻塞,若是数据库中lock_wait_timeout参数设置时间过短,mysqldump将会由于执行FLUSH TABLES WITH READ LOCK语句获取全局读锁超时而致使备份失败退出
  • FLUSH TABLES tbl_name [,tbl_name] … WITH READ LOCK

刷新表并获取指定表的读锁。该语句首先获取表的独占MDL锁,因此须要等待该表的全部事务提交完成。而后刷新该表的表缓存,从新打开表,获取表读锁(相似LOCK TABLES … READ),并将MDL锁从独占级别降级为共享。在该语句获取表读锁、降级MDL锁以后,其余会话能够读取该表,但不能修改表数据及其表结构。

执行该语句须要RELOAD和LOCK TABLES权限。

该语句仅适用于基表(持久表),不适用于临时表,会自动忽略,另外在对视图使用该语句使会报错。

与LOCK TABLES语句相似,在使用该语句对某表加锁以后,再同一个会话中开启一个事务时,会被自动解锁

MySQL5.7官方文档描述说:这种新的变体语法可以使得只针对某一个表加读锁的同时还可以同时刷新这个表,这解决了某表使用LOCK TABLES … READ语句加读锁时,须要刷新表不能使用FLUSH TABLES语句的问题,此时可使用FLUSH TABLES tbl_name [,tbl_name] … WITH READ LOCK语句代替,可是,官方描述不太清晰,实测在同一个会话中使用LOCK TABLES … READ语句加读锁时,不容许执行该语句(不管操做表是不是同一张表),会报错:ERROR 1192 (HY000): Can’t execute the given command because you have active locked tables or an active transaction,可是若是在不一样的会话中,那么,若是表不相同,容许执行,表相同,则FLUSH TABLES tbl_name [,tbl_name] … WITH READ LOCK语句发生等待

该语句同一个会话重复执行时,不管是否同一个表,都会报错:ERROR 1192 (HY000): Can't execute the given command because you have active locked tables or an active transactio,若是是不一样会话不一样表则容许执行,可是表相同则发生等待

1.2.2 修改隔离级别的做用

为何要执行SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ语句呢?由于后续须要使用START TRANSACTION /!40100 WITH CONSISTENT SNAPSHOT语句开启一个一致性事务快照,根据事务一致性读要求,一致性事务快照只支持RR隔离级别,在其余隔离级别下执行语句START TRANSACTION /!40100 WITH CONSISTENT SNAPSHOT会报以下警告信息:

'# RU、RC、串行隔离级别报同样的警告,告诉你WITH CONSISTENT SNAPSHOT子句被忽略,该子句只支持RR隔离级别'
root@localhost : (none) 02:54:15> show variables like '%isolation%';
+---------------+----------------+
| Variable_name | Value          |
+---------------+----------------+
| tx_isolation  | READ-COMMITTED |
+---------------+----------------+
1 row in set (0.00 sec)
root@localhost : (none) 02:54:35> START TRANSACTION WITH CONSISTENT SNAPSHOT;
Query OK, 0 rows affected, 1 warning (0.00 sec)
Warning (Code 138): InnoDB: WITH CONSISTENT SNAPSHOT was ignored because this phrase can only be used with REPEATABLE READ isolation level.

1.2.3 使用WITH CONSISTENT SNAPSHOT子句的做用

START TRANSACTION语句使用WITH CONSISTENT SNAPSHOT子句时,会为事务启动一致性读(该子句仅适用于InnoDB)。其行为与执行START TRANSACTION语句以后+一个SELECT语句效果相同(会获取一个事务号,在read view中占个坑,可是不会请求任何锁)。WITH CONSISTENT SNAPSHOT子句不会自动修改当前的事务隔离级别,因为WITH CONSISTENT SNAPSHOT子句要求必须RR隔离级别下才会自动启用,所以只有当前隔离级别为RR时才会启用一致性快照,非RR隔离级别下,会忽略WITH CONSISTENT SNAPSHOT子句。从MySQL 5.7.2起,当WITH CONSISTENT SNAPSHOT子句被忽略时,会产生一个警告(相似上一篇mysqldump与innobackupex备份过程你知多少(一)提到的警告信息)。

为了使得更清晰地了解mysqldump在备份过程当中使用WITH CONSISTENT SNAPSHOT子句的做用,下面我们来演示一下带与不带WITH CONSISTENT SNAPSHOT子句会发生什么?

  • 开启两个会话,操做同一张表






从上面的表格对比结果中能够看到:

  • WITH CONSISTENT SNAPSHOT子句的做用就至关于START TRANSACTION+ SELECT语句,目地是为了在开启事务的那一刻往mvcc的read view中当即加入这个事务,就好像read view在事务一开始就被固定了同样,使得后续其余事务的DML不会影响到该事务的查询结果,这就是所说的一致性读

  • 若是不使用WITH CONSISTENT SNAPSHOT子句,在使用START TRANSACTION语句显式开启一个事务以后,在执行SELECT语句以前,这段时间内若是有别的事务发起了DML操做,就会致使该事务查询该表的时候读取的数据与事务开始时间点不一致。

1.2.4 使用savepoint来设置回滚点的做用

你们都知道,设置SAVEPOINT是为了回滚在设置这个点时候发生变动的数据,可是mysqldump备份只是使用select语句作查询,为何要使用savepoint呢?须要回滚什么呢?请看下文分析:

  • SAVEPOINT 'identifier'语句,为事务设置一个命名的事务保存点(回滚点),该字符串为事务保存点的标识符。

  • ROLLBACK TO SAVEPOINT语句的做用是将事务回滚到指定的保存点的位置,而不终止事务。当前事务在回滚点以后的修改的行数据将被撤销(注:InnoDB不会释放这些发生修改且被撤销行的行锁,注意是修改,不是新插入,这些发生修改的数据行行锁被存储在内存中),对于设置了保存点以后,新插入的行数据也会被撤销(注:这些锁信息被存储在行数据中的事务ID上,这些行锁不会单独存储在内存中,在这种状况下,这些新插入的行数据在被回滚以后,对应的行锁将被释放)。另外,回滚到某个保存点以后,比这个保存点在时间上更晚设置的保存点将被删除。

  • ROLLBACK TO SAVEPOINT语句还有一个做用,能够释放在设置保存点以后事务持有的MDL锁,这点即是mysqldump须要使用保存点的关键点。

为了更清晰地了解mysqldump在备份过程当中使用SAVEPOINT sp + ROLLBACK TO SAVEPOINT sp语句的做用,下面使用两个会话演示一下使用与不使用保存点会发生什么?






从上面的对比结果中能够得知:

  • mysqldump使用savepoint的做用就是,当一个显式开启的事务回滚到保存点时,除了回滚数据变动以外,还会释放保存点以后select语句获取的MDL锁,使得其余会话的DDL语句能够正常执行。对于mysqldump来讲,select 语句执行完成以后就表明着该表的数据已经备份完成,无需再继续持有MDL锁,使用savepoint就实现了在select 执行完成以后释放MDL锁的目的(注:在事务内,执行select *语句虽然不会有数据行锁,可是会持有表的MDL锁)。

  • with consistent snapshot子句对应mysqldump实现一致性备份来讲相当重要,不只仅是数据的一致性,使用该子句时,表定义也保持事务开启的那一刻,因此,从上面的对比结果中能够看到,使用了with consistent snapshot子句开启一个一致性快照事务以后,若是一旦表结构定义发生改变,事务将没法重复查询表。

  • 从上面的演示过程当中,咱们也能够看到,使用 with consistent snapshot子句显式开启一个事务以后,若是该事务没有对任何表作任何操做时,此时是没有得到任何锁的,因此,若是在该事务对某表执行操做以前其余事务对该表执行了DDL操做以后,将致使该事务没法再对表执行查询,会报表结构发生变化的错误;固然,若是显式开启事务后当即对某表执行查询,那么其余会话的DDL是会发生阻塞的;当在该事务使用savepoint实现方式释放表的MDL锁以后,其余会话容许执行DDL,可是执行了DDL语句以后,该事务就没法再对该表执行查询。固然,若是不使用 with consistent snapshot子句,则其余会话执行的DDL对表定义的变动不会影响到该事务重复对表执行查询。

1.3 mysqldump有什么坑吗?

想必你们都知道,mysqldump备份时可使用--single-transaction + --master-data两个选项执行备份(老实讲,为图方便,本人以前很长一段时间,生产库也是使用mysqldudmp远程备份的),这样备份过程当中既能够尽可能不锁表,也能够获取到binlog pos位置,备份文件能够用于数据恢复,也能够用于搭建备库。看起来那么美好,然而……其实一不当心你就发现踩到坑了

1.3.1 坑一

使用--single-transaction + --master-data时,myisam表持续不断插入,并用于搭建备库。

首先在A库上把myisam表的数据行数弄到100W以上:

...... root@localhost : luoxiaobo 11:26:42> insert into t_luoxiaobo2(test,datet_time) select test,now() from t_luoxiaobo2; Query OK, 1572864 rows affected (4.47 sec) Records: 1572864 Duplicates: 0 Warnings: 0 root@localhost : luoxiaobo 11:26:47> select count(*) from t_luoxiaobo2; +----------+ | count(*) | +----------+ | 3145728 | +----------+ 1 row in set (0.00 sec)

A库新开一个ssh会话2,使用以下脚本持续对表t_luoxiaobo2进行插入操做(该表为myisam表),限于篇幅,请点击此处获取。

A库新开一个ssh会话3,清空查询日志:

[root@localhost ~]# echo > /home/mysql/data/mysqldata1/mydata/localhost.log 

如今,A库在ssh会话3中,使用mysqldump备份整个实例:

[root@localhost ~]# mysqldump -h 192.168.2.111 -uadmin -pletsg0 --single-transaction --master-data=2 --triggers --routines --events -A >\ backup_`date +%F_%H_%M_%S`.sql mysqldump: [Warning] Using a password on the command line interface can be insecure. [root@localhost ~]# ls -lh backup_2017-07-03_00_47_50.sql -rw-r--r-- 1 root root 112M 7月 3 00:47 backup_2017-07-03_00_47_50.sql

备份完成以后,A库在ssh会话2中,中止持续造数脚本。

A库在ssh会话2中,查看备份文件中的binlog pos:

[root@localhost ~]# head -100 backup_*.sql |grep -i 'change master to' -- CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000005', MASTER_LOG_POS=76657819;

A库在ssh会话3中,查看查询日志,能够发如今UNLOCK TABLES以后,select *…t_luoxiaobo2表以前,还有数据插入到该表中:

2017-07-03T00:47:50.366670+08:00 87364 Query SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ 2017-07-03T00:47:50.366795+08:00 87363 Query insert into t_luoxiaobo2(test,datet_time) values(11377,now()) 2017-07-03T00:47:50.366862+08:00 87364 Query START TRANSACTION /*!40100 WITH CONSISTENT SNAPSHOT */ 2017-07-03T00:47:50.367023+08:00 87364 Query SHOW VARIABLES LIKE 'gtid\_mode' 2017-07-03T00:47:50.372331+08:00 87364 Query SELECT @@GLOBAL.GTID_EXECUTED 2017-07-03T00:47:50.372473+08:00 87364 Query SHOW MASTER STATUS 2017-07-03T00:47:50.372557+08:00 87364 Query UNLOCK TABLES ...... 2017-07-03T00:47:50.381256+08:00 87366 Query insert into t_luoxiaobo2(test,datet_time) values(11383,now()) ...... 2017-07-03T00:47:50.381577+08:00 87365 Query insert into t_luoxiaobo2(test,datet_time) values(11380,now()) 2017-07-03T00:47:50.381817+08:00 87360 Init DB luoxiaobo 2017-07-03T00:47:50.381886+08:00 87360 Query insert into t_luoxiaobo2(test,datet_time) values(11382,now()) ...... 2017-07-03T00:47:50.391873+08:00 87364 Query show fields from `t_luoxiaobo2` 2017-07-03T00:47:50.392116+08:00 87364 Query show fields from `t_luoxiaobo2` 2017-07-03T00:47:50.392339+08:00 87364 Query SELECT /*!40001 SQL_NO_CACHE */ * FROM `t_luoxiaobo2`

如今,咱们将这个备份文件用于B库上搭建备库,并启动复制,能够发现有以下复制报错:

root@localhost : (none) 12:59:12> show slave status\G; *************************** 1\. row *************************** Slave_IO_State: Waiting for master to send event Master_Host: 192.168.2.111 Master_User: qfsys Master_Port: 3306 Connect_Retry: 60 Master_Log_File: mysql-bin.000005 Read_Master_Log_Pos: 79521301 Relay_Log_File: mysql-relay-bin.000002 Relay_Log_Pos: 320 Relay_Master_Log_File: mysql-bin.000005 Slave_IO_Running: Yes Slave_SQL_Running: No ...... Last_SQL_Errno: 1062 Last_SQL_Error: Could not execute Write_rows event on table luoxiaobo.t_luoxiaobo2; Duplicate entry '6465261' for key 'PRIMARY', Error_code: 1062;\ handler error HA_ERR_FOUND_DUPP_KEY; the event's master log mysql-bin.000005, end_log_pos 76658175 ...... ERROR: No query specified

从上面的结果中能够看到,主键冲突了,也就是说备份的表t_luoxiaobo2中的数据与备份文件中获取的binlog pos点并不一致,我们如今在B库中,查询一下这个表中大于等于这个冲突主键的数据,从下面的结果中能够看到,备份文件中若是严格按照一致性要求,备份文件中的数据必须和binlog pos点一致,可是如今,备份文件中的数据却比获取的binlog pos点多了5行数据:

root@localhost : (none) 12:59:24> use luoxiaobo Database changed root@localhost : luoxiaobo 12:59:44> select id from t_luoxiaobo2 where id>=6465261; +---------+ | id | +---------+ | 6465261 | | 6465263 | | 6465265 | | 6465267 | | 6465269 | +---------+ 5 rows in set (0.01 sec)

如今,我们去掉--single-transaction选项,从新执行本小节以上步骤,从新搭建从库,看看是否还有问题(这里限于篇幅,步骤省略,只贴出最后结果):

root@localhost : (none) 01:09:12> change master to master_host='192.168.2.111',master_user='qfsys',master_password='letsg0',master_log_file='mysql-bin.000005',\ master_log_pos=83601517; Query OK, 0 rows affected, 2 warnings (0.02 sec) Note (Code 1759): Sending passwords in plain text without SSL/TLS is extremely insecure. Note (Code 1760): Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider \ using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information. root@localhost : (none) 01:09:23> start slave; Query OK, 0 rows affected (0.01 sec) root@localhost : (none) 01:09:25> show slave status\G; *************************** 1\. row *************************** Slave_IO_State: Waiting for master to send event Master_Host: 192.168.2.111 Master_User: qfsys Master_Port: 3306 Connect_Retry: 60 Master_Log_File: mysql-bin.000005 Read_Master_Log_Pos: 84697699 Relay_Log_File: mysql-relay-bin.000002 Relay_Log_Pos: 1096502 Relay_Master_Log_File: mysql-bin.000005 Slave_IO_Running: Yes Slave_SQL_Running: Yes ...... Exec_Master_Log_Pos: 84697699 ......

从上面的show slave status输出信息中咱们能够看到,去掉了--single-transaction选项以后的备份,用于搭建备库就正常了。另外,咱们从新在A库上查看查询日志也能够发现,只搜索到flush语句而没有搜索到unlock tables、set session transaction.. 、start transaction.. 语句,说明备份过程没有开启一致性快照事务,没有修改隔离级别,是全程加全局读锁的,mysqldump备份进程结束退出以后mysql server自动回收锁资源:

[root@localhost ~]# grep -iE 'flush|unlock tables|transaction' /home/mysql/data/mysqldata1/mydata/localhost.log 2017-07-03T01:07:08.195470+08:00 102945 Query FLUSH /*!40101 LOCAL */ TABLES 2017-07-03T01:07:08.206607+08:00 102945 Query FLUSH TABLES WITH READ LOCK

也许你会说,咱们数据库环境很规范,没有myisam表,不会有这个问题,OK,赞一个。

1.3.2 坑二

使用--single-transaction + --master-data时,InnoDB表执行online ddl,备份文件用于搭建备库(注意,本小节中的数据库实例与前一小节不一样)。

此次咱们操做InnoDB表,在A库上先把t_luoxiaobo表的数据也弄到几百万行。

qogir_env@localhost : luoxiaobo 10:03:35> insert into t_luoxiaobo(test,datet_time) select test,now() from t_luoxiaobo; Query OK, 1048576 rows affected (9.83 sec) Records: 1048576 Duplicates: 0 Warnings: 0 qogir_env@localhost : luoxiaobo 10:03:46> select count(*) from t_luoxiaobo; +----------+ | count(*) | +----------+ | 2097152 | +----------+ 1 row in set (0.39 sec)

A库在ssh会话2中,使用以下脚本持续对表t_luoxiaobo进行DDL操做(该表为InnoDB表),限于篇幅,请点击此处获取。

A库在ssh会话3中,清空查询日志:

[root@localhost ~]# echo > /home/mysql/data/mysqldata1/mydata/localhost.log 

如今,A库在ssh会话3中,使用mysqldump备份整个实例:

[root@localhost ~]# mysqldump -h 192.168.2.111 -uadmin -pletsg0 --single-transaction --master-data=2 --triggers --routines --events -A > backup_`date +%F_%H_%M_%S`.sql mysqldump: [Warning] Using a password on the command line interface can be insecure. [root@5f1772e3-0c7a-4537-97f9-9b57cf6a04c2 ~]# ls -lh backup_2017-07-03_12_46_49.sql -rw-r--r-- 1 root root 74M Jul 3 12:46 backup_2017-07-03_12_46_49.sql

A库在ssh会话2中,中止DDL添加脚本。

A库在ssh会话2中,查看备份文件中的binlog pos:

[root@5f1772e3-0c7a-4537-97f9-9b57cf6a04c2 ~]# head -100 backup_*.sql |grep -i 'change master to' -- CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000257', MASTER_LOG_POS=62109599;

如今,咱们将这个备份文件用于在B库中搭建备库,并启动复制,从下面的结果中能够看到,复制状态正常:

qogir_env@localhost : (none) 01:32:00> show slave status\G; ...... Master_Log_File: mysql-bin.000257 Read_Master_Log_Pos: 62110423 Relay_Log_File: mysql-relay-bin-channel@002d241.000002 Relay_Log_Pos: 1144 Relay_Master_Log_File: mysql-bin.000257 Slave_IO_Running: Yes Slave_SQL_Running: Yes ...... Exec_Master_Log_Pos: 62110423 ...... Seconds_Behind_Master: 0 ...... Retrieved_Gtid_Set: 799ef59c-4126-11e7-83ce-00163e407cfb:53831090-53831093 Executed_Gtid_Set: 799ef59c-4126-11e7-83ce-00163e407cfb:1-53831093, f9b1a9b6-46b7-11e7-9e8b-00163e4fde29:1 Auto_Position: 0 ......

如今咱们回到A库上,对表t_luoxiaobo插入一些测试数据:

qogir_env@localhost : luoxiaobo 12:43:30> insert into t_luoxiaobo(test,datet_time) values('test_replication',now()); Query OK, 1 row affected (0.00 sec) qogir_env@localhost : luoxiaobo 01:36:31> select * from t_luoxiaobo where test='test_replication'; +---------+------------------+---------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ | id | test | datet_time | test1 | test2 | test3 | test4 | test5 | test6 | test8 | test7 | test9 | +---------+------------------+---------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ | 7470943 | test_replication | 2017-07-03 13:36:31 | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | +---------+------------------+---------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ 1 row in set (0.96 sec)

在B库上查询复制状态和表t_luoxiaobo中的数据:

qogir_env@localhost : luoxiaobo 01:32:21> show slave status\G; ...... Master_Log_File: mysql-bin.000257 Read_Master_Log_Pos: 62110862 Relay_Log_File: mysql-relay-bin-channel@002d241.000002 Relay_Log_Pos: 1583 Relay_Master_Log_File: mysql-bin.000257 Slave_IO_Running: Yes Slave_SQL_Running: Yes ...... Exec_Master_Log_Pos: 62110862 ...... Seconds_Behind_Master: 0 ...... Retrieved_Gtid_Set: 799ef59c-4126-11e7-83ce-00163e407cfb:53831090-53831094 Executed_Gtid_Set: 799ef59c-4126-11e7-83ce-00163e407cfb:1-53831094, f9b1a9b6-46b7-11e7-9e8b-00163e4fde29:1 ...... 1 row in set (0.00 sec) qogir_env@localhost : luoxiaobo 01:38:23> select * from t_luoxiaobo where test='test_replication'; +---------+------------------+---------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ | id | test | datet_time | test1 | test2 | test3 | test4 | test5 | test6 | test8 | test7 | test9 | +---------+------------------+---------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ | 7470943 | test_replication | 2017-07-03 13:36:31 | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | +---------+------------------+---------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ 1 row in set (0.05 sec)

到这里,看起来一切正常,对不对?开心吗?先等等,请保持DBA一向严谨的优良传统,我们在主库上使用pt-table-checksum工具检查一下:

# 删除了一部分无用信息,只保留了咱们以前造数的两张表 [root@5f1772e3-0c7a-4537-97f9-9b57cf6a04c2 ~]# pt-table-checksum --nocheck-replication-filters --no-check-binlog-format --replicate=xiaoboluo.checksums h=localhost,u=admin,p=letsg0,P=3306 TS ERRORS DIFFS ROWS CHUNKS SKIPPED TIME TABLE 07-03T13:57:48 0 16 2097153 18 0 7.543 luoxiaobo.t_luoxiaobo 07-03T13:57:57 0 0 2097152 18 0 9.281 luoxiaobo.t_luoxiaobo2 ......

从上面的信息中能够看到,表luoxiaobo.t_luoxiaobo的检测DIFFS 列为16,表明主从有数据差别,神马状况?别急,我们先来分别在AB库查询下这张表的数据行数,从下面的结果能够看到,该表主从数据差别2097152行!!

# A库 qogir_env@localhost : (none) 01:57:03> use luoxiaobo Database changed qogir_env@localhost : luoxiaobo 02:09:40> select count(*) from t_luoxiaobo; +----------+ | count(*) | +----------+ | 2097153 | +----------+ 1 row in set (0.41 sec) B库 qogir_env@localhost : (none) 01:55:28> use luoxiaobo Database changed qogir_env@localhost : luoxiaobo 02:10:30> select count(*) from t_luoxiaobo; +----------+ | count(*) | +----------+ | 1 | +----------+ 1 row in set (0.00 sec)

发生什么了?也许你会说,平时使用mysqldump不都是这样的吗?没毛病啊。回想一下,从我们上篇《mysqldump与innobackupex备份过程知多少(二)》中提到的“WITH CONSISTENT SNAPSHOT语句的做用”时的演示过程能够知道,DDL的负载是刻意加上去的,还记得以前演示mysqldump使用savepoint的做用的时候,使用start transaction with consistent snapshot语句显式开启一个事务以后,该事务执行select以前,该表被其余会话执行了DDL以后没法查询数据,咱们知道mysqldump备份数据的时候,就是在start transaction with consistent snapshot语句开启的一个一致性快照事务下使用select语句查询数据进行备份的。为了证明这个问题,下面咱们打开查询日志查看一下在start transaction with consistent snapshot语句和select … 之间是否有DDL语句,以下:

.......

2017-07-03T12:46:57.082727+08:00 1649664 Query SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ 2017-07-03T12:46:57.082889+08:00 1649664 Query START TRANSACTION /*!40100 WITH CONSISTENT SNAPSHOT */ ...... 2017-07-03T12:46:57.085821+08:00 1649664 Query SELECT @@GLOBAL.GTID_EXECUTED 2017-07-03T12:46:57.085954+08:00 1649664 Query SHOW MASTER STATUS ...... ' # 这里能够看到,在START TRANSACTION /*!40100 WITH CONSISTENT SNAPSHOT */语句以后,select 备份表t_luoxiaobo表以前,有一个DDL语句插入进来' 2017-07-03T12:46:57.089833+08:00 1649667 Query alter table t_luoxiaobo add column test8 varchar(10) 2017-07-03T12:46:57.095153+08:00 1649664 Query UNLOCK TABLES ...... 2017-07-03T12:46:57.098199+08:00 1649664 Init DB luoxiaobo 2017-07-03T12:46:57.098273+08:00 1649664 Query SHOW CREATE DATABASE IF NOT EXISTS `luoxiaobo` 2017-07-03T12:46:57.098360+08:00 1649664 Query SAVEPOINT sp 2017-07-03T12:46:57.098435+08:00 1649664 Query show tables 2017-07-03T12:46:57.098645+08:00 1649664 Query show table status like 't\_luoxiaobo' 2017-07-03T12:46:57.098843+08:00 1649664 Query SET SQL_QUOTE_SHOW_CREATE=1 2017-07-03T12:46:57.098927+08:00 1649664 Query SET SESSION character_set_results = 'binary' 2017-07-03T12:46:57.099013+08:00 1649664 Query show create table `t_luoxiaobo` 2017-07-03T12:46:57.105056+08:00 1649664 Query SET SESSION character_set_results = 'utf8' 2017-07-03T12:46:57.105165+08:00 1649664 Query show fields from `t_luoxiaobo` 2017-07-03T12:46:57.105538+08:00 1649664 Query show fields from `t_luoxiaobo` '# 这里本来应该是一句:SELECT /*!40001 SQL_NO_CACHE */ * FROM `t_luoxiaobo`,可是却没有,咱们能够推理一下,由于select的时候报了表定'\ '# 义已经发生变化的错误,因此这句select并无被记录到查询日志中来' 2017-07-03T12:46:57.105857+08:00 1649664 Query SET SESSION character_set_results = 'binary' 2017-07-03T12:46:57.105929+08:00 1649664 Query use `luoxiaobo` 2017-07-03T12:46:57.106021+08:00 1649664 Query select @@collation_database 2017-07-03T12:46:57.106116+08:00 1649664 Query SHOW TRIGGERS LIKE 't\_luoxiaobo' 2017-07-03T12:46:57.106394+08:00 1649664 Query SET SESSION character_set_results = 'utf8' 2017-07-03T12:46:57.106466+08:00 1649664 Query ROLLBACK TO SAVEPOINT sp 2017-07-03T12:46:57.106586+08:00 1649664 Query show table status like 't\_luoxiaobo2' ...... 2017-07-03T12:46:57.107063+08:00 1649664 Query show create table `t_luoxiaobo2` 2017-07-03T12:46:57.107151+08:00 1649664 Query SET SESSION character_set_results = 'utf8' 2017-07-03T12:46:57.107233+08:00 1649664 Query show fields from `t_luoxiaobo2` 2017-07-03T12:46:57.107511+08:00 1649664 Query show fields from `t_luoxiaobo2` 2017-07-03T12:46:57.107807+08:00 1649664 Query SELECT /*!40001 SQL_NO_CACHE */ * FROM `t_luoxiaobo2` ......

如今,咱们打开备份文件,找到表t_luoxiaob的备份语句位置,能够看到并无生成INSERT语句:

DROP TABLE IF EXISTS `t_luoxiaobo`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `t_luoxiaobo` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `test` varchar(50) COLLATE utf8_bin DEFAULT NULL, `datet_time` datetime DEFAULT NULL, `test1` varchar(10) COLLATE utf8_bin DEFAULT NULL, `test2` varchar(10) COLLATE utf8_bin DEFAULT NULL, `test3` varchar(10) COLLATE utf8_bin DEFAULT NULL, `test4` varchar(10) COLLATE utf8_bin DEFAULT NULL, `test5` varchar(10) COLLATE utf8_bin DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=7470943 DEFAULT CHARSET=utf8 COLLATE=utf8_bin; /*!40101 SET character_set_client = @saved_cs_client */; '# 正常状况下,在这个位置,应该出现LOCK TABLES `t_luoxiaobo` WRITE; + /*!40000 ALTER TABLE `t_luoxiaobo2` DISABLE KEYS */; + INSERT INTO语句的,然而,如今这里倒是空的' -- -- Table structure for table `t_luoxiaobo2` -- DROP TABLE IF EXISTS `t_luoxiaobo2`; ...... LOCK TABLES `t_luoxiaobo2` WRITE; /*!40000 ALTER TABLE `t_luoxiaobo2` DISABLE KEYS */; INSERT INTO `t_luoxiaobo2` VALUES (1,'1','2017-07-03 09:22:16'),(4,'2','2017-07-03 09:22:19'),(7,'3','2017-07-03 09:22:21'), ......

到这里,是否是忽然心弦一紧呢?so..若是你决定继续使用mysqldump,那么之后搭建好备库以后,必定要记得校验一下主备数据一致性!!

1.3.3 有办法改善这这些问题吗?

在寻找解决办法以前,我们先来看看mysqldump的备份选项--single-transaction--master-data[=value]的做用和使用限制。

  • --single-transaction

此选项将事务隔离模式设置为REPEATABLE READ,并在备份数据以前向server发送START TRANSACTION SQL语句以显示开启一个事务快照。仅适用于InnoDB这样的事务表,因为是在事务快照内进行备份,这样可使得备份的数据与获取事务快照时的数据是一致的,并且不会阻塞任何应用程序对server的访问。

在进行单事务备份时,为确保有效的备份文件(正确的表内容和二进制日志位置),不能有其余链接应使用语句:ALTER TABLE,CREATE TABLE,DROP TABLE,RENAME TABLE,TRUNCATE等DDL语句。这会致使一致状态被破坏,可能致使mysqldump执行SELECT检索表数据时查询到不正确的内容或备份失败 。

注意:该选项仅适用于事务引擎表,对于MyISAM或MEMORY表因为不支持事务,因此备份过程当中这些引擎表的数据仍可能发生更改。

  • --master-data[=value]

使用此选项备份时会在备份文件中生成change master to语句,使用的binlog pos是使用的备份server本身的binlog pos,可以使用备份文件用于将另外一台服务器(恢复这个备份文件的服务器)设置为备份server的从库。

--dump-slave选项相似,若是选项值为2,则CHANGE MASTER TO语句将做为SQL注释写入备份文件,所以仅供参考;当备份文件被从新加载时,这个注释不起做用。若是选项值为1,则该语句不会注释,并在从新加载备份文件时会生效(被执行)。若是未指定选项值,则默认值为1。

指定此选项的用户须要RELOAD权限,而且server必须启用二进制日志,由于这个位置是使用show master status获取的(若是没有开启log_bin参数,则show master status输出信息为空),而不是使用show slave status获取的。

--master-data选项自动关闭--lock-tables选项。同时还会打开--lock-all-tables,除非指定了--single-transaction选项,在指定了--single-transaction选项以后,只有在备份开始时间内才加全局读取锁。

so,--single-transaction选项中明确说明了若是使用了该选项,那么在备份期间若是发生DDL,则可能致使备份数据一致性被破坏,select检索不到正确的内容。另外,该选项仅仅只适用于事务引擎表,不适用于非事务引擎。做为DBA,不少时候是很是无奈的,虽然有各类规范,可是保不齐就是有lo漏网之鱼,这个时候,生活还得继续,工做还得作好, 那么,有什么办法能够缓解这个问题吗?有的:

1) 就如同上文中演示步骤中那样,去掉--single-transaction选项进行备份,此时单独使用--master-data选项时会自动开启--lock-all-tables,备份过程当中整个实例全程锁表,不会发生备份数据与获取的binlog pos点不一致的问题,这样,用该备份来搭建备库时就不会出现数据冲突。可是问题显而易见,备份期间数据库不可用,若是采用这种方法,至少须要在业务低峰期进行备份
2) 使用innobackupex备份工具

二、如今看innobackupex

2.1. innobackupex备份过程解读

  • A库清空查询日志
[root@localhost ~]# echo > /home/mysql/data/mysqldata1/mydata/localhost.log 
  • 为了更清晰地追踪innobackupex是如何拷贝redo log的,咱们在A库新开一个ssh会话2,使用以下脚本持续对表t_luoxiaobo进行插入操做(该表为innodb表),限于篇幅,请到以下为知笔记连接获取

  • http://5d096a11.wiz03.com/share/s/1t2mEh0a-kl_2c2NZ33kSiac1Rgvxq1vgkhL21ibWU2cLidk

  • A库使用innobackupex执行备份,使用strace命令抓取备份过程当中的调用栈

[root@5f1772e3-0c7a-4537-97f9-9b57cf6a04c2 ~]# rm -rf /data/backup/full/* '# 注意:strace必须加-f选项,不然fork线程的调用栈打印不出来,由于innobackupex备份时是单进程多线程的的方式执行备份的' [root@5f1772e3-0c7a-4537-97f9-9b57cf6a04c2 ~]# strace -f innobackupex --defaults-file=/home/mysql/conf/my1.cnf --user=admin --password=letsg0\ --no-timestamp /data/backup/full 2> a.txt [root@5f1772e3-0c7a-4537-97f9-9b57cf6a04c2 ~]# 
  • 查看general_log日志中的记录(删掉了加压脚本中的语句)
[root@5f1772e3-0c7a-4537-97f9-9b57cf6a04c2 ~]# cat /home/mysql/data/mysqldata1/mydata/localhost.log ...... 2017-07-04T09:58:21.037311Z 6842 Query set autocommit=1 2017-07-04T09:58:21.037916Z 6842 Query SET SESSION wait_timeout=2147483 2017-07-04T09:58:21.038551Z 6842 Query SELECT CONCAT(@@hostname, @@port) 2017-07-04T09:58:21.043617Z 6843 Query SET SESSION wait_timeout=2147483 2017-07-04T09:58:21.043870Z 6843 Query SHOW VARIABLES 2017-07-04T09:58:21.050047Z 6843 Query SHOW ENGINE INNODB STATUS 2017-07-04T09:58:34.949224Z 6843 Query SET SESSION lock_wait_timeout=31536000 2017-07-04T09:58:34.949820Z 6843 Query FLUSH NO_WRITE_TO_BINLOG TABLES 2017-07-04T09:58:34.950331Z 6843 Query FLUSH TABLES WITH READ LOCK 2017-07-04T09:58:38.117764Z 6843 Query SHOW MASTER STATUS 2017-07-04T09:58:38.118012Z 6843 Query SHOW VARIABLES 2017-07-04T09:58:38.123193Z 6843 Query FLUSH NO_WRITE_TO_BINLOG ENGINE LOGS 2017-07-04T09:58:38.327210Z 6843 Query UNLOCK TABLES 2017-07-04T09:58:38.329734Z 6843 Query SELECT UUID() 2017-07-04T09:58:38.330003Z 6843 Query SELECT VERSION() 2017-07-04T09:58:38.539340Z 6843 Quit ...... 
  • 从上面的记录中能够看到,与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为何须要这么作

  • innobackupex备份时启动一个进程多个线程,经过拷贝磁盘文件实现物理备份,为了保证备份数据的一致性,须要在备份过程当中恰当的时机发送一些加锁解锁语句与数据库实例进行交互,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备份,留意日志打印过程。从下面的结果中,咱们能够看到报错终止了

[root@localhost ~]# innobackupex --defaults-file=/home/mysql/conf/my1.cnf --user=admin --password=letsg0 --no-timestamp /data/backup/full ...... 170705 14:47:21 >> log scanned up to (129385440) 170705 14:47:21 [01] ...done 170705 14:47:21 [01] Copying ./luoxiaobo/t_luoxiaobo.ibd to /data/backup/full/luoxiaobo/t_luoxiaobo.ibd #从这里能够看到,正在备份t_luoxiaobo表 170705 14:47:22 >> log scanned up to (129385507) '#这里能够看到,scanned lsn的时候,发现了DDL作的修改,报错了,告诉你DDL已经执行,将没法保证备份数据的一致性' InnoDB: Last flushed lsn: 129385032 load_index lsn 129385558 [FATAL] InnoDB: An optimized(without redo logging) DDLoperation has been performed. All modified pages may not have been flushed to the disk yet. PXB will not be able take a consistent backup. Retry the backup operation 2017-07-05 14:47:23 0x7f9125365700 InnoDB: Assertion failure in thread 140261371303680 in file ut0ut.cc line 916 InnoDB: We intentionally generate a memory trap. InnoDB: Submit a detailed bug report to http://bugs.mysql.com. InnoDB: If you get repeated assertion failures or crashes, even InnoDB: immediately after the mysqld startup, there may be InnoDB: corruption in the InnoDB tablespace. Please refer to InnoDB: http://dev.mysql.com/doc/refman/5.7/en/forcing-innodb-recovery.html InnoDB: about forcing recovery. 06:47:23 UTC - xtrabackup got signal 6 ; This could be because you hit a bug or data is corrupted. This error can also be caused by malfunctioning hardware. Attempting to collect some information that could help diagnose the problem. As this is a crash and something is definitely wrong, the information collection process might fail. Thread pointer: 0x0 Attempting backtrace. You can use the following information to find out where mysqld died. If you see no messages after this, something went terribly wrong.. ...... 
  • 发生什么了?

  • 首先,咱们知道,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备份期间,在数据库中建立的链接不要误杀,不然备份失败

三、总结

相关文章
相关标签/搜索