- 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/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]

而原始碼中則是:
/*
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
GreatSQL社區:
歡迎來GreatSQL社區發帖提問
https://greatsql.cn/

技術交流群:
微信:掃碼添加
GreatSQL社區助手微信好友,發送驗證資訊加群,

轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/520720.html
標籤:其他
上一篇:Windows 環境搭建 PostgreSQL 邏輯復制高可用架構資料庫服務
下一篇:教你如何解決T+0的問題
