简明的binlog event解析

读取mysql binlog event_MySQL Binlog(四)—— MySQL binlog event解析——基础知识 MySQL Binlog(四)—— MySQL binlog event解析——基础知识前言MySQL复制实质上就是讲binlog同步到从库上进行应用,前面的文章已经讲了如何建立复制连接,从这篇文章开始,我们来讲讲binlog里面的内容,看看主从之间同步具体同步了哪些内容,首先会介绍一些关于binlog event的基础知识,之后的文章中会详细对binlog event进行分析。event基础知识... 阅读详情
  • GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源。
  • GreatSQL是MySQL的国产分支版本,使用上与MySQL一致。

用一个简明、清晰的步骤来解析一下DML操作产生的binlog event。主要是 TABLE_MAP_EVENT 和 UPDATE_ROWS_EVENT 类型的event。使用语法简单易上手的Golang来编码。数据库使用的是MySQL 5.7.34版本, Golang 1.15版本。

获取binlog event

获取binlog一般是模拟成从库封装通讯 package 向主库发送binlog dump命令(COM_BINLOG_DUMP或者COM_BINLOG_DUMP_GTID)来获取binlog。在这篇文章中,是封装的 COM_BINLOG_DUMP 向数据库请求的指定位点的 event。

模拟操作产生event

查看自动自交参数:

mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit    | ON    |
+---------------+-------+
1 row in set (0.00 sec)

简单的表结构:

mysql> create table test (id int primary key, name char(10), addr varchar(10), birthdate date);
Query OK, 0 rows affected (0.03 sec)

准备数据:

mysql> insert into test value (1, 'tom', 'Hollywood', '1940-02-10');
Query OK, 1 row affected (0.02 sec)

mysql> insert into test value (2, 'Jerry', 'Hollywood', '1940-02-10');
Query OK, 1 row affected (0.01 sec)

mysql> select * from test;
+----+-------+-----------+------------+
| id | name  | addr      | birthdate  |
+----+-------+-----------+------------+
|  1 | tom   | Hollywood | 1940-02-10 |
|  2 | Jerry | Hollywood | 1940-02-10 |
+----+-------+-----------+------------+
2 rows in set (0.01 sec)

查看binlog:

mysql> show binary logs;
+--------------------+-----------+
| Log_name           | File_size |
+--------------------+-----------+
| greatdb-bin.000001 |     98896 |
+--------------------+-----------+
1 row in set (0.00 sec)

查看event(部分输出省略):

mysql> show binlog events in 'greatdb-bin.000001';
+--------------------+-------+----------------+-----------+-------------+-----------------------------------------------------------------------------------------------------+
| Log_name           | Pos   | Event_type     | Server_id | End_log_pos | Info                                                                                                |
+--------------------+-------+----------------+-----------+-------------+-----------------------------------------------------------------------------------------------------+
| greatdb-bin.000001 | 98110 | Gtid           |        13 |       98175 | SET @@SESSION.GTID_NEXT= '7950f91c-2f13-11ed-b2a8-52540099600c:420'                                 |
| greatdb-bin.000001 | 98175 | Query          |        13 |       98336 | use `test`; create table test (id int primary key, name char(10), addr varchar(10), birthdate date) |
| greatdb-bin.000001 | 98336 | Gtid           |        13 |       98401 | SET @@SESSION.GTID_NEXT= '7950f91c-2f13-11ed-b2a8-52540099600c:421'                                 |
| greatdb-bin.000001 | 98401 | Query          |        13 |       98473 | BEGIN                                                                                               |
| greatdb-bin.000001 | 98473 | Table_map      |        13 |       98527 | table_id: 455 (test.test)                                                                           |
| greatdb-bin.000001 | 98527 | Write_rows     |        13 |       98584 | table_id: 455 flags: STMT_END_F                                                                     |
| greatdb-bin.000001 | 98584 | Xid            |        13 |       98615 | COMMIT /* xid=43677697 */                                                                           |
| greatdb-bin.000001 | 98615 | Gtid           |        13 |       98680 | SET @@SESSION.GTID_NEXT= '7950f91c-2f13-11ed-b2a8-52540099600c:422'                                 |
| greatdb-bin.000001 | 98680 | Query          |        13 |       98752 | BEGIN                                                                                               |
| greatdb-bin.000001 | 98752 | Table_map      |        13 |       98806 | table_id: 455 (test.test)                                                                           |
| greatdb-bin.000001 | 98806 | Write_rows     |        13 |       98865 | table_id: 455 flags: STMT_END_F                                                                     |
| greatdb-bin.000001 | 98865 | Xid            |        13 |       98896 | COMMIT /* xid=43678192 */                                                                           |
+--------------------+-------+----------------+-----------+-------------+-----------------------------------------------------------------------------------------------------|

执行更新,生成 TABLE_MAP_EVENT 和 UPDATE_ROWS_EVENT :

mysql> update test set birthdate = '1940-02-11' where name = 'Jerry';
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select * from test;
+----+-------+-----------+------------+
| id | name  | addr      | birthdate  |
+----+-------+-----------+------------+
|  1 | tom   | Hollywood | 1940-02-10 |
|  2 | Jerry | Hollywood | 1940-02-11 |
+----+-------+-----------+------------+
2 rows in set (0.00 sec)

查看event(部分输出省略):

mysql> show binlog events in 'greatdb-bin.000001';
+--------------------+-------+----------------+-----------+-------------+---------------------------------------------------------------------+
| Log_name           | Pos   | Event_type     | Server_id | End_log_pos | Info                                                                |
+--------------------+-------+----------------+-----------+-------------+---------------------------------------------------------------------+
| greatdb-bin.000001 | 98896 | Gtid           |        13 |       98961 | SET @@SESSION.GTID_NEXT= '7950f91c-2f13-11ed-b2a8-52540099600c:423' |                   
| greatdb-bin.000001 | 98961 | Query          |        13 |       99033 | BEGIN                                                               |  
| greatdb-bin.000001 | 99033 | Table_map      |        13 |       99087 | table_id: 455 (test.test)                                           |       
| greatdb-bin.000001 | 99087 | Update_rows    |        13 |       99171 | table_id: 455 flags: STMT_END_F                                     |
| greatdb-bin.000001 | 99171 | Xid            |        13 |       99202 | COMMIT /* xid=43691718 */                                           |
+--------------------+-------+----------------+-----------+-------------+---------------------------------------------------------------------+

因为 binlog_rows_query_log_events 参数是关闭的,所以不会产生 ROWS_QUERY_LOG_EVENT 类型的event,我们的一个更新语句在开启GTID的情况下产生了五个event,分别是:GTID_LOG_EVENT、QUERY_EVENT、TABLE_MAP_EVENT、UPDATE_ROWS_EVENT、XID_EVENT。最后的 XID_EVENT 也标志着事务成功提交。

至此,获取到了目标 TABLE_MAP_EVENT 和 UPDATE_ROWS_EVENT 两个类型的event。通过计算也可以看到 Xid event 大小是固定的 32 字节。

发送binlog dump请求event

封装一个 COM_BINLOG_DUMP 数据包,然后建立连接发送到数据库,就可以获得 event 了。这个过程可另文详说,这里就不展开了。
我把两个 event 转成了base64串,主要是为了方便“携带”,拿着base64串去解析。

这里有一个细节,就是我们向主库发送binlog dump后,获得的二进制字节流,第 0 位是一个表示当前包是否正常的标志位,第 0 位的值为 0,获得的package就是正常的。

EVENT TYPE: TABLE_MAP_EVENT
Puk/YxMNAAAANgAAAA+DAQAAAMcBAAAAAAEABHRlc3QABHRlc3QABAP+DwoE/hQUAA7FA/Pg

EVENT TYPE: UPDATE_ROWS_EVENT_V2
Puk/Yx8NAAAAVAAAAGODAQAAAMcBAAAAAAEAAgAE///wAgAAAAVKZXJyeQlIb2xseXdvb2RKKA/wAgAAAAVKZXJyeQlIb2xseXdvb2RLKA/v9Mdc

通过mysqlbinlog解析(不使用–base64-output="decode-rows"选项)得到的文件中,也会输出base64串,通常 TABLE_MAP_EVENT、UPDATE_ROWS_EVENT 会放在一起,这是因为 TABLE_MAP_EVENT 中有表中列信息(类型、非空等)。

image-20221009160414204

上图就是binlog file中的event显示格式,可以看到和我们在上面转换为base64串是一样的,只是我们把它们分开来操作了。

解析event

获取到binlog event的base64编码,开始我们的解析,解析结果直接在控制台打印出来。这里主要参看的是官方文档描述的 event 格式。

https://dev.mysql.com/doc/internals/en/binlog-event-header.html

先把base64串转为二进制,得到一个二进制的数组([]byte):

x := `Puk/YxMNAAAANgAAAA+DAQAAAMcBAAAAAAEABHRlc3QABHRlc3QABAP+DwoE/hQUAA7FA/Pg`
eventByte, _ := base64.StdEncoding.DecodeString(x)

解析event header

定义一个位置偏移常量,用来表示event字节数组的偏移位置。
首先是解析 event header ,它的长度固定,是19个字节。
然后解析前4位字节,根据文档,这4个字节是timestamp格式的时间戳。解析后,偏移移动4个字节。

pos := 0
fmt.Println("timestamp: " + strconv.Itoa(utils.Byte2Int(eventByte[pos:pos+4])) + " time: " + time.Unix(int64(utils.Byte2Int(eventByte[pos:pos+4])), 0).String()) // timestamp, 4 byte
pos += 4

接下来,是1个字节长度的 event type 。

eventType := utils.Byte2Int(eventByte[pos : pos+1])
fmt.Println("event type: " + strconv.Itoa(eventType)) // event type, 1 byte
pos += 1

接下来是4个字节长度的 server id,这个id就是实例的server id。binlog会记录下这个信息。

fmt.Println("server id: " + strconv.Itoa(utils.Byte2Int(eventByte[pos:pos+4]))) // server id, 4 byte
pos += 4

这个输出结果是 13。与我们在数据库中查看是一致的。

mysql> select @@server_id;
+-------------+
| @@server_id |
+-------------+
|          13 |
+-------------+
1 row in set (0.00 sec)

接下来是4个字节长度的event length,也就是当前这个event的二进制字符串数组的长度,包含 event header 和 event body 的总长度。

fmt.Println("event length: " + strconv.Itoa(utils.Byte2Int(eventByte[pos:pos+4]))) // event length, 4 byte
pos += 4

接下来是4个字节长的 event log position ,也就是下一个 event 的 log position,而不是当前这个event。这里输出是:99087,正是下一个 UPDATE_ROWS_EVENT event的开始位置。

fmt.Println("log pos: " + strconv.Itoa(utils.Byte2Int(eventByte[pos:pos+4]))) // log pos, 4 byte
pos += 4

接下来是2个字节长度的flags,flags详细释义可以参考:https://dev.mysql.com/doc/internals/en/binlog-event-flag.html ,这里不再赘述。

fmt.Println("flags: " + strconv.Itoa(utils.Byte2Int(eventByte[pos:pos+2]))) // flags, 2 byte
pos += 2

至此,19个字节长度的 event header 解析就完成了。最重要的信息当属 event type ,因为我们要根据这个type信息才能继续下面的 event body 解析。不同类型的event, event header 布局相同,但是 event body 却不同,自然就要区别对待了。

TABLE_MAP_EVENT的event body

在解析 event header 时,获得了 event type 。根据 event type 来解析不同的event。这个 event type 在源码binlog_event.h文件中定义,其中 TABLE_MAP_EVENT = 19,UPDATE_ROWS_EVENT = 31。

TABLE_MAP_EVENT的布局格式参考:

https://dev.mysql.com/doc/internals/en/table-map-event.html

代码中判断如果是 TABLE_MAP_EVENT ,就按照既定格式解析。很简单的判断:

if eventType == 19 {
  ......
}

在这个判断中,增加解析逻辑。event body开头就是6个字节长度的table id ,这个值与我们执行

mysql> show binlog events in 'greatdb-bin.000001';

输出中的 table_id: 455 是一致性。解析逻辑如下:

fmt.Println("table id:" + strconv.Itoa(byte2Int(eventByte[pos:pos+6]))) // table id, 6 byte
pos += 6

接下来是2个字节长度的flags

fmt.Println("flags:" + strconv.Itoa(byte2Int(eventByte[pos : pos+2]))) // flag, 2 byte
pos += 2

接下来是schema信息,schema是变长的,无法确认用户使用了多长的字段,MySQL规定用户的schema长度不得超过64个字符。所以这里先用一个字节来存储scheme的长度信息。

lenSchemaN := byte2Int(eventByte[pos : pos+1])
fmt.Println("schema name length: " + strconv.Itoa(lenSchemaN)) // schema name length, 1 byte
pos += 1

scheme的长度信息确定了接下来的几个字节的schema name信息。

fmt.Println("schema name: " + bytesToString(eventByte[pos:pos+lenSchemaN])) // schema name, (schema name length) byte
pos += lenSchemaN

接下来是1个字节的空闲位,用[00]填充,跳过不解析。

之后是1个字节长度的table name length,这与上面的schema name格式相同。确定了table name length,就可以解析后面的table name了。

之后仍然是一个字节的空闲位,用[00]填充。

pos += 1   //[00]

lenTableN := byte2Int(eventByte[pos : pos+1])
fmt.Println("table name length: " + strconv.Itoa(lenTableN))   // table name length, 1 byte
pos += 1

fmt.Println("table name: " + bytesToString(eventByte[pos:pos+lenTableN]))   // table name, (table name length) byte
pos += lenTableN

pos += 1   //[00]

接下来是相对比较复杂的 column_count 信息,也就是执行的语句涉及了多少表中的列。首先是1个字节长度的 lenenc-int 位,就是通过这1个字节的值来判断后面使用了多长字节来存储 column_count 值。

如果 lenenc-int 位数值小于251,则 column_count 通过1个字节存储;

如果 lenenc-int 位数值小于216,则 column_count 通过 1 + 2 = 3 个字节存储;

如果 lenenc-int 位数值小于224,则 column_count 通过 1 + 3 = 4 个字节存储;

如果 lenenc-int 位数值小于264,则 column_count 通过 1 + 8 = 9 个字节存储;

参考官方文档的描述(地址:https://dev.mysql.com/doc/internals/en/integer.html#packet-Protocol::LengthEncodedInteger )

An integer that consumes 1, 3, 4, or 9 bytes, depending on its numeric value
To convert a number value into a length-encoded integer:
If the value is < 251, it is stored as a 1-byte integer.
If the value is ≥ 251 and < ( 216 ), it is stored as fc + 2-byte integer.
If the value is ≥ (216) and < (224), it is stored as fd + 3-byte integer.
If the value is ≥ (224) and < (264) it is stored as fe + 8-byte integer.

下面就是解析获得 column_count 的代码逻辑:

lenLenenc := byte2Int(eventByte[pos : pos+1])
fmt.Println("lenenc-int: " + strconv.Itoa(lenLenenc))

var columnCnt int
if lenLenenc < 251 {
  columnCnt = byte2Int(eventByte[pos : pos+1])
  pos += 1
} else if lenLenenc >= 251 && lenLenenc < int(math.Pow(2, 16)) {
  columnCnt = byte2Int(eventByte[pos : pos+3])
  pos += 3
} else if lenLenenc >= int(math.Pow(2, 16)) && lenLenenc < int(math.Pow(2, 24)) {
  columnCnt = byte2Int(eventByte[pos : pos+4])
  pos += 4
} else if lenLenenc >= int(math.Pow(2, 24)) && lenLenenc < int(math.Pow(2, 64)) {
  columnCnt = byte2Int(eventByte[pos : pos+9])
  pos += 9
}
fmt.Println("column_count: " + strconv.Itoa(columnCnt))

接下来就是表中列的类型信息:column_type_def 。当然,在event里是不会存储具体列类型文本,如int、char等,而是存储的类型的“代号”,例如“0x03”代表int类型,这在 binary_log_types.h 中进行了定义。为了方便,把本文涉及到的类型放在一个map结构中,方便取用:

// column type, binary_log_types.h
var typeCol map[string]string
typeCol = make(map[string]string)
typeCol["03"] = "MYSQL_TYPE_LONG"    // int
typeCol["0a"] = "MYSQL_TYPE_DATE"    // date
typeCol["0f"] = "MYSQL_TYPE_VARCHAR" // varchar
typeCol["fe"] = "MYSQL_TYPE_STRING"  // char

接下来字节记录了表的列的类型,每一列占1个字节,总共column-count个字节。

var columnDef []string
a := eventByte[pos : pos+columnCnt]
for i := 0; i < columnCnt; i++ {
  columnDef = append(columnDef, typeCol[hex.EncodeToString(a[i:i+1])])
}
fmt.Println(columnDef) //column-def
pos += columnCnt

接下来是 column_meta_def 信息,MySQL文档的描述是:
column_meta_def (lenenc_str) – array of metainfo per column, length is the overall length of the metainfo-array in bytes, the length of each metainfo field is dependent on the columns field type

column_meta_def 记录了表的列的类型的元数据(通常为列的长度和精度),有些列类型没有元数据,有些类型有元数据,根据类型不同,有的用1个字节记录,有的用2个字节记录。
例如: MYSQL_TYPE_NEWDECIMAL(0xf6)有2个字节的元数据,第一个字节用于记录长度(precision), 第二个字节用于记录精度(scale): decimal(8,2) meta_def = 0x0802。这个信息暂时用不到,不做详细解析。

for _, v := range columnDef {
			if v == "MYSQL_TYPE_STRING" || v == "MYSQL_TYPE_VAR_STRING" || v == "MYSQL_TYPE_VARCHAR" || v == "MYSQL_TYPE_DECIMAL" || v == "MYSQL_TYPE_NEWDECIMAL" || v == "MYSQL_TYPE_ENUM" {
        pos += 2
			} else if strings.Contains(v, "int") || strings.Contains(v, "bit") || v == "date" {
				continue
			} else if v == "MYSQL_TYPE_BLOB" || v == "MYSQL_TYPE_DOUBLE" || v == "MYSQL_TYPE_FLOAT" {
				pos += 1
			}
		}

接下来是 null_bitmap 信息。也就是使用bitmap格式存储哪些列是null。计算 null_bitmap 字节长度的公式:
文档中是:[len=(column_count + 8) / 7]

image-20221009160550571

而源码中则是:

/*
  Calculate a bitmap for the results of maybe_null() for all columns.
  The bitmap is used to determine when there is a column from the master
  that is not on the slave and is null and thus not in the row data during
  replication.
*/
uint num_null_bytes = (m_colcnt + 7) / 8;
m_data_size += num_null_bytes;

我们表中的列的数量是4,((4 + 8) / 7) 与 ((4 + 7) / 8)值相同,因此暂时看不出差异。但是假设我们表中有24列,((24 + 8) / 7) = 4 ,而 ((4 + 7) / 8) = 3,24列需要24 bit位来存储 null_bitmap ,所以这里文档中描述应该是错误的。这里暂时以源码为准。

lenNull := (columnCnt + 7) / 8
//lenNull := (columnCnt + 8) / 7
fmt.Println("null bitmap length: " + strconv.Itoa(lenNull))
bitmap := ""
for _, v := range eventByte[pos : pos+lenNull] {
  bitmap = bitmap + " " + reverse(byteToBinaryString(v))
}
pos += lenNull
fmt.Println("null bitmap: " + bitmap)

接下来的4个字节就是binlog event的checksum了,业务解析暂时用不到,这里没有解析。

pos += 4

我们来看一下TABLE_MAP_EVENT解析输出:

timestamp: 1665132862 time: 2022-10-07 16:54:22 +0800 CST
event type: 19
server id: 13
event length: 54
log pos: 99087
flags: 0
table id:455
flags:1
schema name length: 4
schema name: test
table name length: 4
table name: test
lenenc-int: 4
column_count: 4
columnDef: [MYSQL_TYPE_LONG MYSQL_TYPE_STRING MYSQL_TYPE_VARCHAR MYSQL_TYPE_DATE]
null bitmap length: 1
null bitmap:  00000000
checksum: [14 197 3 243]

到了这里,整个 TABLE_MAP_EVENT 就解析结束了。

UPDATE_ROWS_EVENT的event body

接下来是 UPDATE_ROWS_EVENT的解析。
参看文档地址:https://dev.mysql.com/doc/internals/en/rows-event.html

增加一个新的判断逻辑,判断event是不是 UPDATE_ROWS_EVENT 类型:

  //UPDATE_ROWS_EVENT  Payload
	if eventType == 31 {
    ......
  }

UPDATE_ROWS_EVENT的 event body 的前6个字节同样是table id。这里可以参考MySQL源码:log_event.cc:11591,Rows_log_event::write_data_header。

fmt.Println("table id: " + strconv.Itoa(byte2Int(eventByte[pos:pos+6]))) // table id
pos += 6

接下来是2个字节的flags和2个字节的extra-data-length。

fmt.Println("flags: " + strconv.Itoa(byte2Int(eventByte[pos:pos+2]))) // flags
pos += 2

pos += 2 //  extra-data-length

接下来是列的数量 number of columns 。这是变长的,我们的测试表只有4列,所以只需要1个字节就可以存储。为了代码简单,没有考虑更多列的情况,

fmt.Println("columns: " + strconv.Itoa(byte2Int(eventByte[pos:pos+1]))) // columns
cols := (byte2Int(eventByte[pos:pos+1]) + 7) / 8
pos += 1

接下来就是两个同样长度的 columns-present-bitmap ,因为这是 UPDATE_ROWS_EVENT ,会存储修改前和修改后的列的数据,所以需要两个 columns-present-bitmap 来显示列是否为空。columns-present-bitmap1 是展示修改前是否用到的列,columns-present-bitmap2 是展示修改后是否用到的列。

fmt.Println("columns-present-bitmap1: " + bytesToBinaryString(eventByte[pos:pos+cols])) // columns-present-bitmap1
pos += cols

fmt.Println("columns-present-bitmap2: " + bytesToBinaryString(eventByte[pos:pos+cols])) // columns-present-bitmap2
pos += cols

如果执行代码:这两行会输出:

columns-present-bitmap1: [11111111]
columns-present-bitmap2: [11111111]

各列的bit位都是1,说明修改语句涉及了所有列。因为只有4列,剩余bit位没有用到,用1填充。

接下来也就是真正存储的行数据了:rows。修改前的数据和修改后的数据。
首先是修改前数据的 nul-bitmap ,也就是更新涉及的那些列的值是null。文档描述它的长度是:nul-bitmap, length (bits set in ‘columns-present-bitmap1’+7)/8

bits := ((len(eventByte[pos:pos+cols]) * 8) + 7) / 8
bitmap1 := ""
for _, v := range eventByte[pos : pos+bits] {
  bitmap1 = bitmap1 + " " + reverse(byteToBinaryString(v))
}
fmt.Println("null bitmap1:" + bitmap1)
pos += bits

因为所有列都不为空,为了代码逻辑简单,就不在后面解析列数据判断 nul-bitmap 了,实际上是需要判断对应列是否为空再解析。

接下来是第一列数据,从 TABLE_MAP_EVENT 解析的结果看,第一列是int类型,所以使用固定长度4个字节。这里没有判断 TABLE_MAP_EVENT 中列类型信息,实际是需要的,同样为了代码简洁,直接解析。

fmt.Println("col1: " + strconv.Itoa(byte2Int(eventByte[pos:pos+4])))
pos += 4

接下来是第二列,数据类型是char类型,需要1个字节来存储长度信息(超过255,则需要2个字节,这是从 TABLE_MAP_EVENT 中的 column_meta_def 信息得来,这里没有考虑)。

len2 := byte2Int(eventByte[pos : pos+1])
pos += 1

fmt.Println("col2: " + bytesToString(eventByte[pos:pos+len2]))
pos += len2

接下来是第三列,数据类型是varchar,同样是变长字段,这里以为数据较小,使用1个字节存储长度。

len3 := byte2Int(eventByte[pos : pos+1])
pos += 1

fmt.Println("col3: " + bytesToString(eventByte[pos:pos+len3]))
pos += len3

接下来是第四列,数据类型是date,使用固定3个字节存储。字节十六进制输出:0x4a280f,因为是小端字节序存储,所以解析顺序应该是0x0f284a,转换为二进制:000011110010100001001010。从左至右,前15位表示年份,所以,year=‘000011110010100’=1940,接下来4位表示月份,所以,month=‘0010’=2,后5位表示日志,所以,day=‘01010’=10。最终解析到日期值:1940-2-10

x1 := reverse(eventByte[pos : pos+3])
dateByte1 := []byte(bytesToBinaryString(x1))
fmt.Println("col4: " + strconv.Itoa(str2DEC(string(dateByte1[:15]))) + "-" + strconv.Itoa(str2DEC(string(dateByte1[15:19]))) + "-" + strconv.Itoa(str2DEC(string(dateByte1[19:]))))
pos += 3

修改前的rows解析完成,下面是修改后的rows。
逻辑与解析修改前数据逻辑一样,开始是 nul-bitmap ,其后是各列数据,解析逻辑与修改前数据解析逻辑一致,不再重复。

最后,是4个字节的checksum。

我们来看一下解析输出:

timestamp: 1665132862 time: 2022-10-07 16:54:22 +0800 CST
event type: 31
server id: 13
event length: 84
log pos: 99171
flags: 0
table id: 455
flags: 1
---------------------------------------
columns: 4
columns-present-bitmap1: 11111111
columns-present-bitmap2: 11111111
before VALUES:  ------------------------------------
null bitmap1: 00001111
col1: 2
col2: Jerry
col3: Hollywood
col4: 1940-2-10
after VALUES:  ------------------------------------
null bitmap2: 00001111
col1: 2
col2: Jerry
col3: Hollywood
col4: 1940-2-11
checksum: [239 244 199 92]

至此我们完成了 UPDATE_ROWS_EVENT 类型 event 的解析。

总结一下

之所以选择 TABLE_MAP_EVENT 和 UPDATE_ROWS_EVENT 两个event 来解析,是因为 TABLE_MAP_EVENT 中包含 ROWS_EVENT 需要的表结构信息,包括列类型,类元数据信息。选择 UPDATE_ROWS_EVENT ,是因为 UPDATE_ROWS_EVENT 是 ROWS_EVENT 中相对比较复杂的event,WRITE_ROWS_EVENT 和 DELETE_ROWS_EVENT 相对要简单得多。

解析涉及到的数据类型比较少,只有 int、char、varchar、date四种,除了date略显复杂外,其它三个比较简单。更多的数据类型没有涉及。

从解析过程中来看,MySQL的逻辑日志还是很大的,包含了很多额外数据,一条DML语句会产生五个event,这也是为什么开启binlog为什么会很容易达到IO瓶颈或者网络瓶颈,MySQL为此也使用了组提交来进行优化。

这可能是写的最差的解析代码了,毫无重用和封装可言,这主要是为了阅读代码逻辑方便,能够清晰的看出对event的解析过程。文中很多调用的函数没有列出,主要是很占篇幅。

为了能够得到详细参考,这个比较差劲的代码被上传到了gitee:

https://gitee.com/huajiashe_byte/binlog_parse

希望通过文章的解析,binlog不再是黑盒,更好的理解 MySQL 的主从复制原理。


Enjoy GreatSQL 😃

关于 GreatSQL

GreatSQL是由万里数据库维护的MySQL分支,专注于提升MGR可靠性及性能,支持InnoDB并行查询特性,是适用于金融级应用的MySQL分支版本。

相关链接: GreatSQL社区 Gitee GitHub Bilibili

团子杂记:SAP收入确认工具RAR(Revenue Recognition&Reporting)在新收入准则下的应用 SAP收入确认工具RAR知识分享 阅读详情

相关推荐

ComfyUI实战:AnimateDiff与ControlNet结合,打造无缝风格化视频转绘

本文详细介绍了如何利用ComfyUI平台,结合AnimateDiff与ControlNet插件,实现高质量的视频风格化转绘。通过搭建节点工作流、调整关键参数,有效解决画面闪烁与失真问题,并分享了进阶玩法与常见问题解决方案,帮助用户轻松打造流畅、风格统一的动态视觉效果。

weixin_29039773的博客 338

深入MySQL(二)—— 深入理解binlog event 与解析原理

事件结构的某些细节在所有versions中是不变的, 其他取决于版本。 在任何给定版本中,不同类型的事件在 event data 部分的结构中都不同。 除 first event 外,event data 由 fixed part 与 variable part 两部分组成, fixed part 由 event type 决定, variable part 取决于event记录的内容。

洛丹伦的夏天 2053

【课程设计】TCP/IP 应用环境中服务器的部署与实现

本项目旨在通过搭建一个虚拟网络环境,实现软路由系统的配置和服务器的搭建与测试。具体包括安装虚拟机 VMWare Workstation,安装 Windows Server 2003 操作系统,配置软路由系统(包括双网卡安装、协议配置和测试),以及搭建并测试 DHCP 服务、DNS 服务、WWW 服务、FTP 服务和 SMTP/POP3 服务。

weixin_74907236的博客 1524

简明binlog event解析

用一个简明、清晰的步骤来解析一下DML操作产生的binlog event。主要是 TABLE_MAP_EVENT 和 UPDATE_ROWS_EVENT 类型的event。使用语法简单易上手的Golang来编码。数据库使用的是MySQL 5.7.34版本, Golang 1.15版本。获取binlog event获取binlog一般是模拟成从库封装通讯 package 向主库发送binlog du...

老叶茶馆 1086

快速上手springboot+vue的前后端分离项目

使用webStorm新建项目后,把 .lock文件删除后项目运行才不报错,配置文件 vue.config.js 中加入 lintOnSave: false 关闭eslint语法检测。在多层嵌套的父子组件之间传递状态已经十分麻烦,而Vue更是没有为兄弟组件提供直接共享数据的办法。基于这个问题,许多框架提供了解决方案——使用全局的状态管理器,将所有分散的共享数据交由状态管理器保管,Vue也不例外。其中一种解决CORS 的方法, 在springboot项目中跨域的方法或类加上@CrossOrigin注解。

m0_61950264的博客 1906

利用RFIDIOt和NFC进行Hitag2密码暴力破解: Python代码实现

RFID即无线射频识别,是一种通信技术,可以通过无线电信号读取、捕获和保存终端上的信息。这项技术在物流、零售、医疗等领域有着广泛的应用。NFC即近场通信,是一种短距离的高频无线通信技术,允许电子设备之间进行非接触式点对点数据传输。Hitag2是一种常见的RFID标签,广泛应用于汽车防盗系统、门禁系统等。由于其安全性在过去被广泛认可,很多设备都采用了这种标签。但近年来随着密码破解技术的发展,Hitag2的安全性受到了一些挑战。

qq_38334677的博客 1561

mysql binlog详细介绍

MySQL binlog记录的所有操作实际上都有对应的事件类型的,STATEMENT格式中的DML操作对应的是QUERY_EVENT类型,ROW格式下的DML操作对应的是ROWS_EVENT类型。 下面就挑几个重点的说一下: QUERY_EVENT QUERY_EVENT以文本的形式来记录事务的操作。 QUERY_EVENT类型的事件通常在以下几种情况下使用: 1. 事务开始...

weixin_40524659的博客 1529

微信小程序|基于小程序实现人脸识别对比

此文主要通过小程序实现对比人脸相似度,并返回相似度分值,可以基于分值判断是否为同一人。人脸登录、用户认证等场景都可以用到。

商务合作 / 项目定制 / 学习交流。个人vx:lovely_wml 2万+

mysql binlog event_mysql原理~binlog系列之~event浅谈

一 将具体分析binlog中的常用event二 Row格式事件核心ROW_LOG_EVENT+TABLE_MAP_EVENT+GTID_EVENT三 event生效机制在binlog cache中生效,然后刷新纪录写入binlog中四 具体event 内容0 FORMAT_DESCRIPTION_EVENT1 PREVIOUS_GTIDS_LOG_EVENT2 GTID_LOG_EVENT3 ...

weixin_32703763的博客 420

网络安全 | 企业网络安全的十大挑战及解决方案

网络安全 | 企业网络安全的十大挑战及解决方案,随着信息技术的飞速发展,企业数字化转型进程不断加速,网络安全问题日益成为企业运营与发展面临的关键挑战。本文深入剖析企业网络安全的十大挑战,包括网络攻击日益复杂、数据泄露风险、内部威胁、物联网安全、云安全、合规性要求、移动办公安全、供应链安全、人才短缺以及安全预算有限等,并针对每个挑战详细阐述相应的解决方案,旨在为企业构建全面、有效的网络安全体系提供指导与参考,助力企业在复杂多变的网络环境中保障自身信息资产安全,实现可持续发展。

不踩坑,不知道坑有多深;不总结,踩过的坑白踩。 4万+

Vue3 - Element Plus 全局修改更换主题色配置详细教程,element-plus自定义主题色全局替换主色调强力解决方案,支持动态修改主题颜色/动态皮肤,将默认蓝色主题色更换为其他颜色!

vue3,nuxt3,element-plus,组件库,主题色,修改主题颜色,更换主题色,如何修改组件默认主题蓝色,项目主题色定制,修改失败,主题色覆盖不了,:root直接修改,hover颜色没改,自定义主题色,vue3全局设置elementPlus主题色最新详细修改教程,怎么修改全局的颜色,scss变量,覆盖原来的主题色改为自己的颜色,统一替换,有没有好办法,颜色不变,失败,无效,动态主题切换,主题颜色怎么在项目里直接改,plus主题色更换,覆盖默认主题色,最强最好用,设置主题色,换肤,皮肤,动态更换

🏆 前端领域优质创作者 1万+

书生笔记-mysql binlog Event详解-概述

Binlog是一种二进制格式的文件,理解Binlog二进制格式能够帮助我们进一步理解MySQL的主从复制原理。本文将对Binlog文件格式进行分析 官方文档: https://dev.mysql.com/doc/internals/en/binlog-event.html Binlog文件头 Binlog文件,前4个字节固定为一个Magic Number,十六进制值为fe62696e。 [root@localhost logs]# hexdump -Cv binlog.000012 0000

魏书生的博客 1217

安装WSL过程中出现注册表编辑器无法重命名WSL。重命名项时出现错误的解决方案

本文主要介绍了安装WSL过程中出现注册表编辑器无法重命名WSL。重命名项时出现错误的解决方案,希望能对使用Windows WSL的同学们有所帮助。 文章目录 1. 问题描述 2. 解决方案

weixin_43178406的博客 9万+

书生笔记-Binlog Event Type解析

binlog以事件的形式记录数据库变更情况。通过执行show binlog events in "XXX"命令可以查看事件 源码中定义的binlog event type事件类型,源码位置:binlog_event.h 先来看一个例子: 可以看到第一个event type是前面文章我们介绍的第一个event,FORMAT_DESCRIPTION_EVENT,event type 为Format_desc,这里我们还可以看到其他类型,如: Previous_gtids:上一个binlog结束的gt

魏书生的博客 2765

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

binlog二进制文件解析

本文主要介绍MySQL的binlog二进制文件的解析,目的是更好的了解binlog文件的构成并做相应的二次开发,并帮助对主从复制机制有更多理解。 以下内容基于row的日志格式。操作系统redhat7,MySQL版本5.7.17,开启GTID。 一 Binlog生成 在MySQL中执行flush logs,并执行以下操作。 mysql> create database ab...

勇往直前的专栏 4177

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

mysql binlog events_MySQL binlog event 详解

我也是只菜鸡,blog写的不对或者不严谨的地方还请大伙指出来,我及时改正,免得误人子弟。实验环境:CentOS7.3.1611 + MySQL社区版 5.7.19参考:小菜鸟DBA的微信公众号推送官方文档:3个在线工具:binlog实际上由一个个不同类型的binlog event组成,每个binlog event还包含了event header部分和event data部分(可选)。【注意:每个e...

weixin_39768247的博客 758

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

mysql binlog row 解析_解析MySQL binlog --(5)ROWS_EVENT

1、介绍对于statement格式的binlog,所有增删改的SQL语句都记录在QUERY_EVENT中,而row格式的binlog则以ROWS_EVENT格式记录对数据库的修改。ROWS_EVENT分为3种:WRITE_ROWS_EVENT、UPDATE_ROWS_EVENT、DELETE_ROWS_EVENT,分别对应insert、update、delete。WRITE_ROWS_EVENT包...

weixin_42298225的博客 670

mysql binlog event_解析MySQL binlog --(5)ROWS_EVENT:WRITE_ROWS_EVENT、UP

对于statement格式的binlog,所有增删改的SQL语句都记录在QUERY_EVENT中,而row格式的binlog则以ROWS_EVENT格式记录对数据库的修改。ROWS_EVENT分为3种:WRITE_ROWS_EVENT、UPDATE_ROWS_EVENT、DELETE_ROWS_EVENT,分别对应insert、update、delete。WRITE_ROWS_EVENT包含了要插...

weixin_36204061的博客 582

[小技巧8]MySQL中binlog日志的三种模式解析,以及event解析

MySQL中binlog日志的三种模式解析,以及event解析

分享工作中解决的问题,希望能帮到你 1205

mysql xid_MySQL Binlog 文件格式解析(XID_EVENT)

1. XID_EVENT 是什么?MySQL Binlog 文件由 event 组成,event 有不同的类型,本文介绍的 XID_EVENT 表示一个事务的提交操作。举个例子,执行一条事务,然后查看这个事务生成的 Binlog event,如下:事务 SQL:begin;insert into t1 select 1,1;commit;生成的 Binlog Event:mysql> sho...

weixin_29248653的博客 1813

mysql binlog events_MySQL binlog之log event

MySQL binlog之log event概述binlog是mysql以二进制形式打印的日志,它默认不加密,不压缩。每个正常的binlog文件头部,有4个字节的标记,值为0xfe 0x62 0x69 0x6e。LOG_EVENT是binlog里的单位。除去头部的标记,binlog就是一个LOG_EVENT的序列。每个LOG_EVENT都独立单元,没有互相引用的关系,它也有自己的二进制头部,主要是...

weixin_35819549的博客 349

MySQL binlog格式解析

MySQL binlog格式解析 ----by 石锅拌饭 binlog想必大家都不陌生,在主从复制或者某些情况下的数据恢复会用到。由于binlog是二进制数据,要查看一般都借助mysqlbinlog工具。这篇笔记分析了binlog格式,希望能够了解下mysqlbinlog工具背后所做的事情。 1.什么时候写binlog在说明什么时候写binlog前,先

__七把刀__ 4208

mysql binlog gtid_解析MYSQL BINLOG二进制格式(8)--GTID_LOG_EVENT/ANONYMOUS_GTID_LOG_EVENT及其他...

原创:转载请说明出处谢谢!上接http://blog.itpub.net/7728585/viewspace-2133188/ 解析MYSQL BINLOG 二进制格式(1)--准备工作http://blog.itpub.net/7728585/viewspace-2133189/ 解析MYSQL BINLOG 二进制格式(2)--FORMAT_DESCRIPTION_EVENThttp://bl...

weixin_39946964的博客 949

mysql binlog xid_解析MYSQL BINLOG 二进制格式(7)--Xid_log_event/XID_EVENT

原创:转载请说明出处谢谢!上接http://blog.itpub.net/7728585/viewspace-2133188/ 解析MYSQL BINLOG 二进制格式(1)--准备工作http://blog.itpub.net/7728585/viewspace-2133189/ 解析MYSQL BINLOG 二进制格式(2)--FORMAT_DESCRIPTION_EVENThttp://bl...

weixin_39874881的博客 464

mysql binlog解析 c_解析MySQL binlog --(3)QUERY_EVENT

一、介绍QUERY_EVENT事件以文本的形式记录信息。当binlog格式时statement时,执行的语句都存储在QUERY_EVENT中,如下所示:mysql> show binlog events in "mysql-bin.000002";+------------------+-----+-------------+-----------+-------------+-------...

weixin_33987275的博客 420

mysql-bin 解析_Mysql binlog 解析

首先,我们知道MySQL本身就带有replication的机制,我们需要伪造一个slave,向master注册,这样的话master才会发送binlog event。注册很简单,通过调用limysql.so中的cli_advanced_command(),指定binlog filename+position,向master发送COM_BINLOG_DUMP命令。在发送dump命令的时候,我们可以指...

weixin_36053084的博客 717
上一篇: 图文结合带你搞懂InnoDB MVCC
下一篇: 实现一个简单Database6
GreatSQL社区
博客等级 码龄5年 1210粉丝 390原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值