关于mysql:简明的binlog-event解析

3次阅读

共计 16051 个字符,预计需要花费 41 分钟才能阅读完成。

  • 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 中有表中列信息(类型、非空等)。

上图就是 binlog file 中的 event 显示格局,能够看到和咱们在下面转换为 base64 串是一样的,只是咱们把它们离开来操作了。

解析 event

获取到 binlog event 的 base64 编码,开始咱们的解析,解析后果间接在控制台打印进去。这里次要参看的是官网文档形容的 event 格局。

https://dev.mysql.com/doc/int…

先把 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/int…,这里不再赘述。

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/int…

代码中判断如果是 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 位数值小于 2 16,则 column_count 通过 1 + 2 = 3 个字节存储;

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

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

参考官网文档的形容(地址:https://dev.mysql.com/doc/int…)

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]

而源码中则是:

/*
  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/int…

减少一个新的判断逻辑,判断 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_b…

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


Enjoy GreatSQL :)

## 对于 GreatSQL

GreatSQL 是由万里数据库保护的 MySQL 分支,专一于晋升 MGR 可靠性及性能,反对 InnoDB 并行查问个性,是实用于金融级利用的 MySQL 分支版本。

相干链接:GreatSQL 社区 Gitee GitHub Bilibili

GreatSQL 社区:

欢送来 GreatSQL 社区发帖发问
https://greatsql.cn/

技术交换群:

微信:扫码增加 GreatSQL 社区助手 微信好友,发送验证信息 加群

正文完
 0