【转】mysqldump与innobackupex知多少
作者:罗小波
【目录】
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. 总结
导读
想必搞数据库的都知道:
-
mysqldump优点:mysqldump的优点就是逻辑备份,把数据生成SQL形式保存,在单库,单表数据迁移,备份恢复等场景方便,SQL形式的备份文件通用,也方便在不同数据库之间移植。对于InnoDB表可以在线备份。
-
mysqldump缺点:mysqldump是单线程,数据量大的时候,备份时间长,甚至有可能在备份过程中非事务表长期锁表对业务造成影响(SQL形式的备份恢复时间也比较长)。mysqldump备份时会查询所有的数据,这可能会把内存中的热点数据刷掉
-
innobackupex优点:物理备份可以绕过MySQL Server层,加上本身就是文件系统级别的备份,备份速度块,恢复速度快,可以在线备份,支持并发备份,支持加密传输,支持备份限速
-
innobackupex缺点:要提取部分库表数据比较麻烦,不能按照基于时间点来恢复数据,并且不能远程备份,只能本地备份,增量备份的恢复也比较麻烦。如果使用innobackupex的全备+binlog增量备份就可以解决基于时间点恢复的问题。
要查看备份过程中这俩备份工具都对数据库做了什么操作,想必大家都知道:可以打开general_log
来查。那么问题来了,general_log
输出的信息都代表什么?如果不这样做会怎样?这两个备份工具会不会有什么平时被忽略的坑?请看下文分析,也许……你会发现原来之前对这俩备份工具好像也不是那么了解!
环境信息
-
服务器配置:
- 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_updates
,log-bin
,binlog_rows_query_log_events=ON
,server-id=3306111
,gtid_mode=ON
,enforce_gtid_consistency=ON
,auto_increment_increment=2
,auto_increment_offset=1
- 备库:
双一
,log_slave_updates
,log-bin
,binlog_rows_query_log_events=ON
,server-id=3306121
,gtid_mode=ON
,enforce_gtid_consistency=ON
,auto_increment_increment=2
,auto_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备份工具
2、现在看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备份期间,在数据库中创建的连接不要误杀,否则备份失败
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/log-destinations.html
-
PS:本博客系个人博客,所发布大部分内容会延后于微信公众号"沃趣科技"和"DBGeeK" 以及其他任何与沃趣科技有合作的网站、公众号 发布,另外,本文为markdown格式编写,贴上博客园解析出来的格式实在有些惨不忍睹,有些地方博客园的编辑器无法支持的markdown格式使用了图片代替,图片字有点小,不得不吐槽一下,见谅!