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

作者:姚远
专注于 Oracle、MySQL 数据库多年 , Oracle 10G 和 12C OCM , MySQL 5.6 , 5.7 , 8.0 OCP 。 现在鼎甲科技任技术顾问 , 为同事和客户提供数据库培训和技术支持服务 。
本文来源:原创投稿
*爱可生开源社区出品 , 原创内容未经授权不得随意使用 , 转载请联系小编并注明来源 。
前言MySQL 的 Binlog 记录着 MySQL 数据库的所有变更信息 , 了解 Binlog 的结构可以帮助我们解析Binlog , 甚至对 Binlog 进行一些修改 , 或者说是“篡改” , 例如实现类似于 Oracle 的 flashback 的功能 , 恢复误删除的记录 , 把 update 的记录再还原回去等 。 本文将带您探讨一下这些神奇功能的实现 , 您会发现比您想象地要简单得多 。 本文指的 Binlog 是 ROW 模式的 Binlog , 这也是 MySQL 8 里的默认模式 , STATEMENT 模式因为使用中有很多限制 , 现在用得越来越少了 。
Binlog 的结构Binlog 由事件(event)组成 , 请注意是事件(event)不是事务(transaction) , 一个事务可以包含多个事件 。 事件描述对数据库的修改内容 。
从 MySQL 5 版本开始 , Binlog 采用的是 v4 版本 。 事件的类型根据 MySQL 的内部文档 , 有下面 36 类:
enum Log_event_type {UNKNOWN_EVENT= 0,START_EVENT_V3= 1,QUERY_EVENT= 2,STOP_EVENT= 3,ROTATE_EVENT= 4,INTVAR_EVENT= 5,LOAD_EVENT= 6,SLAVE_EVENT= 7,CREATE_FILE_EVENT= 8,APPEND_BLOCK_EVENT= 9,EXEC_LOAD_EVENT= 10,DELETE_FILE_EVENT= 11,NEW_LOAD_EVENT= 12,RAND_EVENT= 13,USER_VAR_EVENT= 14,FORMAT_DESCRIPTION_EVENT= 15,XID_EVENT= 16,BEGIN_LOAD_QUERY_EVENT= 17,EXECUTE_LOAD_QUERY_EVENT= 18,TABLE_MAP_EVENT = 19,PRE_GA_WRITE_ROWS_EVENT = 20,PRE_GA_UPDATE_ROWS_EVENT = 21,PRE_GA_DELETE_ROWS_EVENT = 22,WRITE_ROWS_EVENT = 23,UPDATE_ROWS_EVENT = 24,DELETE_ROWS_EVENT = 25,INCIDENT_EVENT= 26,HEARTBEAT_LOG_EVENT= 27,IGNORABLE_LOG_EVENT= 28,ROWS_QUERY_LOG_EVENT= 29,WRITE_ROWS_EVENT = 30,UPDATE_ROWS_EVENT = 31,DELETE_ROWS_EVENT = 32,GTID_LOG_EVENT= 33,ANONYMOUS_GTID_LOG_EVENT= 34,PREVIOUS_GTIDS_LOG_EVENT= 35,ENUM_END_EVENT/* end marker */ };每个 Binlog 文件总是以 Format Description Event 作为开始 , 以 Rotate Event 结束作为结束 , 我们来看一个 Binlog 的例子:
mysql>show binlog events in 'scut.000023';+-------------+-----+----------------+-----------+-------------+--------------------------------------------------------+| Log_name| Pos | Event_type| Server_id | End_log_pos | Info|+-------------+-----+----------------+-----------+-------------+--------------------------------------------------------+| scut.000023 |4 | Format_desc|1024 |123 | Server ver: 5.7.31-0ubuntu0.16.04.1-log, Binlog ver: 4 || scut.000023 | 123 | Previous_gtids |1024 |154 ||| scut.000023 | 154 | Anonymous_Gtid |1024 |219 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS'|| scut.000023 | 219 | Query|1024 |291 | BEGIN|| scut.000023 | 291 | Rows_query|1024 |330 | # delete from tt1|| scut.000023 | 330 | Table_map|1024 |378 | table_id: 111 (test.tt1)|| scut.000023 | 378 | Delete_rows|1024 |434 | table_id: 111 flags: STMT_END_F|| scut.000023 | 434 | Xid|1024 |465 | COMMIT /* xid=216 */|| scut.000023 | 465 | Rotate|1024 |507 | scut.000024;pos=4|+-------------+-----+----------------+-----------+-------------+--------------------------------------------------------+9 rows in set (0.00 sec)关于 “show binlog events” 语法显示的每一列的作用说明如下:
使用 Python 解析并“篡改”MySQL的 Binlog文章插图
【使用 Python 解析并“篡改”MySQL的 Binlog】每个事件类型的说明可以参考《MySQL 的内部文档》
我们这里说明一下这里遇到的几个事件类型:
使用 Python 解析并“篡改”MySQL的 Binlog文章插图
根据官方文档 , 事件(event)数据结构如下:
+=====================================+| event| timestamp0 : 4|| header +----------------------------+|| type_code4 : 1||+----------------------------+|| server_id5 : 4||+----------------------------+|| event_length9 : 4||+----------------------------+|| next_position13 : 4||+----------------------------+|| flags17 : 2||+----------------------------+|| extra_headers19 : x-19 |+=====================================+| event| fixed partx : y|| data+----------------------------+|| variable part|+=====================================+恢复误删除的记录现在我们已经了解了 Binlog 的结构 , 我们可以试着修改 Binlog 里的数据 。 例如前面举例的 Binlog 删除了一条记录 , 我们可以试着把这条记录恢复 , Binlog 里面有个删除行(DELETE_ROWS_EVENT)的事件 , 就是这个事件删除了记录 , 这个事件和写行(WRITE_ROWS_EVENT)的事件的数据结构是完全一样的 , 只是删除行事件的类型是 32 , 写行事件的类型是 30 , 我们把对应的 Binlog 位置的 32 改成 30 即可把已经删除的记录再插入回去 。 从前面的 “show binlog events” 里面可看到这个 DELETE_ROWS_EVENT 是从位置 378 开始的 , 这里的位置就是 Binlog 文件的实际位置(以字节为单位) 。 从事件(event)的结构里面可以看到 type_code 是在 event 的第 5 个字节 , 我们写个 Python 小程序把把第383(378+5=383)字节改成 30 即可 。 当然您也可以用二进制编辑工具来改 。