mysql基于binlog回滚工具实例详解

 

    update、delete的条件写错甚至没有写,导致数据操作错误,需要恢复被误操作的行记录。这种情形,其实时有发生,可以选择用备份文件+binlog来恢复到测试环境,然后再做数据修复,但是这样其实需要耗费一定的时间跟资源。

    其实,如果binlog format为row,binlog文件中是会详细记录每一个事务涉及到操作,并把每一个事务影响到行记录均存储起来,能否给予binlog 文件来反解析数据库的行记录变动情况呢?    业界已有不少相关的脚本及工具,但是随着MySQL版本的更新、binlog记录内容的变化以及需求不一致,大多数脚本不太适合个人目前的使用需求,所以开始着手编写 mysql的 flash back脚本。      如果转载,请注明博文来源: www.cnblogs.com/xinysu/   ,版权归 博客园 苏家小萝卜 所有。望各位支持!       仅在MySQL 5.6/5.7版本测试,python运行环境需要安装pymysql模块。

1 实现内容

    根据binlog文件,对某个些事务、某段时间某些表、某段时间全库 做回滚操作,实现闪回功能。工具处理过程中,会把binlog中的事务修改的行记录存储到表格中去,通过 dml_sql 列,可以查看每一个事务内部的所有行记录变更情况,通过 undo_sql 查看回滚的SQL内容。如下图,然后再根据表格内容做回滚操作。     mysql基于binlog回滚工具实例详解      那么这个脚本有哪些优点呢?

回滚分为2个命令:第一个命令 分析binglog并存储进入数据库;第二个命令 执行回滚操作;

回滚的时候,可以把执行脚本跟回滚脚本统一存放到数据库中,可以查看 更新内容以及回滚内容;

根据存储的分析表格,方便指定事务或者指定表格来来恢复;

详细的日志输出,说明分析进度跟执行进度。

    分析binlog的输出截图(分析1G的binlog文件)    mysql基于binlog回滚工具实例详解    回滚数据库的输出截图:   mysql基于binlog回滚工具实例详解

2 原理

    前提:实例启动了binlog并且格式为ROW。        使用python对mysqlbinlog后的log文件进行文本分析处理,在整个处理过程中,有以下6个疑难点需要处理:

判断一个事务的开始跟结束

同一个事务的执行顺序需要反序执行

解析回滚SQL

同一个事务操作不同表格处理

转义字符处理,比如换行符、tab符等等

timestamp数据类型参数值转换

负数处理

单个事务涉及到行修改SQL操作了 max_allow

针对某个表格做回滚,而不是全库回滚

    

2.1 事务的开始与结束

    按照Xid出现的位置来判断,从binlog文件的最开始开始读取,遇到SQL语句则提取出来,直到遇到Xid,统一把之前提取出来的SQL汇总为一个事务,然后继续提取SQL语句,直到遇到下一个Xid,再把这个事务的SQL汇总成一个事务,一直这样循环,直至文件顺序遍历结束。    mysql基于binlog回滚工具实例详解

 

2.2 事务内部反序处理

    同一个事务中,如果有多个表格多行记录发生变更,在回滚的时候,应该反序回滚SQL,那么,如何将提取出来的SQL反序存储呢?思路如下:

每行记录的修改SQL独立出来

将独立出来的SQL反序存储

   假设正序的事务SQL语句存储在变量 dml_sql 中,反序后的可以回滚的SQL存储在变量 undo_sql中。按顺序把行记录修改的SQL抽取出来 存储到变量 record_sql 中去,然后 赋值 undo_sql =record_sql + undo_sql ,再置空 record_sql 变量,如此,便可实现反序事务内部的执行SQL。

2.3 解析回滚SQL

    首先,查看binlog的日志内容,发现行修改的SQL情形如下,提取过程中需要注意这几个问题:

行记录的列名配对,binlog file存储的列序号,不能直接使用

WHERE部分 跟 SET部分 之间并无关键字或者符号,需要添加 AND 或者 逗号

DELETE SQL 需要反转为 INSERT

UPDATE SQL 需要把WHERE 跟 SET的部分进行替换

INSERT SQL需要反转为 DELETE

mysql基于binlog回滚工具实例详解

2.4 同事务不同表格处理

    同一个事务中,允许对不同表格进行数据修改,这点在列名替换列序号的时候,需要留意处理。    每一个的行记录前有一行记录,含有 ‘Table_map’ 标识,会说明这一行当行记录是修改哪个表格,可以根据这个提示,来替换binlog里边的列序号为列名。

2.5 转义字符处理

    binlog文件在对非空格的空白字符处理,采用转义字符字符串存储,比如,在表格insert一列记录含换行符,而实际上在binlog文件中,是使用了 替换了 换行操作,所以在回滚数据的过程中,需要对转义字符做处理。    mysql基于binlog回滚工具实例详解

    mysql基于binlog回滚工具实例详解

 

        这里注意一个地方,039的转义字符是没有在函数 esc_code 中统一处理,而是单独做另外处理。    mysql基于binlog回滚工具实例详解

 

    转移字符表相见下图:    mysql基于binlog回滚工具实例详解

 

2.6 timestamp数据类型处理

    timestamp实际在数据库中的存储值是 INT类型,需要使用 函数 from_unixtime转换。    建立测试表格tbtest,只有一列timestamp的列,存储值后查看binlog的内容,具体截图如下:    mysql基于binlog回滚工具实例详解

 

    在处理行记录的时候,要对timestamp的value做处理,添加from_unixtime函数转换。

2.7 负数值处理

    这个一开始写代码的时候,并没有考虑到。大量测试的过程中发现,所有整型的数据类型,在存储负数的时候,都会存入一个最大范围值。binlog在处理这块的机制有些不是很了解。测试如下:    mysql基于binlog回滚工具实例详解

 

   所以当遇到INT的各种数据类型并且VALUE为负数的时候,需要把 这个范围值去除,才能执行执行undo_sql。

2.8 单个事务行记录总SQL超过max_allowed_package处理

    分析binlog后存储两种sql类型,一种是行记录的修改SQL,即 dml_sql;一种是 行记录的回滚sql,即 undo_sql。从代码可知,存储这两个sql的列是longtext,最大可存储4G的内容。但是 MySQL中单个会话的包大小是有限制的,限制的参数为 max_allowed_packet,默认大小为 4Mb,最大为1G,所以这个脚本使用前,请手动设置 存储binlog file的数据库实例以及线上的数据库实例这个参数:set global max_allowed_packet = 1073741824; #记得后续修改回来        万一操作了呢?那么回滚只能分段来回滚,先回滚到 这个大事务,然后单独执行这个大事务,紧接着继续回滚,这部分不能使用pymysql嗲用source 文件执行,所以只能手动做这个操作。 求高能人士修改这个逻辑代码!!!

2.9 针对性回滚

    假设误操作的没有明确的时间点,只有一个区间,而这个区间还有其他的表格操作,那么这个时候,需要在分析binlog文件的时候,添加–database选项,先帅选到同一个数据库中binlog文件中。    这里的处理是将这段区间的dml_sql跟undo_sql都存储到数据库表格中,然后再删除不需要回滚的事务,剩余需要回滚的事务。再执行回滚操作。

3 使用说明

3.1 参数说明

    这个脚本的参数稍微多些,可以 –help 查看具体说明。    mysql基于binlog回滚工具实例详解

 

    本人喜欢用各种颜色来分类参数(blingbling五颜六色,看着多有趣多精神),所以,按颜色来说明这些参数。

黄色区域:这6个参数,提供的是 分析并存储binlog file的相关值,说明存储分析结果的数据库的链接方式、binlog文件的位置以及存储结果的表格名字;

蓝色区域:这4个参数,提供 与线上数据库表结构一致的DB实例连接方式,仅需跟线上一模一样的表结构,不一定需要是主从库;

绿色区域:最最重要的选项 -a,0代表仅分析binlog文件,1代表仅执行回滚操作,必须先执行0才可以执行1;

紫色区域:举例说明。

3.2 应用场景说明

全库回滚某段时间

需要回滚某个时间段的所有SQL操作,回滚到某一个时间点

这种情况下呢,大多数是使用备份文件+binlog解决

但是这个脚本也可以满足,但请勿直接在线上操作,先 -a=0,看下分析结果,是否符合,符合的话,停掉某个从库,再在从库上执行,最后开发业务接入检查是否恢复到指定时间点,数据是否正常。

LuckyCola工具库 LuckyCola工具库

LuckyCola工具库是您工作学习的智能助手,提供一系列AI驱动的工具,旨在为您的生活带来便利与高效。

LuckyCola工具库 19 查看详情 LuckyCola工具库

某段时间某些表格回滚某些操作

比如,开发提交了一个批量更新脚本,各个测试层面验证没有问题,提交线上执行,但是执行后,发现有个业务漏测试,导致某些字段更新后影响到其他业务,现在需要紧急 把被批量更新的表格回滚到原先的行记录

这个并不能单纯从技术角度来处理,要综合考虑

mysql基于binlog回滚工具实例详解

 

这种情况下,如何回顾tab A表格的修改操作呢?

个人觉得,这种方式比较行得通,dump tabA表格的数据到测试环境,然后再分析 binlog file 从11点-12点的undo sql,接着在测试环境回滚该表格到11点这个时刻,紧接着,由开发跟业务对比测试环境11点的数据跟线上现有的数据中,看下是哪些行哪些列需要在线上进行回滚,哪些是不需要的,然后开发再提交SQL脚本,再在线上执行。其实,这里边,DBA仅提供一个角色,就是把 表格 tab A 在一个新的环境上,回滚到某个时间点,但是不提供直接线上回滚SQL的处理。

回滚某个/些SQL

这种情况比较常见,某个update某个delete缺少where条件或者where条件执行错误

这种情况下,找到对应的事务,执行回滚即可,回滚流程请参考上面一说,对的,我就是这么胆小怕事 mysql基于binlog回滚工具实例详解

 

3.3 测试案例

3.3.1 全库回滚某段时间

假设需要回滚9点10分到9点15分间数据库的所有操作:

准备测试环境实例存储分析后的数据 

测试环境修改set global max_allowed_packet = 1073741824

mysqlbinlog分析binlog文件

python脚本分析文件,action=0

线上测试环境修改set global max_allowed_packet = 1073741824

回滚数据,action=1

线上测试环境修改set global max_allowed_packet = 4194304

 1 --测试环境(请安装pymysql):IP: 192.168.9.242,PORT:3310 ,数据库:flashback,表格:tbevent 2 --具有线上表结构的db:IP:192.168.9.243 PORT:3310 3  4  5 mysql> show global variables like 'max_allowed_packet'; 6 +--------------------+----------+ 7 | Variable_name      | Value    | 8 +--------------------+----------+ 9 | max_allowed_packet | 16777216 |10 +--------------------+----------+11 1 row in set (0.00 sec)12 13 mysql> set global max_allowed_packet = 1073741824;14 Query OK, 0 rows affected (0.00 sec)15 16 [root@sutest244 ~]# mysqlbinlog --start-datetime='2017-06-19 09:00:00' --stop-datetime='2017-06-19 10:00:00' --base64-output=decode-rows -v ~/data/mysql/data/mysql-bin.007335 > /tmp/binlog.log17 18 [root@sutest242 pycharm]# python su_flashback.py -h=127.0.0.1 -P=3310 -u=root -p=**** -f=/tmp/binlog.log -t=flashback.tbevent -oh=192.168.9.244 -oP=3310 -u=root -op=**** -a=019 2017-06-19 10:59:39,041 INFO begin to assign values to parameters20 2017-06-19 10:59:39,041 INFO assign values to parameters is done:host=127.0.0.1,user=root,password=***,port=3310,fpath=/tmp/binlog.log,tbevent=flashback.tbevent21 2017-06-19 10:59:39,049 INFO MySQL which userd to store binlog event connection is ok22 2017-06-19 10:59:39,050 INFO assign values to online mysql parameters is done:host=192.168.9.244,user=,password=***,port=331023 2017-06-19 10:59:39,054 INFO MySQL which userd to analyse online table schema connection is ok24 2017-06-19 10:59:39,054 INFO MySQL connection is ok25 2017-06-19 10:59:39,055 INFO creating table flashback.tbevent to store binlog event26 2017-06-19 10:59:39,058 INFO created table flashback.tbevent 27 2017-06-19 10:59:39,060 INFO begining to analyze the binlog file ,this may be take a long time !!!28 2017-06-19 10:59:39,061 INFO analyzing...29 2017-06-19 11:49:53,781 INFO finished to analyze the binlog file !!!30 2017-06-19 11:49:53,782 INFO release all db connections31 2017-06-19 11:49:53,782 INFO All done,check the flashback.tbevent which stored binlog event on host 127.0.0.1 , port 3310 32 33 34 [root@sutest242 pycharm]# python su_flashback.py -h=127.0.0.1 -P=3310 -u=root -p=**** -f=/tmp/binlog.log -t=flashback.tbevent -oh=192.168.9.244 -oP=3310 -u=root -op=**** -a=135 2017-06-19 16:30:20,633 INFO begin to assign values to parameters36 2017-06-19 16:30:20,635 INFO assign values to parameters is done:host=127.0.0.1,user=root,password=***,port=3310,fpath=/tmp/binlog.log,tbevent=flashback.tbevent37 2017-06-19 16:30:20,865 INFO MySQL which userd to store binlog event connection is ok38 2017-06-19 16:30:20,866 INFO assign values to online mysql parameters is done:host=192.168.9.244,user=,password=***,port=331039 2017-06-19 16:30:20,871 INFO MySQL which userd to analyse online table schema connection is ok40 2017-06-19 16:30:20,871 INFO MySQL connection is ok41 2017-06-19 16:30:21,243 INFO There has 347868 transactions ,need 35 batchs ,each batche doing 10000 transactions 42 2017-06-19 16:30:21,243 INFO doing batch : 1 43 2017-06-19 16:31:01,182 INFO doing batch : 2 44 2017-06-19 16:31:16,909 INFO doing batch : 3 45 -------省空间忽略不截图--------------46 2017-06-19 16:41:11,287 INFO doing batch : 34 47 2017-06-19 16:41:25,577 INFO doing batch : 35 48 2017-06-19 16:41:44,629 INFO release all db connections49 2017-06-19 16:41:44,630 INFO All done,check the flashback.tbevent which stored binlog event on host 127.0.0.1 , port 3310

3.3.2 某段时间某些表格回滚某些操作

准备测试环境实例存储分析后的数据 

测试环境修改set global max_allowed_packet = 1073741824

mysqlbinlog分析binlog文件

python脚本分析文件,action=0

分析帅选需要的事务,rename表格

dump 对应的表格到测试环境

回滚数据,action=1

提交给开发业务对比数据

3.3.3 回滚某个/些SQL

准备测试环境实例存储分析后的数据 

测试环境修改set global max_allowed_packet = 1073741824

mysqlbinlog分析binlog文件

python脚本分析文件,action=0

分析帅选需要的事务,rename表格

dump 对应的表格到测试环境

回滚数据,action=1

提交给开发业务对比数据

4 python脚本

     脚本会不定期修复bug,若是感兴趣,可以往github下载: 中的 mysql_xinysu_flashback

  1 # -*- coding: utf-8 -*-  2 __author__ = 'xinysu'  3 __date__ = '2017/6/15 10:30'  4   5   6   7 import re  8 import os  9 import sys 10 import datetime 11 import time 12 import logging 13 import importlib 14 importlib.reload(logging) 15 logging.basicConfig(level=logging.DEBUG,format='%(asctime)s %(levelname)s %(message)s ') 16  17 import pymysql 18 from pymysql.cursors import DictCursor 19  20 usage='''usage: python [script's path] [option] 21 ALL options need to assign: 22 [1;33;40m 23 -h    : host, the database host,which database will store the results after analysis 24 -u    : user, the db user 25 -p    : password, the db user's password 26 -P    : port, the db port 27  28 -f    : file path, the binlog file 29 -t    : table name, the table name to store the results after analysis , {dbname}.{tbname}, 30         when you want to store in `test` db and the table name is `tbevent`,then this parameter  31         is test.tbevent 32 [1;34;40m 33 -oh   : online host, the database host,which database have the online table schema 34 -ou   : online user, the db user 35 -op   : online password, the db user's password 36 -oP   : online port, the db port 37 [1;32;40m 38 -a    : action,  39         0 just analyse the binlog file ,and store sql in table;  40         1 after execute self.dotype=0, execute the undo_sql in the table 41 [0m   42 --help: help document 43 [1;35;40m 44 Example: 45 analysize binlog: 46 python su_flashback.py -h=127.0.0.1 -P=3310 -u=root -p=*** -f=/tmp/binlog.log -t=flashback.tbevent  47                        -oh=192.168.9.244 -oP=3310 -u=root -op=***  48                        -a=0 49  50 flash back: 51 python su_flashback.py -h=127.0.0.1 -P=3310 -u=root -p=*** -f=/tmp/binlog.log -t=flashback.tbevent  52                        -oh=192.168.9.244 -oP=3310 -u=root -op=***  53                        -a=1 54 [0m                         55 ''' 56  57 class flashback: 58     def __init__(self): 59         self.host='' 60         self.user='' 61         self.password='' 62         self.port='3306' 63         self.fpath='' 64         self.tbevent='' 65  66         self.on_host='' 67         self.on_user='' 68         self.on_password='' 69         self.on_port='3306' 70  71         self.action=0 # 0 just analyse the binlog file ,and store sql in table;1 after execute self.dotype=0, execute the undo_sql in the table 72  73         self._get_db() # 从输入参数获取连接数据库的相关参数值 74  75         # 连接数据库,该数据库是用来存储binlog文件分析后的内容 76         logging.info('assign values to parameters is done:host={},user={},password=***,port={},fpath={},tbevent={}'.format(self.host,self.user,self.port,self.fpath,self.tbevent)) 77         self.mysqlconn = pymysql.connect(host=self.host, user=self.user, password=self.password, port=self.port,charset='utf8') 78         self.cur = self.mysqlconn.cursor(cursor=DictCursor) 79         logging.info('MySQL which userd to store binlog event connection is ok') 80  81         # 连接数据库,该数据库的表结构必须跟binlogfile基于对数据库表结构一致 82         # 该数据库用于提供 binlog file 文件中涉及到表结构分析 83         logging.info('assign values to online mysql parameters is done:host={},user={},password=***,port={}'.format(self.on_host, self.on_user, self.on_port)) 84         self.on_mysqlconn = pymysql.connect(host=self.on_host, user=self.on_user, password=self.on_password, port=self.on_port,charset='utf8') 85         self.on_cur = self.on_mysqlconn.cursor(cursor=DictCursor) 86         logging.info('MySQL which userd to analyse online table schema connection is ok') 87  88         logging.info('[33mMySQL connection is ok[0m') 89  90         self.dml_sql='' 91         self.undo_sql='' 92  93         self.tbfield_where = [] 94         self.tbfield_set = [] 95  96         self.begin_time='' 97         self.db_name='' 98         self.tb_name='' 99         self.end_time=''100         self.end_pos=''101         self.sqltype=0102 103     #_get_db用于获取执行命令的输入参数104     def _get_db(self):105         logging.info('begin to assign values to parameters')106         if len(sys.argv) == 1:107             print(usage)108             sys.exit(1)109         elif sys.argv[1] == '--help':110             print(usage)111             sys.exit()112         elif len(sys.argv) > 2:113             for i in sys.argv[1:]:114                 _argv = i.split('=')115                 if _argv[0] == '-h':116                     self.host = _argv[1]117                 elif _argv[0] == '-u':118                     self.user = _argv[1]119                 elif _argv[0] == '-P':120                     self.port = int(_argv[1])121                 elif _argv[0] == '-f':122                     self.fpath = _argv[1]123                 elif _argv[0] == '-t':124                     self.tbevent = _argv[1]125                 elif _argv[0] == '-p':126                     self.password = _argv[1]127 128                 elif _argv[0] == '-oh':129                     self.on_host = _argv[1]130                 elif _argv[0] == '-ou':131                     self.on_user = _argv[1]132                 elif _argv[0] == '-oP':133                     self.on_port = int(_argv[1])134                 elif _argv[0] == '-op':135                     self.on_password = _argv[1]136 137                 elif _argv[0] == '-a':138                     self.action = _argv[1]139 140                 else:141                     print(usage)142 143     #创建表格,用于存储分析后的BINLOG内容144     def create_tab(self):145         logging.info('creating table {} to store binlog event'.format(self.tbevent))146         create_tb_sql ='''147         CREATE TABLE IF NOT EXISTS {}(148             auto_id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,149             binlog_name VARCHAR(100) NOT NULL COMMENT 'the binlog file path and name',150             dml_start_time DATETIME NOT NULL COMMENT 'when to start this transaction ',151             dml_end_time DATETIME NOT NULL COMMENT 'when to finish this transaction ',152             end_log_pos BIGINT NOT NULL COMMENT 'the log position for finish this transaction',153             db_name VARCHAR(100) NOT NULL COMMENT 'which database happened this transaction ',154             table_name VARCHAR(200) NOT NULL COMMENT 'which table happened this transaction ',155             sqltype INT NOT NULL COMMENT '1 is insert,2 is update,3 is delete',156             dml_sql LONGTEXT NULL  COMMENT 'what sql excuted',157             undo_sql LONGTEXT NULL COMMENT 'rollback sql, this sql used for flashback',158             PRIMARY KEY (auto_id),159             INDEX sqltype(sqltype),160             INDEX dml_start_time (dml_start_time),161             INDEX dml_end_time (dml_end_time),162             INDEX end_log_pos (end_log_pos),163             INDEX db_name (db_name),164             INDEX table_name (table_name)165         )166         COLLATE='utf8_general_ci' ENGINE=InnoDB;167         TRUNCATE TABLE {};168 169         '''.format(self.tbevent,self.tbevent)170         self.cur.execute(create_tb_sql)171         logging.info('created table {} '.format(self.tbevent))172 173     #获取表格的列顺序对应的列名,并处理where set的时候,列与列之间的连接字符串是逗号还是 and174     def tbschema(self,dbname,tbname):175         self.tbfield_where = []176         self.tbfield_set = []177 178         sql_tb='desc {}.{}'.format(self.db_name,self.tb_name)179 180         self.on_cur.execute(sql_tb)181         tbcol=self.on_cur.fetchall()182 183         i = 0184         for l in tbcol:185             #self.tbfield.append(l['Field'])186             if i==0:187                 self.tbfield_where.append('`'+l['Field']+'`')188                 self.tbfield_set.append('`'+l['Field']+'`')189                 i+=1190             else:191                 self.tbfield_where.append('/*where*/ and /*where*/' + '`'+l['Field']+'`')192                 self.tbfield_set.append( '/*set*/ , /*set*/'+'`'+l['Field']+'`' )193 194     # 一个事务记录一行,若binlog file中的行记录包含 Table_map,则为事务的开始记录195     def rowrecord(self,bl_line):196         try:197             if bl_line.find('Table_map:') != -1:198                 l = bl_line.index('server')199                 m = bl_line.index('end_log_pos')200                 n = bl_line.index('Table_map')201                 begin_time = bl_line[:l:].rstrip(' ').replace('#', '20')202 203                 self.begin_time = begin_time[0:4] + '-' + begin_time[4:6] + '-' + begin_time[6:]204                 self.db_name = bl_line[n::].split(' ')[1].replace('`', '').split('.')[0]205                 self.tb_name = bl_line[n::].split(' ')[1].replace('`', '').split('.')[1]206 207                 self.tbschema(self.db_name,self.tb_name)208         except Exception:209             return 'funtion rowrecord error'210 211     def dml_tran(self,bl_line):212         try:213 214 215             if bl_line.find('Xid =') != -1:216 217                 l = bl_line.index('server')218                 m = bl_line.index('end_log_pos')219                 end_time = bl_line[:l:].rstrip(' ').replace('#', '20')220                 self.end_time = end_time[0:4] + '-' + end_time[4:6] + '-' + end_time[6:]221                 self.end_pos = int(bl_line[m::].split(' ')[1])222 223 224 225                 self.undo_sql = self.dml_sql.replace(' INSERT INTO', ';DELETE FROM_su').replace(' UPDATE ',';UPDATE').replace(' DELETE FROM', ';INSERT INTO').replace(';DELETE FROM_su', ';DELETE FROM').replace('WHERE', 'WHERE_marksu').replace('SET', 'WHERE').replace('WHERE_marksu', 'SET').replace('/*set*/ , /*set*/', ' and ').replace('/*where*/ and /*where*/',' , ')226                 self.dml_sql=self.dml_sql.replace('/*set*/ , /*set*/', ' , ').replace('/*where*/ and /*where*/',' and ')227 228                 if self.dml_sql.startswith(' INSERT INTO '):229                     self.sqltype=1230                 elif self.dml_sql.startswith(' UPDATE '):231                     self.sqltype=2232                 elif self.dml_sql.startswith(' DELETE '):233                     self.sqltype=3234 235                 record_sql = ''236                 undosql_desc = ''237 238                 #同个事务内部的行记录修改SQL,反序存储239                 for l in self.undo_sql.splitlines():240                     if l.startswith(' ;UPDATE') or l.startswith(' ;INSERT') or l.startswith(' ;DELETE'):241                         undosql_desc = record_sql + undosql_desc242                         record_sql = ''243                         record_sql = record_sql + l244                     else:245                         record_sql = record_sql + l246 247                 self.undo_sql = record_sql + undosql_desc248                 self.undo_sql = self.undo_sql.lstrip()[1:]+';'249 250                 #处理非空格的空白特殊字符251                 self.dml_sql = self.esc_code(self.dml_sql)252                 self.undo_sql = self.esc_code(self.undo_sql)253 254                 #单独处理 转移字符: '255                 self.dml_sql = self.dml_sql.replace("'", "''").replace('x27',"''''")  # + ';'256                 self.undo_sql = self.undo_sql.replace("'", "''").replace('x27',"''''")  # + ';'257 258                 if len(self.dml_sql)>500000000:259                     with open('/tmp/flashback_undosql/'+str(self.end_pos)+'.sql', 'w') as w_f:260                         w_f.write('begin;' + '')261                         w_f.write(self.undo_sql)262                         w_f.write('commit;' + '')263                     self.dml_sql=''264                     self.undo_sql='/tmp/flashback_undosql/'+str(self.end_pos)+'.sql'265                     logging.info("the size of this transaction is more than 500Mb ,the file location : {}".format(self.undo_file))266 267                 insert_sql = "INSERT INTO {}(binlog_name,dml_start_time,dml_end_time,end_log_pos,db_name,table_name,sqltype,dml_sql,undo_sql) select  '{}','{}','{}','{}','{}','{}',{},'{}','{}'".format(268                     self.tbevent, self.fpath, self.begin_time, self.end_time, self.end_pos,269                     self.db_name, self.tb_name, self.sqltype, self.dml_sql, self.undo_sql)270 271                 self.cur.execute(insert_sql)272                 self.mysqlconn.commit()273 274                 self.dml_sql = ''275                 self.undo_sql = ''276         except Exception:277             print( 'funtion dml_tran error')278 279 280     def analyse_binlog(self):281         try:282             sqlcomma=0283             self.create_tab()284 285             with open(self.fpath,'r') as binlog_file:286                 logging.info('[36mbegining to analyze the binlog file ,this may be take a long time !!![0m')287                 logging.info('[36manalyzing...[0m')288                 for bline in binlog_file:289                     if bline.find('Table_map:') != -1:290                         self.rowrecord(bline)291                         bline=''292                     elif bline.rstrip()=='### SET':293                         bline = bline[3:]294                         sqlcomma=1295                     elif bline.rstrip()=='### WHERE':296                         bline = bline[3:]297                         sqlcomma = 2298                     elif bline.startswith('###   @'):299                         len_f=len('###   @')300                         i=bline[len_f:].split('=')[0]301 302                         #处理timestamp类型303                         if bline[8+len(i):].split(' ')[2] == 'TIMESTAMP(0)':304                             stop_pos = bline.find(' /* TIMESTAMP(0) meta=')305                             bline = bline.split('=')[0] + '=from_unixtime(' + bline[:stop_pos].split('=')[1] + ')'306 307                         #处理负数存储方式308                         if bline.split('=')[1].startswith('-'):309                             stop_pos = bline.find(' /* TIMESTAMP(0) meta=')310                             bline = bline.split('=')[0] + '=' + bline.split('=')[1].split(' ')[0]+''311 312                         if sqlcomma==1:313                             bline = self.tbfield_set[int(i) - 1]+bline[(len_f+len(i)):]314                         elif sqlcomma==2:315                             bline = self.tbfield_where[int(i) - 1] + bline[(len_f+len(i)):]316 317                     elif bline.startswith('### DELETE') or bline.startswith('### INSERT') or bline.startswith('### UPDATE'):318                         bline = bline[3:]319 320                     elif bline.find('Xid =') != -1:321                         self.dml_tran(bline)322                         bline=''323                     else:324                         bline = ''325 326                     if bline.rstrip('') != '':327                         self.dml_sql = self.dml_sql + bline + ' '328         except Exception:329             return 'function do error'330 331     def esc_code(self,sql):332         esc={333              'x07':'','x08':'','x0c':'','x0a':'','x0d':'','x09':'','x0b':'','x5c':'',334             #'x27':''',335             'x22':'"','x3f':'?','x00':''336              }337 338         for k,v in esc.items():339             sql=sql.replace(k,v)340         return sql341 342     def binlogdesc(self):343 344         countsql='select sqltype , count(*) numbers from {} group by sqltype order by sqltype '.format(self.tbevent)345         print(countsql)346         self.cur.execute(countsql)347         count_row=self.cur.fetchall()348 349         update_count=0350         insert_couont=0351         delete_count=0352         for row in count_row:353             if row['sqltype']==1:354                 insert_couont=row['numbers']355             elif row['sqltype']==2:356                 update_count=row['numbers']357             elif row['sqltype']==3:358                 delete_count=row['numbers']359         logging.info('[1;35mTotal transactions number is {}: {} inserts, {} updates, {} deletes ![0m(all number is accurate, the other is approximate value) [0m'.format(insert_couont+update_count+delete_count,insert_couont,update_count,delete_count))360 361     def undosql(self,number):362         #这里会有几个问题:363         #1 如果一共有几十万甚至更多的事务操作,那么这个python脚本,极为占用内存,有可能执行错误;364         #2 如果单个事务中,涉及修改的行数高达几十万行,其binlog file 达好几G,这里也会有内存损耗问题;365         #所以,针对第一点,这里考虑对超多事务进行一个分批执行处理,每个批次处理number个事务,避免一次性把所有事务放到python中;但是第2点,目前暂未处理366 367         tran_num=1368         id=0369 370         tran_num_sql="select count(*) table_rows from {}".format(self.tbevent)371 372         self.cur.execute(tran_num_sql)373         tran_rows=self.cur.fetchall()374 375         for num in tran_rows:376             tran_num=num['table_rows']377 378         logging.info('[32mThere has {} transactions ,need {} batchs ,each batche doing {} transactions [0m'.format(tran_num,int(tran_num/number)+1,number))379 380         while id {} and auto_id <= {} order by auto_id desc;'.format(self.tbevent,tran_num-(id+number),tran_num-id)383             self.cur.execute(undo_sql)384 385             undo_rows=self.cur.fetchall()386             f_sql=''387 388             for u_row in undo_rows:389                 try:390                     self.on_cur.execute(u_row['undo_sql'])391                     self.on_mysqlconn.commit()392                 except Exception:393                     print('auto_id:',u_row['auto_id'])394             id+=number395 396 397     def undo_file(self,number):398         # 也可以选择私用undo_file将undo_sql导入到文件中,然后再source399 400         tran_num=1401         id=0402 403         tran_num_sql="select count(*) table_rows from {}".format(self.tbevent)404 405         self.cur.execute(tran_num_sql)406         tran_rows=self.cur.fetchall()407 408         for num in tran_rows:409             tran_num=num['table_rows']410 411         logging.info('copy undo_sql to undo file on : /tmp/flashback_undosql/undo_file_flashback.sql')412         logging.info('[32mThere has {} transactions ,need {} batchs to copy ,each batche doing {} transactions [0m'.format(tran_num,int(tran_num/number)+1,number))413 414         with open('/tmp/flashback_undosql/undo_file_flashback.sql', 'w') as w_f:415             while id {} and auto_id <= {} order by auto_id desc;'.format(self.tbevent,tran_num-(id+number),tran_num-id)418                 self.cur.execute(undo_sql)419 420                 undo_rows=self.cur.fetchall()421                 for u_row in undo_rows:422                     try:423                         w_f.write('begin;' + '')424                         w_f.write('# auto_id'+str(u_row['auto_id']) + '')425                         w_f.write(u_row['undo_sql'] + '')426                         w_f.write('commit;' + '')427                     except Exception:428                         print('auto_id',u_row['auto_id'])429                     #time.sleep(2)430                 id+=number431 432     def do(self):433         if self.action=='0':434             self.analyse_binlog()435             logging.info('[36mfinished to analyze the binlog file !!![0m')436             #self.binlogdesc()437         elif self.action=='1':438             self.undosql(10000)439 440     def closeconn(self):441         self.cur.close()442         self.on_cur.close()443         logging.info('release all db connections')444         logging.info('[33mAll done,check the {} which stored binlog event on host {} , port {} [0m'.format(self.tbevent,self.host,self.port))445 446 def main():447     p = flashback()448     p.do()449     p.closeconn()450 451 if __name__ == "__main__":452     main()

 

以上就是mysql基于binlog回滚工具实例详解的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/400859.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年11月6日 19:21:48
下一篇 2025年11月6日 19:22:49

相关推荐

  • 在Laravel框架中如何解决“Too many open files”错误?

    在laravel框架中解决“too many open files”错误的方法 在使用php7.3和laravel框架执行定时任务时,你可能会遇到一个错误提示,指出“打开文件太多”,错误信息大致如下: [2023-03-15 00:14:13] local.ERROR: include(/www/v…

    好文分享 2025年12月11日
    000
  • php中的卷曲:如何在REST API中使用PHP卷曲扩展

    php客户端url(curl)扩展是开发人员的强大工具,可以与远程服务器和rest api无缝交互。通过利用libcurl(备受尊敬的多协议文件传输库),php curl有助于有效执行各种网络协议,包括http,https和ftp。该扩展名提供了对http请求的颗粒状控制,支持多个并发操作,并提供内…

    2025年12月11日
    000
  • 高并发秒杀下,如何保证Redis和数据库库存一致性?

    高并发秒杀:PHP+Redis与数据库库存一致性解决方案 高并发秒杀系统中,如何确保Redis缓存库存与数据库库存数据一致性是核心挑战。本文分析基于Redis原子自减操作和数据库操作的秒杀流程,探讨可能出现的问题及解决方案。 常见的秒杀流程:下单 -> Redis扣减库存 -> 创建订单…

    2025年12月11日
    000
  • 苹果M1芯片Mac上编译安装Redis失败怎么办?

    苹果m1芯片mac编译安装redis失败的排查与解决 在苹果M1芯片的Mac电脑上编译安装Redis,常常会遇到各种问题,例如编译失败等。本文将指导您如何有效地排查和解决这些问题。 很多用户反馈编译错误,但仅提供截图不足以诊断问题。 为了高效解决,务必提供完整的错误日志文本。 以下几个关键点需要关注…

    2025年12月11日
    000
  • PHP PDO连接远程MySQL数据库:php.ini配置需要修改吗?

    PHP PDO连接远程MySQL数据库:关于php.ini配置的常见误区 许多PHP开发者在使用PDO连接远程MySQL数据库时,常常误认为需要修改php.ini文件。本文将对此进行详细解释,并消除一些常见的误解。 在Linux环境下,开发者可能会发现php.ini中关于PDO的配置项很少,主要涉及…

    2025年12月11日
    000
  • 微信公众号分享卡片信息缺失:新域名下分享失败怎么办?

    微信公众号分享调试:新域名下卡片信息缺失的解决方法 本文解决一个微信公众号个人订阅号网页分享问题:开发者使用个人订阅号AppID和密钥配置网站JSSDK微信分享功能,已添加JS安全域名,并确认拥有access_token和分享接口调用权限。旧域名分享正常,但新域名分享的微信卡片却缺少描述和图片,ti…

    2025年12月11日
    000
  • MySQL数据库和PHP数组在大数据处理方面有何区别?

    MySQL数据库与PHP数组:大数据处理策略的深度比较 本文将深入探讨MySQL数据库和PHP数组在处理大规模数据(例如:十万、百万甚至千万级数据)时的差异,重点关注数据读取和更新操作。 假设我们有一个包含id和name字段的MySQL数据库表,以及一个结构类似的PHP数组$arr = array(…

    2025年12月11日
    000
  • 如何用PHP动态获取URL参数作为MySQL字段名并输出数据?

    利用PHP动态获取MySQL字段名并输出数据 在PHP与MySQL数据库交互中,灵活选择数据库字段至关重要。本文将演示如何将动态字段名应用于数据库查询,并安全地输出数据。 问题:现有PHP代码从名为“table”的数据库表读取数据,但字段名“temp”是硬编码的。如何修改代码,使其能从URL参数中获…

    2025年12月11日
    000
  • PHP PDO执行多条SQL语句插入数据时如何避免语法错误?

    使用PHP原生PDO执行多条SQL语句(例如:同时修改表结构和插入数据)时,SQL语句的书写规范至关重要,否则容易引发语法错误。本文通过一个案例分析错误原因并提供解决方案。 问题: 开发者试图用单条SQL语句同时执行ALTER TABLE(添加字段)和INSERT INTO(插入数据)操作,代码如下…

    2025年12月11日
    000
  • 头条小程序登录获取openid失败:如何排查“code错误”?

    头条小程序登录:解决“code错误”导致openid获取失败 在开发头条小程序登录功能时,开发者经常遇到获取openid失败并提示“code错误”的情况。本文将通过一个实际案例,分析问题原因并提供解决方案。 案例中,开发者使用PHP代码,通过curl向头条小程序的jscode2session接口发送…

    2025年12月11日
    000
  • 如何在LAMP架构中整合Node.js或Python服务并处理网络请求?

    在LAMP架构中集成Node.js或Python服务 许多网站基于传统的LAMP架构(Linux, Apache, MySQL, PHP)构建,但随着项目扩展,可能需要添加Node.js或Python开发的新功能。由于Apache通常将80端口请求默认分配给PHP处理,因此在LAMP环境下启动并集成…

    2025年12月11日
    000
  • 内网CentOS 7服务器如何高效部署PHP环境?

    高效部署内网CentOS 7服务器PHP环境 许多开发者在搭建内网CentOS 7服务器PHP环境时,面临着如何高效同步本地虚拟机环境的难题。本文针对内网环境下,将本地虚拟机PHP环境迁移至服务器的问题,提供几种离线部署方案。 由于内网环境限制,网络同步工具(如rsync)不可用,因此需要采用离线方…

    2025年12月11日
    000
  • MySQL数据库和PHP数组:海量数据处理时该如何选择?

    MySQL数据库与PHP数组:海量数据处理效率对比 本文分析MySQL数据库和PHP数组在处理海量数据时的性能差异,尤其关注效率、CPU和IO占用。 假设我们有一个包含id和name字段的MySQL表,以及一个结构相同的PHP数组$arr = array(“id”=>”name”,…)。数…

    2025年12月11日
    000
  • 头条小程序登录获取openid失败提示“code错误”如何排查?

    头条小程序登录获取OpenID失败,提示“code错误”的解决方案 在开发头条小程序登录功能时,开发者经常遇到获取OpenID失败,并显示“code错误”的提示。本文将结合PHP代码示例,分析并解决此问题。 问题描述: 使用头条小程序登录后,PHP代码向头条开放平台接口请求OpenID时,返回“co…

    2025年12月11日
    200
  • 高效的异步操作:Guzzle Promises 的实践与应用

    最近在开发一个需要同时访问多个外部 API 的应用时,遇到了严重的性能问题。 传统的同步请求方式导致应用响应时间过长,用户体验极差。 每个 API 请求都需要等待完成才能发出下一个请求,这在处理大量请求时效率极低,严重影响了系统的吞吐量。 为了解决这个问题,我开始寻找异步处理的方案,最终选择了 Gu…

    2025年12月11日
    000
  • PHP记录:PHP日志分析的最佳实践

    php日志记录对于监视和调试web应用程序以及捕获关键事件,错误和运行时行为至关重要。它为系统性能提供了宝贵的见解,有助于识别问题,并支持更快的故障排除和决策 – 但仅当它有效地实施时。 在此博客中,我概述了PHP记录以及它在Web应用程序中的使用方式。然后,我概述了一些关键的最佳实践,…

    2025年12月11日
    000
  • 高效处理 JSON 数据:scienta/doctrine-json-functions 库的使用指南

    我最近参与的项目使用了 Doctrine ORM 管理数据库,其中一个实体包含一个 JSON 类型的字段,用于存储用户的配置信息。最初,我尝试使用原生 SQL 查询来处理 JSON 数据,例如使用 MySQL 的 JSON_EXTRACT 函数。这种方法虽然可以实现功能,但代码变得冗长且难以阅读,而…

    2025年12月11日
    000
  • 告别崩溃:使用Sentry提升Symfony应用的稳定性

    在开发过程中,我们都经历过应用崩溃的痛苦。 用户报告问题,但我们却苦于无法快速定位错误,只能在茫茫代码海洋中大海捞针。 更糟糕的是,一些错误可能只在特定环境或用户操作下才会出现,难以在本地复现。 我之前的项目使用的是简单的日志记录,虽然能记录一些错误信息,但缺乏上下文信息,例如请求参数、用户身份、堆…

    2025年12月11日
    000
  • 告别调试地狱:使用 Spatie/Laravel-Ray 提升 Laravel 应用调试效率

    我最近在开发一个 Laravel 应用,其中涉及到复杂的订单处理流程和用户交互。在调试过程中,我遇到了许多问题:数据库查询缓慢、邮件发送失败、业务逻辑错误等等。传统的调试方法,例如 dd() 和 var_dump(),虽然能提供一些信息,但效率低下,且难以追踪复杂的流程。 日志文件虽然记录了详细的信…

    2025年12月11日
    000
  • 微信小程序API接口请求返回空值怎么办?

    微信小程序API接口返回空值:排查与解决 使用GuzzleHttp库调用微信小程序API时,遇到空值返回?本文将引导您逐步排查此类问题。 上图展示了GuzzleHttp POST请求返回空值的情况。 这并非总是代码错误,可能有多种原因。 第一步,验证API接口本身。仔细阅读微信小程序官方文档,确认目…

    2025年12月11日
    000

发表回复

登录后才能评论
关注微信