注册 登录
  • 欢迎访问"运维那点事",推荐使用Google浏览器访问,可以扫码关注本站的"微信公众号"。
  • 如果您觉得本站对你有帮助,那么可以扫码捐助以帮助本站更好地发展。

MySQL备份恢复:单线程mysqldump工具原理

MySQL 彭东稳 1239次浏览 已收录 0个评论

一、mysqldump工作原理

对于搞数据库的应该都知道,mysqldump是MySQL官方提供的用于逻辑备份的工具。

  • mysqldump优点:mysqldump的优点就是逻辑备份,把数据生成sql形式保存,在单库,单表数据迁移,备份恢复等场景方便,sql形式的备份文件通用,也方便在不同数据库之间移植。对于innodb表可以在线备份。
  • mysqldump缺点:mysqldump是单线程,数据量大的时候,备份时间长,甚至有可能在备份过程中非事务表长期锁表对业务造成影响(sql形式的备份恢复时间也比较长)。mysqldump备份时会查询所有的数据,这可能会把内存中的热点数据刷掉。

通常,使用mysqldump备份期间,为了使得数据库中加锁时间尽量短。在使用mysqldump时基本都会用到这两个关键的参数,第一个是--single-transaction,加上这个参数,表示在开始备份前开启一个一致性快照事务,以此来获取一致性备份,该参数仅对InnoDB存储引擎有效;第二个是--master-data=2,主要用于记录一致性备份的binlog position位点。理解mysqldump工作原理,一定要将事务表(InnoDB)和非事务表(比如myisam)区别对待,因为备份的流程与此息息相关。而且,到目前为止,我们也无法规避myisam表,即使我们的所有业务表都是InnoDB,因为mysql库中系统表仍然采用的myisam表。

为了一探mysqldump整个备份流程的原理,打开general_log就可以知道了。如下备份语句。

从general_log中可以得到整个备份的基本流程如下:

从上面的general_log我们知道了mysqldump备份过程中的关键步骤。那么。。问题来了,mysqldump备份过程中为什么需要这些步骤?不这么做会怎样?下面对这些步骤逐一使用演示步骤进行详细解释。

1. 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语句会被阻塞。

2. 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 TABLE WITH READ LOCK

3. 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,如果是不同会话不同表则允许执行,但是表相同则发生等待。

4. SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ

为什么要执行SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ语句呢?因为后续需要使用START TRANSACTION WITH CONSISTENT SNAPSHOT语句开启一个一致性事务快照,根据事务一致性读要求,一致性事务快照只支持RR隔离级别,在其他隔离级别下执行语句START TRANSACTION  WITH CONSISTENT SNAPSHOT会报“ InnoDB: WITH CONSISTENT SNAPSHOT was ignored because this phrase can only be used with REPEATABLE READ isolation level.”警告信息。

5. START TRANSACTION  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在备份过程中使用WITH CONSISTENT SNAPSHOT子句的作用,下面咱们来演示一下带与不带WITH CONSISTENT SNAPSHOT子句会发生什么?

# Session A

# Session B

# Session A

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

# Session A

# Session B

# Session A

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

6. 使用savepoint来设置回滚点的作用

大家都知道,设置SAVEPOINT是为了回滚在设置这个点时候发生变更的数据,但是mysqldump备份只是使用select语句做查询,为什么要使用savepoint呢?需要回滚什么呢?请看下文分析:

  • SAVEPOINT ‘identifier’ 语句,为事务设置一个命名的事务保存点(回滚点),该字符串为事务保存点的标识符。
  • ROLLBACK TO SAVEPOINT 语句的作用是将事务回滚到指定的保存点的位置,而不终止事务。当前事务在回滚点之后的修改的行数据将被撤销(注:InnoDB不会释放这些发生修改且被撤销行的行锁,注意是修改,不是新插入,这些发生修改的数据行行锁被存储在内存中),对于设置了保存点之后,新插入的行数据也会被撤销(注:这些锁信息被存储在行数据中的事务ID上,这些行锁不会单独存储在内存中,在这种情况下,这些新插入的行数据在被回滚之后,对应的行锁将被释放)。另外,回滚到某个保存点之后,比这个保存点在时间上更晚设置的保存点将被删除。
  • ROLLBACK TO SAVEPOINT 语句还有一个作用,可以释放在设置保存点之后事务持有的MDL锁,这点便是mysqldump需要使用保存点的关键点。

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

不使用WITH CONSISTENT SNAPSHOT子句,不使用SAVEPOINT,Session A显式开启一个事务之后先执行查询,Session B执行一个DDL

# Session A

# Session B

# Session A

此时,除非Session A中的事务结束(rollback或commit),否则Session A没有办法释放select获取的MDL锁;Session B会一直等待。但是,mysqldump的备份过程是一个大事务,所有支持事务的表都在这个大事务中进行读取数据的,如果一旦回滚或提交事务,就会导致备份无法获得一致性。这个时候,保存点的作用就出来了。

不使用WITH CONSISTENT SNAPSHOT子句,使用SAVEPOINT,Session A显式开启一个事务之后先执行查询,Session B执行一个DDL

# Session A

# Seesion B

# Session A

当Session A回滚了保存点之后,会发现Session B的DDL立马执行成功了。由于我们select是在保存点中操作的,所以当保存点被回滚后,select的DML锁自然也就释放了。

不使用WITH CONSISTENT SNAPSHOT子句,使用SAVEPOINT,Session A显式开启一个事务之后先不执行查询,Session B执行一个DDL

# Session A

# Session B

# Session A

可以看到DDL语句在Session A开启SAVEPOINT之后立即执行,则执行成功。并且Session A在保存点中执行查询成功了,得到的字段信息就是Session B删除后的字段信息。

使用WITH CONSISTENT SNAPSHOT子句,使用SAVEPOINT,Session A显式开启一个事务之后先不执行查询,Session B执行一个DDL

# Session A

# Session B

# Session A

可以看到使用WITH CONSISTENT SNAPSHOT子句后,Session B虽然执行DDL成功了,但是Session A无法再读取数据了,报了一个表定义被改变了,请重新尝试启动事务,此时你可以提交或滚回此事务后再次开启事务。

从上面的对比结果中可以得知:

  • 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对表定义的变更不会影响到该事务重复对表执行查询。

二、了解了原理后,mysqldump有什么坑吗?

前面已经说,mysqldump备份时一般都会使用--single-transaction + --master-data两个选项执行备份,这样备份过程中既可以尽量不锁表,也可以获取到binlog pos位置,备份文件可以用于数据恢复,也可以用于搭建备库。看起来那么美好,然而,其实一不小心你就发现自己已经在坑里了。

坑一:会造成MyISAM表跟binlog pos位点不一致

通过使用--single-transaction + --master-data选项时,备份过程是在RR隔离级别下开启一个事务来保证既可以尽量不锁表,也可以获取到binlog pos位置。而通过general_log产生的备份过程我们可以知道,不管是InnoDB表还是MyISAM表,mysqldump都是同等对待的,既都在一个事务中通过查询产生的备份(所有库表都在这个大事务中备份的)。这就会带来一个严重的问题,InnoDB表可以通过RR级别开启一个事务快照来保证数据一致性,而MyISAM不是事务表,所以如果在备份过程中,如果MyISAM表持续有数据写入或删除,那么mysqldump会复制最新的数据,但此时的binlog pos已经停止在了开启事务的那一刻。

如果你拿着这个备份去创建一个从库,会怎么样呢?你使用的binlog pos其实是InnoDB表的准确位点,但是MyISAM此刻的数据已经变的增多或减少了。也就意味着你用获取到的那个binlog pos去主从同步数据的话,会同步事务开始之后MyISAM表的变化产生的二进制日志,从库必然会报错。如果MyISAM在事务开始后是增加数据,那么从库就会报主键或唯一键冲突(前提是表有此约束);如果MyISAM表在事务开始后是删除数据,那么从库就会报数据不存在。至此,搭建从库失败。

原理是这么个原理,如果你不信,可以自己造点数据测试测试看。当然,你可以选择去掉--single-transaction选项,那么就不会产生事务快照了;同时,如果只添加--master-data参数会自动打开--lock-all-tables参数,锁住所有表,当然可以保证数据一致性了。

坑二:会造成InnoDB表丢数据

在使用--single-transaction + --master-data备份时,如果InnoDB表执行Online ddl也有可能碰上InnoDB表数据不一致的问题。通过前面提到的”WITH CONSISTENT SNAPSHOT语句的作用” 时的演示过程可以知道,DDL的负载是刻意加上去的,在演示mysqldump使用savepoint的作用的时候,使用start transaction with consistent snapshot语句显式开启一个事务之后,该事务执行select之前,该表被其他会话执行了DDL之后是无法查询数据的(查询会报错),我们知道mysqldump备份数据的时候,就是在start transaction with consistent snapshot语句开启的一个一致性快照事务下使用select语句查询数据进行备份的。如果你指定的DDL刚好在保存点执行之后查询执行之前操作了,那么不好意思,你这个表的数据会丢失。

所以,如果你决定继续使用mysqldump,那么以后搭建好备库之后,一定要记得校验一下主备数据一致性!!!

三、有办法改善这这些问题吗?

在寻找解决办法之前,咱们先来看看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,很多时候是非常无奈的,虽然有各种规范,但是保不齐就是有漏网之鱼,这个时候,生活还得继续,工作还得做好。

那么,有什么办法可以缓解这个问题吗?有的:

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

* 使用innobackupex备份工具。

<参考>

mysqldump与innobackupex备份过程你知多少(一)

mysqldump与innobackupex备份过程你知多少(二)

mysqldump与innobackupex备份过程你知多少(三)

mysqldump与innobackupex备份过程你知多少(四)


如果您觉得本站对你有帮助,那么可以支付宝扫码捐助以帮助本站更好地发展,在此谢过。
喜欢 (0)or分享 (0)
关于作者:

您必须 登录 才能发表评论!