前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >MYSQL BINLOG文件解析

MYSQL BINLOG文件解析

原创
作者头像
大大刺猬
发布2023-03-10 13:11:19
2.3K0
发布2023-03-10 13:11:19
举报
文章被收录于专栏:大大刺猬大大刺猬

写在前面

本文有点长, 不耐心的可以直接看总结.

说明

也可以使用gdb查看生成binlog过程的, 但是太复杂了... 还是看源码注释方便点.

本文主要介绍的binlog 4的格式,下面使用的均是binlog4的情况, 然后使用python解析该格式与mysqlbinlog做对比.

解析binlog的工具有: mysqlbinlog, binlog2sql, pymysqlreplication等.

下面的int类型未特殊说明均使用小端(little), 均为无符号

我的环境:

mysql 5.7.38-log

binlog_format = ROW

binlog_row_image = FULL

binlog_checksum = None #不影响,反正是最后4字节

可变长度计算方法:

本文例子均只考虑第一种情况. 主要是演示

第一个字段值

占用大小 取值范围

0-250

1字节 0-250

252

2字节 251-0xffff

253

3字节 0xffff-0xffffff

254

8字节 0xffffff-0xfffffffffffffff

BINLOG文件格式

官网介绍binlog文件 由开头的 4字节(0xFE 'bin’) 加上一些列的 event 组成.

第一个event是START_EVENT_V3或者FORMAT_DESCRIPTION_EVENT.

最后一个event是STOP_EVENT或者rROTATE_EVENT

所以我们着重看event组成即可.

BINLOG EVENT

binlog event组成

binlog event是 由 event_header(固定19字节) 加上 event_body(由各事件决定大小)组成


|???event?header(19字节)???|??????event?body???????????|


event_header

从左到右的字节为如下表内容

数据类型

名字

描述

int<4>

timestamp

时间戳

int<1>

event_type

event类型(5.7.38就有43种...)

int<4>

server-id

server_id

int<4>

event-size

event大小(含event_header的19字节)

int<4>

log-pos

下个event的起始地址(本event的结束地址)

int<2>

flags

flag

event_body

event_body由 post_header body crc32(可选,由变量binlog_checksum决定)组成


|???post_header??|????? body??? ???| crc32 |


不同的event的内容不一样, 下面讲下常用的event格式

binlog event分类

只列举部分.

管理类:

主要是控制识别binlog file的

  • START_EVENT_V3 第一个event
  • FORMAT_DESCRIPTION_EVENT 第一个event,替代start_event_v3的, 格式同start_event_v3
  • STOP_EVENT 最后一个event, 表示服务器已经停止运行(下次启动自动轮转)
  • ROTATE_EVENT 最有一个event, 服务器还在运行(自动轮转或者flush log)
  • SLAVE_EVENT
  • INCIDENT_EVENT
  • HEARTBEAT_EVENT

语句类:

  • QUERY_EVENT 存SQL的, 比如DDL
  • INTVAR_EVENT
  • RAND_EVENT
  • USER_VAR_EVENT
  • XID_EVENT 2pc提交的时候会写入这个event. 当作事务结束的标志

ROW格式类:

下面会讲这个的详细结构

  • TABLE_MAP_EVENT 每个row event前面都有个table_map_event记录表名和字段数据类型
  • DELETE_ROWS_EVENT 记录delete的
  • UPDATE_ROWS_EVENT 记录update的
  • WRITE_ROWS_EVENT 记录insert的

常见binlog event结构

FORMAT_DESCRIPTION_EVENT

每个Binlog文件的第一个event, 这个event记录如下数据

名字

大小(字节)

描述

binlog_version

int<2>

记录binlog版本的, 均为4

mysql_server_version

char<50>

记录mysql版本的, 不足的填充0

create_timestamp

int<4>

创建时间

event_header_length

int<1>

每个event的event_header的大小,固定值19

event_type_header_length

header的event_size减去上面的大小(19), 为数组类型, 每个记录值为1字节

每个event的event body的post header的大小

event_type_header_length 记录值参考(5.7.38-log) [56, 13, 0, 8, 0, 18, 0, 4, 4, 4, 4, 18, 0, 0, 95, 0, 4, 26, 8, 0, 0, 0, 8, 8, 8, 2, 0, 0, 0, 10, 10, 10, 42, 42, 0, 18, 52, 0, 1, 20, 121, 129, 83]

TABLE_MAP_EVENT

这个是row格式独有的, 记录下个row_event的表名,各字段类型.

除了开头8字节外(post_header)外, 均为可变长度....

名字

大小(字节)

描述

table_id

int<6>

表打开的id, 不是数据库里面的table_id

flags

2

保留字段

database_name_length

可变长度

数据库名长度

database_name

取决于database_name_length

数据库名(以0x00结尾, 这个字节不计算在database_name_length中)

table_name_length

可变长度

表名长度

table_name

取决于table_name_length

表名(以额外的0x00结尾, 就是不在table_name_length的计算中)

column_count

可变长度

多少个字段

column_type_list

取决于column_count

list类型, 每个字段的数据类型,用1字节表示(比如3表示int<4> 详情)

.....

.....

暂时用不上其它的

ROWS_EVENT

row_event 包含Delete_rows_log_event 和 Write_rows_log_event 和 Update_rows_log_event

继承关系如下图

Delete_rows_log_event 和 Write_rows_log_event 和 Update_rows_log_event 基本上一样, 都是继承自row_log_event

区别在于 Write_rows_log_event(insert) 没得Cols_before_image delete_rows_log_event没得Cols_after_image

代码语言:javascript
复制
         +-------------------------------------------------------+
         | Event Type | Cols_before_image | Cols_after_image     |
         +-------------------------------------------------------+
         |  DELETE    |   Deleted row     |    NULL              |
         |  INSERT    |   NULL            |    Inserted row      |
         |  UPDATE    |   Old     row     |    Updated row       |
         +-------------------------------------------------------+

结构如下

也是只有开头的8字节(post header)固定

名字

大小(字节)

描述

table_id

6

flags

2

width

可变长度

表有多少列

cols

INT((width + 7) / 8)

是否使用该列, 每列对于一个bit位, 对字节向上取整(数据读写只能按字节读写)

extra_row_info

Extra_row_info

暂不考虑, 以1字节算

columns_before_image

INT((width + 7) / 8)

仅update和delete有. 与binlog_row_image有关

columns_after_image

INT((width + 7) / 8)

仅update和insert有

Null_bit_mask

INT((width + 7) / 8)

某列是否为空, 每列对应一个比特位

row

table_map_event记录了大小

具体的数据(before_image + after_image), 如果还剩4字节的话, 就是CRC32校验. 每个image数据前面都有null_bit_mask

验证

生成测试数据

另起一个binlog 方便观察

代码语言:sql
复制
(root@127.0.0.1) [(none)]> flush logs;
Query OK, 0 rows affected (0.01 sec)

(root@127.0.0.1) [(none)]> create table db1.t20230310(id int primary key, name varchar(20));
Query OK, 0 rows affected (0.01 sec)

(root@127.0.0.1) [(none)]> begin;
Query OK, 0 rows affected (0.00 sec)

(root@127.0.0.1) [(none)]> insert into db1.t20230310 values(1,'first'),(2,'ddcw');
Query OK, 2 rows affected (0.00 sec)
Records: 2  Duplicates: 0  Warnings: 0

(root@127.0.0.1) [(none)]> delete from db1.t20230310 where id=1;
Query OK, 1 row affected (0.00 sec)

(root@127.0.0.1) [(none)]> update db1.t20230310 set name = 'ddcw update' where id=2;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

(root@127.0.0.1) [(none)]> commit;
Query OK, 0 rows affected (0.01 sec)

(root@127.0.0.1) [(none)]> show master status\G
*************************** 1. row ***************************
             File: m3308.001008
         Position: 1027
     Binlog_Do_DB: 
 Binlog_Ignore_DB: 
Executed_Gtid_Set: 6d650f1f-ba4e-11ed-99ab-000c2980c11e:1-29253,
7ab066ef-c1be-11ec-92dd-000c2980c11e:2579-2584:2700,
90bdfbb7-cbe2-11ec-a870-000c2980c112:25178542,
90bdfbb7-cbe2-11ec-a870-000c2980c11e:1-14138280:25178542,
aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:1-283382
1 row in set (0.00 sec)

hexdump解析

这个就是人工解析了, 只解析一部分. 重复的工作应该机器做

只解析第一个event吧...

代码语言:javascript
复制
12:23:55 [root@ddcw21 ~]#hexdump -C /data/mysql_3308/mysqllog/binlog/m3308.001008 
00000000  fe 62 69 6e 35 b0 0a 64  0f 6c 30 ad 18 77 00 00  |.bin5..d.l0..w..|
00000010  00 7b 00 00 00 01 00 04  00 35 2e 37 2e 33 38 2d  |.{.......5.7.38-|
00000020  6c 6f 67 00 00 00 00 00  00 00 00 00 00 00 00 00  |log.............|
00000030  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000040  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 13  |................|
00000050  38 0d 00 08 00 12 00 04  04 04 04 12 00 00 5f 00  |8............._.|

前4个字节 fe 62 69 6e 查询ascii表得 '\xfebin' 和上面官方说的一致

再来看看第一个event, 前19字节是header, 不看了, 太多了. 从 4 + 19 字节看起走

注意是使用的小端

名字

二进制数据

ascii编码后

binlog_version

b'\x04\x00'

4

mysql_server_version

b'5.7.38-log\x00\x00' 为方便观察,去掉了后面的填充字段

5.7.38-log (去掉了填充字段)

create_timestamp

b'\x00\x00\x00\x00'

0

event_header_length

b'\x13'

19

event_type_header_length

b'8\r\x00\x08\x00 .... \x00m/Lp' 为方便观察省略了中间的数据

[56, 13, 0, 8, 0, 18, 0, 4, 4, 4, 4, 18, 0, 0, 95, 0, 4, 26, 8, 0, 0, 0, 8, 8, 8, 2, 0, 0, 0, 10, 10, 10, 42, 42, 0, 18, 52, 0, 1, 20, 121, 129, 83]

Python解析

人工解析确实太费劲了, 我们使用python来解析

脚本见文末. 此脚本未解析 row(需要TABLE_MAP_EVENT) 和 crc32

代码语言:javascript
复制
import row_event
aa = row_event.parse_event('/data/mysql_3308/mysqllog/binlog/m3308.001008',1000)
for x in aa:
	print(x)
每个row_event上面都有个TABLE_MAP_EVENT
每个row_event上面都有个TABLE_MAP_EVENT

我们人工解析下最后个update的row

数据 b'\xfc\x02\x00\x00\x00\x04ddcw\xfc\x02\x00\x00\x00\x0bddcw update'

类型 [3, 15] 查表 得 3对应 int<4> 15对应varchar

未使用binlog crc32校验(mgr), 有的话, row的最后4字节就是crc32校验

代码语言:python
复制
>>> def btoint(bdata,t='little'):
...         return int.from_bytes(bdata,t)
... 
>>> aa = b'\xfc\x02\x00\x00\x00\x04ddcw\xfc\x02\x00\x00\x00\x0bddcw update'
>>> aa[0:1] #befor image null_bit_map
b'\xfc'
>>> btoint(aa[1:1+4]) #before image first column
2
>>> btoint(aa[5:6]) #查看varchar记录的长度, 仅考虑0-250(1字节的情况)
4
>>> aa[6:6+4] #before image seccond column
b'ddcw'
>>> 
>>> aa[10:11] #after image的 null_bit_map
b'\xfc'
>>> btoint(aa[11:15]) #after image的第一列 
2
>>> btoint(aa[15:16]) #after image的 第二列的长度
11
>>> aa[16:27]
b'ddcw update'
>>> 

得到 Update前数据 (2,'ddcw') update后数据为(2,'ddcw update')

然后使用mysqlbinlog 解析对比一下, 发现对的上, 说明没有解析错

总结

1. binlog文件由开头固定4字节和 各个event组成 (relay log也是)

2. 每个event由 header(固定19字节) 和 body组成, body又由post header 和 data组成(若剩余4字节就是crc32校验码)

3. 每个row_event前面都有个table_map_event记录表名,字段类型等信息.

4. 最后一个event如果是stop_event, 那就说明服务器停止了(下次启动字段切换), 如果是rota_event就说明文件切换了.

5. Delete_rows_log_event 和 Write_rows_log_event 和 Update_rows_log_event 都是继承自row_log_event, 区别在于 Write_rows_log_event(insert) 没得Cols_before_image

delete_rows_log_event没得Cols_after_image

附python代码

row_event.py

代码语言:javascript
复制
import binlog_event_type
import struct
def btoint(bdata,t='little'):
	return int.from_bytes(bdata,t)

def event_header(bdata):
	timestamp, event_type, server_id, event_size, log_pos, flags = struct.unpack("<LBLLLh",bdata[0:19])
	return {"timestamp":timestamp,'event_type':event_type,'server_id':server_id,'event_size':event_size,'log_pos':log_pos,'flags':flags,}


def first_event(bdata):
	#FORMAT_DESCRIPTION_EVENT
	ethl = len(bdata) - 57 #2 50 4 1 var
	ff = f'<h50sLB{ethl}s'
	binlog_version, mysql_server_version, create_timestamp, event_header_length, event_type_header_length = struct.unpack(ff,bdata)
	mysql_server_version = mysql_server_version.decode('ascii').replace('\x00','') #美化一下
	event_type_header_length = [ int(x) for x in event_type_header_length ] #event specific header length. 比如TABLE_MAP_EVENT = 8 (table_id:6 + flag:2) #记录其它event的post header的长度
	return {'binlog_version':binlog_version, 'mysql_server_version':mysql_server_version, 'create_timestamp':create_timestamp, 'event_header_length':event_header_length, 'event_type_header_length':event_type_header_length,}

def table_map_event(bdata):
	post_header = {'table_id':btoint(bdata[0:6]), 'flags':btoint(bdata[6:8])} #flags保留字段
	offset = 8
	database_length = btoint(bdata[offset:offset+1])
	offset +=1
	database_name = bdata[offset:offset+database_length].decode() #0x00 结尾
	offset += database_length + 1
	table_length = btoint(bdata[offset:offset+1])
	offset +=1
	table_name = bdata[offset:offset+table_length].decode() #0x00 结尾, 但是我不读,计数的时候别忘了就行
	offset += table_length + 1
	column_count = btoint(bdata[offset:offset+1]) #Packed Integer 我只考虑0-250个字段. 也就是占用1字节 计算方式https://dev.mysql.com/doc/dev/mysql-server/latest/classbinary__log_1_1Binary__log__event.html#packed_integer
	offset += 1
	column_type_list = []
	for x in range(column_count):
		column_type_list.append(btoint(bdata[offset:offset+1])) #先不做转换了.具体类型参考https://dev.mysql.com/doc/dev/mysql-server/latest/classbinary__log_1_1Table__map__event.html
		offset += 1

	#metadata_length和column_count一样, 但是我不想写了
	#省略 metadata_length metadata null_bits optional metadata fields
	return {
		'post_header':post_header,
		'body':{
			'database_name':database_name,
			'table_name':table_name,
			'column_type_list':column_type_list,
		}
		}
	


def row_event(bdata,imaget):
	#不解析具体的字段, 因为需要table_map才知道对应的字段类型
	#columns_before_image delete,update
	#columns_after_image  insert,update
	data = {}
	post_header = {'table_id':btoint(bdata[0:6]), 'flags':btoint(bdata[6:8])} #flags保留字段
	data['post_header'] = post_header
	data['body'] = {}
	offset = 8
	width = btoint(bdata[offset:offset+1])
	offset += 1
	_toff = int((width+7)/8)
	cols = btoint(bdata[offset:offset+_toff])
	offset += _toff
	extra_row_info = btoint(bdata[offset:offset+1])
	offset += 1
	if imaget == 30 or imaget == 31: #30 write   31 update   32 delete
		columns_after_image = btoint(bdata[offset:offset+_toff])
		offset += _toff
		data['body']['columns_after_image'] = columns_after_image
	if imaget == 32 or imaget == 31:
		columns_before_image = btoint(bdata[offset:offset+_toff])
		offset += _toff
		data['body']['columns_before_image'] = columns_before_image
	data['body']['row'] = bdata[offset:]
	data['body']['width'] = width
	data['body']['cols'] = cols
	data['body']['extra_row_info'] = extra_row_info
	return data
		



def parse_event(filename,n=10): #默认只解析前面10个event 
	data = []
	with open(filename,'rb') as f:
		magic = f.read(4)
		if magic != b'\xfebin':
			return False
		for x in range(n):
			event_data = None
			try:
				common_header = event_header(f.read(19))
			except:
				break
			event_bdata = f.read(common_header['event_size']-19)
			if common_header['event_type'] == binlog_event_type.FORMAT_DESCRIPTION_EVENT:
				event_data = first_event(event_bdata)
				common_header['event_type'] = 'FORMAT_DESCRIPTION_EVENT'
			elif common_header['event_type'] == binlog_event_type.WRITE_ROWS_EVENT:
				event_data = row_event(event_bdata,common_header['event_type'])
				common_header['event_type'] = 'WRITE_ROWS_EVENT'
			elif common_header['event_type'] == binlog_event_type.UPDATE_ROWS_EVENT:
				event_data = row_event(event_bdata,common_header['event_type'])
				common_header['event_type'] = 'UPDATE_ROWS_EVENT'
			elif common_header['event_type'] == binlog_event_type.DELETE_ROWS_EVENT:
				event_data = row_event(event_bdata,common_header['event_type'])
				common_header['event_type'] = 'DELETE_ROWS_EVENT'
			elif common_header['event_type'] == binlog_event_type.TABLE_MAP_EVENT:
				event_data = table_map_event(event_bdata)
				common_header['event_type'] = 'TABLE_MAP_EVENT'
			elif common_header['event_type'] == binlog_event_type.GTID_LOG_EVENT:
				common_header['event_type'] = 'GTID_LOG_EVENT'
			elif common_header['event_type'] == binlog_event_type.XID_EVENT:
				common_header['event_type'] = 'XID_EVENT'
			elif common_header['event_type'] == binlog_event_type.QUERY_EVENT:
				common_header['event_type'] = 'QUERY_EVENT'
				event_data = event_bdata
			elif common_header['event_type'] == binlog_event_type.STOP_EVENT:
				common_header['event_type'] = 'STOP_EVENT'
			elif common_header['event_type'] == binlog_event_type.PREVIOUS_GTIDS_LOG_EVENT:
				common_header['event_type'] = 'PREVIOUS_GTIDS_LOG_EVENT'
			elif common_header['event_type'] == binlog_event_type.ROTATE_EVENT:
				common_header['event_type'] = 'ROTATE_EVENT'
			data.append({'event_header':common_header,'event_body':event_data})
	return data

binlog_event_type.py

从源码 libbinlogevents/include/binlog_event.h 里面复制出来的

代码语言:javascript
复制
# -*- coding: utf-8 -*-
# libbinlogevents/include/binlog_event.h
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_V1 = 23
UPDATE_ROWS_EVENT_V1 = 24
DELETE_ROWS_EVENT_V1 = 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 #描述之前的gtid信息(不需要扫描之前的binlog文件)

TRANSACTION_CONTEXT_EVENT= 36

VIEW_CHANGE_EVENT= 37

XA_PREPARE_LOG_EVENT= 38

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 写在前面
    • 说明
      • 我的环境:
        • 可变长度计算方法:
        • BINLOG文件格式
        • BINLOG EVENT
          • binlog event组成
            • event_header
            • event_body
          • binlog event分类
            • 管理类:
            • 语句类:
            • ROW格式类:
        • 常见binlog event结构
          • FORMAT_DESCRIPTION_EVENT
            • TABLE_MAP_EVENT
              • ROWS_EVENT
              • 验证
                • 生成测试数据
                  • hexdump解析
                    • Python解析
                    • 总结
                    • 附python代码
                      • row_event.py
                        • binlog_event_type.py
                        领券
                        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
                        http://www.vxiaotou.com