背景
每个业务都或多或少遇到过业务bug、误操作等问题,因此数据恢复一直是一个很普遍、也很紧急的需求。通常MySQL用户习惯使用以下两种方式进行数据恢复:
全备+Binlog方式构造特定时间点的数据;
业务相关同学根据业务日志,构造出更新SQL更新现网。
然而这两种方式都非常耗时,且容易出错,造成停机时间过长,对用户体验造成影响。
Flashback开源工具研究
2012年淘宝彭立勋提出了MySQL Flashback特性,并在MySQL-5.5.18的基础上开发实现。MariaDB分支从10.2.4版本开始支持flashback 特性。
除此以外,美团、58到家等也开源了Flashback组件(美团MyFlash https://github.com/Meituan-Dianping/MyFlash、Mariadb flashback https://mariadb.com/kb/en/getting-the-mariadb-source-code/)
带着下面几个问题,我们开始对已有Flashback开源工具进行研究:
社区已开发出很多MySQL flashback工具,能否直接使用,避免重复开发?
Flashback只能回档ROW模式下DML语句(insert/delete/update/replace),DDL(create/alter/drop)如何处理?
现网Tendb3(MySQL5.7)、Tendb2(MySQL5.6)、Tendb(MySQL5.5)是主要版本,均有很多实例,如何让三个版本都能使用Flashback?
研究结果如下:
结论: 经研究对比后发现,现有Flashback组件均无法满足我们的需求,因此我们选择自研实现。
Flashback 实现
MySQL binlog格式
binlog简单来说就是记录数据更新操作的二进制日志。binlog两个重要作用:
用于主从之间数据同步,也就是将master上的更新操作在slave上重放;
由于binlog记录了数据更新操作,因此binlog也常用于恢复数据。
binlog_format && binlog_row_image
STATEMENT
: MySQL server上的每一个数据更新操作以SQL文本的形式记录到binlog文件中。可以简单理解为,用户传什么SQL给server执行,server就将该SQL原封不动的记录到binlog。不会保存数据修改前与修改后的镜像(image)。
此时,所有DDL(alter drop create等) DML(insert update delete)语句在binlog中都会以QUERY_EVENT(关于QUERY_EVENT后面会介绍)保存。
ROW
: 所有DDL(alter drop create等)依然以SQL文本方式(QUERY_EVENT)保存。所有DML(insert update delete)则以二进制形式保存每一行数据的更新操作,每一行数据的更新记录中包含每一列更新前的镜像(Before Image,简写为BI)和更新后的镜像(After Image,简写为AI)。
ROW 模式下,binlog是否记录每一行更新前后镜像还受binlog_row_image参数的影响:
binlog_row_image=FULL
: 无论某列的值是否有更新,都将记录所有列更新前的镜像(Before Image)和更新后的镜像(After Image);binlog_row_image=MINIMAL
: Before Image只记录唯一识别列(唯一索引列、主键列),After Image只记录修改列;binlog_row_image=NOBLOB
: 类似FULL格式,记录所有列更新前后的镜像。但对于BLOB或TEXT格式的列,如果不是唯一识别列(唯一索引列、主键列),或者没有修改,则不记录。MIXED
:STATEMENT
、ROW
两种格式混用。详情请参考:Mixed Binary Logging Format (https://dev.mysql.com/doc/refman/5.7/en/binary-log-mixed.html)
而我们本文讨论的mysql flashback,有一个大前提即:
binlog_format=ROW
、binlog_row_image=FULL/NOBLOB
(在MySQL 5.5版本中,指定binlog_format=ROW
即可)
binlog文件内容格式简介
每个binlog文件由一系列event组成,文件最开始的event是FORMAT_DESCRIPTION_EVENT,文件结尾的event是ROTATE_EVENT,中间是其他event的组合。
每种event的含义可参考:Event Meanings;
每种event的组成部分可参考:Event Data for Specific Event Types
几种常见event的介绍:
GTID_EVENT
: MySQL server开启Gtid特性后,server会为每个事务(begin~commit)都是生成一个全局事务号(GTID,global transaction identifier),GTID_EVENT即用于记录该编号。如:#191119 9:54:11 server id 135758565 end_log_pos 237 CRC32 0x49f8957c GTID last_committed=0 sequence_number=1 rbr_only=yes
/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= '29b05b90-ee79-11e9-9fff-fe0727adf2b3:1'/*!*/;
复制Query_log_event/QUERY_EVENT
: 用于记录SQL语句。如use database、以及CREATE、ALTER、DROP等DDL(Data Definition Language)操作。DDL不能做flashback。# at 538
#191119 9:54:11 server id 135758565 end_log_pos 625 CRC32 0x87610211 Query thread_id=3 exec_time=0 error_code=0
SET TIMESTAMP=1574128451/*!*/;
BEGIN
/*!*/;
复制Table_map_log_event/TABLE_MAP_EVENT
: 用于描述数据变更涉及表的表结构是什么样的。在binlog_format=ROW时binlog中数据变更和数据表结构定义是作为两种event存放的。
Rows_log_event/ROWS_EVENT
:binlog_format=ROW
时,ROWS_EVENT
表示行数据的变更事件。其中包含一行或多行的Before ImagWrite_rows_log_event/WRITE_ROWS_EVENT
:写入数据(即INSERT、REPLACE语句),包含变更后的数据镜像(After Image);Update_rows_log_event/UPDATE_ROWS_EVENT
:更新数据(即UPDATE语句),包含变更前后的数据镜像(Before Image和After Image);Delete_rows_log_event/DELETE_ROWS_EVENT
:删除数据(即DELETE语句),包含变更前的数据镜像(Before Image)Xid_log_event/XID_EVENT
:事务提交时会生成一个XID_EVENT,内容就是COMMIT;
event格式
每个event均由event header和event data两部分组成。
下面我们以UPDATE_ROWS_EVENT
为例:
event header:
event_data:
flashback实现思路
首先,需要说明一下WRITE_ROWS_EVENT
、UPDATE_ROWS_EVENT
、DELETE_ROWS_EVENT
对应的type_code
:
WRITE_ROWS_EVENT = 30,
UPDATE_ROWS_EVENT = 31,
DELETE_ROWS_EVENT = 32,
复制
flashback整体实现上有三层反转:
第1层反转: 单行数据更新event:
如果该event是
WRITE_ROWS_EVENT
,则直接将event header中的type_code
从30变成32。此时insert反转成delete;如果该event是
DELETE_ROWS_EVENT
,则直接将event header中的type_code
从32变成30。此时delete反转成insert;如果该event是
UPDATE_ROWS_EVENT
,则需要将event data中AI 与 BI交换。AI:After Image,BI:Before Image。第2层反转:反转由一个
TABLE_MAP_EVENT
和多个[INSERT|UPDATE|DELETE] ROWS_EVENT
组成的最小执行单元((least execution event unit)。最小执行单元概念由美团MyFlash
提出。如下面insert语句:insert into t_char_with_primary(name,grade,birthday,address)
values
('李靖',78.5,'1990-10-01 10:00:00','广东省深圳市南山区'),
('金吒',95.5,'2010-10-01 10:00:00','广东省深圳市南山区科兴'),
('木吒',96,'2011-12-01 10:00:00','广东省深圳市宝安区西乡'),
('哪吒',96,'2013-11-01 10:00:00','中国香港'),
('路人甲',78,'2012-01-01 08:00:00','韩国');
复制其最小执行单元如下:
flashback后的结果:"路人甲"最后一个插入,现在最先删除;"李靖"最先插入,现在最后删除。
第3层反转: 对整个binlog倒序输出所有
[INSERT|UPDATE|DELETE] ROWS_EVENT
。一个binlog文件包含18:00到19:00的更新日志,则flashback后,优先输出19:00反转后的结果,最后输出18:00反转后的结果。
Flashback实现整体流程图
bug修复+优化
Flashback自研实现后,在灰度测试过程中完成以下一系列bug修复与优化:
bug修复:修复UPDATE T1,T2 INNER JOIN T2 ON T1.C1 = T2.C1 中如果只想恢复T2表的数据的bug;
bug修复: Tendb3(MySQL5.7) mysqlbinlog解析Tendb1(MySQL5.5) binlog文件bug;
重点优化:添加选项–flashback-databases,支持同时flashback多个库;
重点优化:添加选项–flashback-tables,支持同时flashback多个表;
重点优化:简易DDL语句识别并报错,排除校验、监控心跳语句,其余
Querey_event
都报错退出;
同类工具对比
Flashback 数据正确性验证方案
下面"数据正确性验证方案"将帮助我们有效确认Flashback是否正确执行。
数据验证流程: 如上图;
注意:B1 到 B2 这段时间内,a b c d表必须持续更新;
B3 备份中a b表insert sql文件和B1备份中a b表insert sql文件 md5值一致,则代表Flashback正确执行;
B3 备份中c d表insert sql文件和B2备份中c d表insert sql md5值一致,则代表Flashback未影响非目标表;
Flashback单据
Flashback工具完善后,让用户/DBA手动敲命令完成数据回档是不现实的。因此,我们在管理平台中实现了"Flashback单据",用户/DB可以方便的通过提交回档单据进行数据的回档操作。
下面来看看单据开发过程中遇到的一些挑战:
挑战一: Tendb Cluster模式如何尽肯能保证 要么全部成功,要么全部失败?
部分成功,部分失败,对集群模式来说与"数据污染无异"。
方案如下:
单个MySQL回档分四个步骤完成:前置检查、文件拉取、flashback_binlog、sql导入;
通过共享Redis保存每个实例当前的状态;
每个MySQL在执行下一步骤之前,必须确保集群所有MySQL上一步骤状态是成功的。
挑战二: Flashback文件遍历完成前,结果保存在内存中,如何保证不占用大量内存?
一条命令处理全部binlog文件,会将binlog文件内容全部读取到内存中,此时内存占用过大极可能造成oom。
经调研发现,现网DB单个binlog文件最大257M且超大事务很少遇到。
因此我们选择将回档步骤拆解为两步完成:
mysqlbinlog --flashabck $binlogFile > $dest.sql
少量并发将单个binlog文件解析为sql文件;mysql -u$user -p$password ... < $dest.sql
串行倒序导入sql文件执行前强烈建议先备份相关库表数据,以便flashback导入.sql文件失败后 不至于数据彻底污染;
flashback执行期间相关库表不能还有写操作(建议停服执行flashback恢复数据)。新的写操作可能直接导致flashback执行失败,甚至造成现网数据污染。
FOREIGN KEY CASCADE DELETE/UPDATE场景中,Child表无法使用flashback恢复数据:
FOREIGN KEY CASCADE DELETE/UPDATE场景中,Father表delete/update时,binlog中只会记录Father表的delete/update,而Child表delete/update不会记录。
而flashback回档依赖于binlog文件数据 ,所以此时Child表数据flashback无法完成回档。
MySQL Flashback拯救手抖党 https://zhuanlan.zhihu.com/p/68845158
Flashback 单据整体实现
Flashback收益
整体对比,Flashback方案时间优势特别明显。