使用 Python 解析并“篡改”MySQL的 Binlog( 二 )


下面是这个 Python 小程序的例子:
#! /usr/bin/python3import sysif len(sys.argv) != 3:print ('Please run chtype.py inputType changedType.')sys.exit()inputType=open(sys.argv[1],"rb")changedType=open(sys.argv[2],"wb")changedType.write(inputType.read(382))changedType.write(chr(30).encode())inputType.seek(1,1)while True:line = inputType.readline()if not line:breakchangedType.write(line)inputType.close()changedType.close()我们把原来的 Binlog 和修改后的 Binlog 进行一个对比:
使用 Python 解析并“篡改”MySQL的 Binlog文章插图
发现这两个 Binlog 只有一个字节有区别 , 也就是 type_code 从 32 变成了 30 , 注意 Binlog 里面显示的是 16 进制的数字 。
我们分别应用一下原来的 Binlog 和修改后的 Binlog , 看看效果如何?
$ mysql-e "select * from test.tt1";$ mysqlbinlog ./scut.000023_ch |mysql$ mysql-e "select * from test.tt1";+---------------------+| col1|+---------------------+| aaaaaaaaaaaaaaaaaaa |+---------------------+$ mysqlbinlog ./scut.000023 |mysql$ mysql-e "select * from test.tt1";$ mysqlbinlog ./scut.000023_ch |mysql$ mysql-e "select * from test.tt1";+---------------------+| col1|+---------------------+| aaaaaaaaaaaaaaaaaaa |+---------------------+我们发现这两个 Binlog 可以分别把对应的记录删除和插入到 MySQL 数据库中 , 这样我们就成功地实现了类似于 Oracle 的 flashback 功能 。
找出 Binlog 中的大事务由于 ROW 模式的 Binlog 是每一个变更都记录一条日志 , 因此一个简单的 SQL , 在 Binlog 里可能会产生一个巨无霸的事务 , 例如一个不带 where 的 update 或 delete 语句 , 修改了全表里面的所有记录 , 每条记录都在 Binlog 里面记录一次 , 结果是一个巨大的事务记录 。 这样的大事务经常是产生麻烦的根源 。 我的一个客户有一次向我抱怨 , 一个 Binlog 前滚 , 滚了两天也没有动静 , 我把那个 Binlog 解析了一下 , 发现里面有个事务产生了 1.4G 的记录 , 修改了 66 万条记录!下面是一个简单的找出 Binlog 中大事务的 Python 小程序 , 我们知道用 mysqlbinlog 解析的 Binlog , 每个事务都是以 BEGIN 开头 , 以 COMMIT 结束 。 我们找出 BENGIN 前面的 “# at” 的位置 , 检查 COMMIT 后面的 “# at” 位置 , 这两个位置相减即可计算出这个事务的大小 , 下面是这个 Python 程序的例子 。
$ cat ./checkBigTran.py #! /usr/bin/python3import sysposition=0beginPosition=0endPosition=0maxSize=0isEnd=0for line in sys.stdin:if line[: 4]=='# at':position=int(line[5:])if isEnd:endPosition=positionisEnd=0if line[: 5]=='BEGIN':beginPosition=positionif line[: 6]=='COMMIT':isEnd=1if endPosition-beginPosition>maxSize:maxBeginPosition= beginPositionmaxEndPosition=endPositionmaxSize=endPosition-beginPositionprint("The largest transaction size is %d, the begion position is %d, the end position is %d." % (maxSize,maxBeginPosition,maxEndPosition))用这个小程序检查一下可能包含大事务的 Binlog:
$ mysqlbinlog binlog1|./checkBigTran.py The largest transaction size is 1468183501, the begion position is 5737766, the end position is 1473921267.发现里面果然包含了一个 1.4G 的大事务 。
切割 Binlog 中的大事务对于大的事务 , MySQL 会把它分解成多个事件(注意一个是事务 TRANSACTION , 另一个是事件 EVENT) , 事件的大小由参数 binlog-row-event-max-size 决定 , 这个参数默认是 8K 。 因此我们可以把若干个事件切割成一个单独的略小的事务 , 例如下面这个 Binlog:
mysql> show binlog events in 'scut.000025';+-------------+-----+----------------+-----------+-------------+--------------------------------------------------------+| Log_name| Pos | Event_type| Server_id | End_log_pos | Info|+-------------+-----+----------------+-----------+-------------+--------------------------------------------------------+| scut.000025 |4 | Format_desc|1024 |123 | Server ver: 5.7.31-0ubuntu0.16.04.1-log, Binlog ver: 4 || scut.000025 | 123 | Previous_gtids |1024 |154 ||| scut.000025 | 154 | Anonymous_Gtid |1024 |219 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS'|| scut.000025 | 219 | Query|1024 |291 | BEGIN|| scut.000025 | 291 | Rows_query|1024 |343 | # insert into tt1 values ('1')|| scut.000025 | 343 | Table_map|1024 |391 | table_id: 111 (test.tt1)|| scut.000025 | 391 | Write_rows|1024 |429 | table_id: 111 flags: STMT_END_F|| scut.000025 | 429 | Rows_query|1024 |481 | # insert into tt1 values ('2')|| scut.000025 | 481 | Table_map|1024 |529 | table_id: 111 (test.tt1)|| scut.000025 | 529 | Write_rows|1024 |567 | table_id: 111 flags: STMT_END_F|| scut.000025 | 567 | Xid|1024 |598 | COMMIT /* xid=397 */|| scut.000025 | 598 | Rotate|1024 |640 | scut.000026;pos=4|+-------------+-----+----------------+-----------+-------------+--------------------------------------------------------+12 rows in set (0.01 sec)