#备份全库    mydumper  -h 127.0.0.1 -P 3306 -u root -p 123456 --less-locking --complete-insert  --regex '^(?!(mysql|sys|performance_schema|information_schema))' --verbose=3 -o /data1/backup_mysql/
#还原全库 myloader -h 127.0.0.1 -P 3306 -u root -p 123465 -o -d /data1/backup_mysql/

mydumper常用参数
-r, --rows 将表按行分块时,指定的块行数,指定这个选项会关闭 --chunk-filesize
-F, --chunk-filesize 将表按大小分块时,指定的块大小,单位是 MB(一般不用)
-o, --outputdir 备份文件输出的目录
-s, --statement-size 生成的insert语句的字节数,默认1000000
-c, --compress 压缩输出文件
-B, --database 要备份的数据库,不指定则备份所有库
-T, --tables-list 需要备份的表,名字用逗号隔开
-x, --regex 是同正则表达式匹配 --rex '(^?!(mysql.|sys.))'
-k, --no-locks 不使用临时共享只读锁,使用这个选项会造成数据不一致(不要用)
--no-backup-locks 不使用备份锁(备份报错的时候需要加上这个选项)
--less-locking 减少对InnoDB表的锁施加时间(不懂原理,加上吧)
-b, --binlogs 导出binlog
-h, --host 连接的主机名
-u, --user 备份所使用的用户
-p, --pass 密码
-P, --port 端口
-t, --threads 开启的备份线程数,默认是4
-v, --verbose 输出信息模式, 0 = silent, 1 = errors, 2 =
warnings, 3= info, 默认为 2
myloader常用参数
-d, --directory 备份文件的文件夹
-o, --overwrite-tables 如果要恢复的表存在,则先drop掉该表,使用该参数,
需要备份时候要备份表结构

1. 简介

相对于 ​​MySQL​​ 官方提供的逻辑备份工具 mysqldump,mydumper 最突出的特性就是可采用多线程并行备份,极大提高了数据导出的速度。本文基于 mydumper在 github 上托管的最新源码,对其实现原理进行较详细的介绍。

2. 备份机制

与其他备份工具一样,mydumper 默认情况下是用 FTWRL (Flush Tables With Read Lock) 全局读锁来保证备份数据的一致性。FTWRL 锁对 MySQL 的杀伤力很大,特别是在读写负载比较高的场景,因而mydumper在加锁时会优先使用影响更小的备份锁,依次执行 ​​LOCK TABLES FOR BACKUP​​​和​​LOCK TABLES FOR BINLOG​​,如果导出实例支持的话。

在进行​​数据备份​​时,mydumper 的主逻辑由一个主线程和多个备份子线程共同完成,默认情况下为四个子线程。其主线程的主要流程为:

  1. 连接​​数据库​
  2. ​FLUSH TABLES WITH READ LOCK​​ 将脏页刷新到磁盘并获得只读锁
  3. ​START TRANSACTION /!40108 WITH CONSISTENT SNAPSHOT /​​开启事务并获取一致性快照
  4. ​SHOW MASTER STATUS​​ 获得binlog位点信息
  5. 创建子线程并连接数据库
  6. 为子线程分配任务并 push 到队列​​queue​​中
  7. 在子线程处理完所有非 InnoDB 表之后,​​UNLOCK TABLES​​ / FTWRL / 释放锁
  8. thread.join() 等待子线程结束

备份子线程的主要流程是:

  1. 连接数据库
  2. 将 session 的隔离级别设置为 Repeatable Read
  3. ​START TRANSACTION /!40108 WITH CONSISTENT SNAPSHOT /​​开启事务并获取一致性快照
  4. 从队列中 pop 任务并执行
  5. 在所有非 InnoDB 表执行完之后,将事件通知给主线程

值得注意的是,虽然 mydumper 支持表级别的并行操作,且在导出的时候会对大的表数据进行分块 chunk 导出,但是同一个表的 chunks 是在同一个线程中处理的,并非多线程并行的。

4. 备份的详细流程

mydumper执行备份的详细流程如下

mydumper_mydumper

mydumper flow

流程图中的步骤基本与源码中的函数名称对应,可以将源码与流程图对照来看。

5. 一致性与锁

如流程图中所示,主线程会在开始时获取 FTWRL 全局只读锁,来保证确保接下来没有变更产生。

之后开启一致性读事务,创建子线程并确认子线程 session 隔离级别为 Repeatable Read、开启一致性读事务。这样保证了即使主线程在所有导出任务结束之前释放锁,子线程在处理 InnoDB 表时能利用 MySQL MVCC 特性继续执行,得到事务开启时间点的一致性数据视图。

接下来主线程会在自己的 session 里面读取数据库表结构、拆分任务,主线程​​UNLOCK TABLES​​释放锁由非 InnoDB 表都处理完的事件来触发,确保所有的非事务表的数据一致性。

mydumper通过结合以上逻辑可以保证即使在多线程处理的情形下,备份数据仍是一致的。

--trx-consistency-only 选项

这个选项与 mysqldump 的​​--single-transaction​​​功能类似,将备份流程当作一个大事务来处理,可以看到流程中在主线程等待子线程创建完之后,主线程就会提前​​UNLOCK TABLES​​释放锁了。

显然,这个时候无法保证非InnoDB表的数据一致性,这是因为MyISAM等非事务并不支持MVCC特性,无法实现一致性快照读。

--less-locking 模式

mydumper有一个比较有意思的​​--less-locking​​选项,主要目的就是尽量减少 mydumper 中FTWRL整体的锁定时间。

这种模式的实现比较巧妙,在这种模式下,线程数量是用户指定线程数n的两倍,默认情况下也即是八个线程,新增的线程为专门处理非 InnoDB 表的一组线程​​less_locking_threads​​。主要的流程为:

  1. 主线程 ​​FLUSH TABLES WITH READ LOCK​​ 获取全局只读锁
  2. 主线程 ​​START TRANSACTION /!40108 WITH CONSISTENT SNAPSHOT /​​ 开启事务并获取一致性快照
  3. 主线程 ​​SHOW MASTER STATUS​​获得binlog位点信息
  4. 主线程创建两组子线程并连接数据库,其中原来的一组线程​​threads​​​会条件等待​​less_locking_threads​​​结束才开始工作,​​less_locking_threads​​​中结束一个就通知​​threads​​中的一个线程开始工作
  5. 主线程将非InnoDB表按照表中数据量均分为与n份push到queue_less_locking​​,其他表的导出任务还是按照原来的流程处理push到队列​​queue`中
  6. ​less_locking_threads​​​中每个线程会拿到一个非InnoDB表任务执行,直到执行完通知​​threads​​​中的一个线程开始执行​​queue​​中的任务
  7. 当所有​​less_locking_threads​​​执行完时,主线程​​UNLOCK TABLES​​ / FTWRL / 释放锁
  8. 主线程 thread.join() 等待子线程结束

可以看到在​​--less-locking​​​模式下,mydumper 会首先集中能力处理完所有非 InnoDB 表的任务,尽快满足主线程释放锁的条件。由于每个​​less_locking_threads​​处理的任务量相当,这基本是最快的处理方式了。