主頁 > 資料庫 > 簡明的binlog event決議

簡明的binlog event決議

2022-10-27 07:40:12 資料庫

  • 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

GreatSQL社區:

歡迎來GreatSQL社區發帖提問
https://greatsql.cn/

20221008151816

技術交流群:

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

圖片

轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/520711.html

標籤:MySQL

上一篇:教你如何解決T+0的問題

下一篇:「MySQL高級篇」MySQL索引原理,設計原則

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • GPU虛擬機創建時間深度優化

    **?桔妹導讀:**GPU虛擬機實體創建速度慢是公有云面臨的普遍問題,由于通常情況下創建虛擬機屬于低頻操作而未引起業界的重視,實際生產中還是存在對GPU實體創建時間有苛刻要求的業務場景。本文將介紹滴滴云在解決該問題時的思路、方法、并展示最終的優化成果。 從公有云服務商那里購買過虛擬主機的資深用戶,一 ......

    uj5u.com 2020-09-10 06:09:13 more
  • 可編程網卡芯片在滴滴云網路的應用實踐

    **?桔妹導讀:**隨著云規模不斷擴大以及業務層面對延遲、帶寬的要求越來越高,采用DPDK 加速網路報文處理的方式在橫向縱向擴展都出現了局限性。可編程芯片成為業界熱點。本文主要講述了可編程網卡芯片在滴滴云網路中的應用實踐,遇到的問題、帶來的收益以及開源社區貢獻。 #1. 資料中心面臨的問題 隨著滴滴 ......

    uj5u.com 2020-09-10 06:10:21 more
  • 滴滴資料通道服務演進之路

    **?桔妹導讀:**滴滴資料通道引擎承載著全公司的資料同步,為下游實時和離線場景提供了必不可少的源資料。隨著任務量的不斷增加,資料通道的整體架構也隨之發生改變。本文介紹了滴滴資料通道的發展歷程,遇到的問題以及今后的規劃。 #1. 背景 資料,對于任何一家互聯網公司來說都是非常重要的資產,公司的大資料 ......

    uj5u.com 2020-09-10 06:11:05 more
  • 滴滴AI Labs斬獲國際機器翻譯大賽中譯英方向世界第三

    **桔妹導讀:**深耕人工智能領域,致力于探索AI讓出行更美好的滴滴AI Labs再次斬獲國際大獎,這次獲獎的專案是什么呢?一起來看看詳細報道吧! 近日,由國際計算語言學協會ACL(The Association for Computational Linguistics)舉辦的世界最具影響力的機器 ......

    uj5u.com 2020-09-10 06:11:29 more
  • MPP (Massively Parallel Processing)大規模并行處理

    1、什么是mpp? MPP (Massively Parallel Processing),即大規模并行處理,在資料庫非共享集群中,每個節點都有獨立的磁盤存盤系統和記憶體系統,業務資料根據資料庫模型和應用特點劃分到各個節點上,每臺資料節點通過專用網路或者商業通用網路互相連接,彼此協同計算,作為整體提供 ......

    uj5u.com 2020-09-10 06:11:41 more
  • 滴滴資料倉庫指標體系建設實踐

    **桔妹導讀:**指標體系是什么?如何使用OSM模型和AARRR模型搭建指標體系?如何統一流程、規范化、工具化管理指標體系?本文會對建設的方法論結合滴滴資料指標體系建設實踐進行解答分析。 #1. 什么是指標體系 ##1.1 指標體系定義 指標體系是將零散單點的具有相互聯系的指標,系統化的組織起來,通 ......

    uj5u.com 2020-09-10 06:12:52 more
  • 單表千萬行資料庫 LIKE 搜索優化手記

    我們經常在資料庫中使用 LIKE 運算子來完成對資料的模糊搜索,LIKE 運算子用于在 WHERE 子句中搜索列中的指定模式。 如果需要查找客戶表中所有姓氏是“張”的資料,可以使用下面的 SQL 陳述句: SELECT * FROM Customer WHERE Name LIKE '張%' 如果需要 ......

    uj5u.com 2020-09-10 06:13:25 more
  • 滴滴Ceph分布式存盤系統優化之鎖優化

    **桔妹導讀:**Ceph是國際知名的開源分布式存盤系統,在工業界和學術界都有著重要的影響。Ceph的架構和演算法設計發表在國際系統領域頂級會議OSDI、SOSP、SC等上。Ceph社區得到Red Hat、SUSE、Intel等大公司的大力支持。Ceph是國際云計算領域應用最廣泛的開源分布式存盤系統, ......

    uj5u.com 2020-09-10 06:14:51 more
  • es~通過ElasticsearchTemplate進行聚合~嵌套聚合

    之前寫過《es~通過ElasticsearchTemplate進行聚合操作》的文章,這一次主要寫一個嵌套的聚合,例如先對sex集合,再對desc聚合,最后再對age求和,共三層嵌套。 Aggregations的部分特性類似于SQL語言中的group by,avg,sum等函式,Aggregation ......

    uj5u.com 2020-09-10 06:14:59 more
  • 爬蟲日志監控 -- Elastc Stack(ELK)部署

    傻瓜式部署,只需替換IP與用戶 導讀: 現ELK四大組件分別為:Elasticsearch(核心)、logstash(處理)、filebeat(采集)、kibana(可視化) 下載均在https://www.elastic.co/cn/downloads/下tar包,各組件版本最好一致,配合fdm會 ......

    uj5u.com 2020-09-10 06:15:05 more
最新发布
  • day02-2-商鋪查詢快取

    功能02-商鋪查詢快取 3.商鋪詳情快取查詢 3.1什么是快取? 快取就是資料交換的緩沖區(稱作Cache),是存盤資料的臨時地方,一般讀寫性能較高。 快取的作用: 降低后端負載 提高讀寫效率,降低回應時間 快取的成本: 資料一致性成本 代碼維護成本 運維成本 3.2需求說明 如下,當我們點擊商店詳 ......

    uj5u.com 2023-04-20 08:33:24 more
  • MySQL中binlog備份腳本分享

    關于MySQL的二進制日志(binlog),我們都知道二進制日志(binlog)非常重要,尤其當你需要point to point災難恢復的時侯,所以我們要對其進行備份。關于二進制日志(binlog)的備份,可以基于flush logs方式先切換binlog,然后拷貝&壓縮到到遠程服務器或本地服務器 ......

    uj5u.com 2023-04-20 08:28:06 more
  • day02-短信登錄

    功能實作02 2.功能01-短信登錄 2.1基于Session實作登錄 2.1.1思路分析 2.1.2代碼實作 2.1.2.1發送短信驗證碼 發送短信驗證碼: 發送驗證碼的介面為:http://127.0.0.1:8080/api/user/code?phone=xxxxx<手機號> 請求方式:PO ......

    uj5u.com 2023-04-20 08:27:27 more
  • 快取與資料庫雙寫一致性幾種策略分析

    本文將對幾種快取與資料庫保證資料一致性的使用方式進行分析。為保證高并發性能,以下分析場景不考慮執行的原子性及加鎖等強一致性要求的場景,僅追求最終一致性。 ......

    uj5u.com 2023-04-20 08:26:48 more
  • sql陳述句優化

    問題查找及措施 問題查找 需要找到具體的代碼,對其進行一對一優化,而非一直把關注點放在服務器和sql平臺 降低簡化每個事務中處理的問題,盡量不要讓一個事務拖太長的時間 例如檔案上傳時,應將檔案上傳這一步放在事務外面 微軟建議 4.啟動sql定時執行計劃 怎么啟動sqlserver代理服務-百度經驗 ......

    uj5u.com 2023-04-20 08:26:35 more
  • 云時代,MySQL到ClickHouse資料同步產品對比推薦

    ClickHouse 在執行分析查詢時的速度優勢很好的彌補了MySQL的不足,但是對于很多開發者和DBA來說,如何將MySQL穩定、高效、簡單的同步到 ClickHouse 卻很困難。本文對比了 NineData、MaterializeMySQL(ClickHouse自帶)、Bifrost 三款產品... ......

    uj5u.com 2023-04-20 08:26:29 more
  • sql陳述句優化

    問題查找及措施 問題查找 需要找到具體的代碼,對其進行一對一優化,而非一直把關注點放在服務器和sql平臺 降低簡化每個事務中處理的問題,盡量不要讓一個事務拖太長的時間 例如檔案上傳時,應將檔案上傳這一步放在事務外面 微軟建議 4.啟動sql定時執行計劃 怎么啟動sqlserver代理服務-百度經驗 ......

    uj5u.com 2023-04-20 08:25:13 more
  • Redis 報”OutOfDirectMemoryError“(堆外記憶體溢位)

    Redis 報錯“OutOfDirectMemoryError(堆外記憶體溢位) ”問題如下: 一、報錯資訊: 使用 Redis 的業務介面 ,產生 OutOfDirectMemoryError(堆外記憶體溢位),如圖: 格式化后的報錯資訊: { "timestamp": "2023-04-17 22: ......

    uj5u.com 2023-04-20 08:24:54 more
  • day02-2-商鋪查詢快取

    功能02-商鋪查詢快取 3.商鋪詳情快取查詢 3.1什么是快取? 快取就是資料交換的緩沖區(稱作Cache),是存盤資料的臨時地方,一般讀寫性能較高。 快取的作用: 降低后端負載 提高讀寫效率,降低回應時間 快取的成本: 資料一致性成本 代碼維護成本 運維成本 3.2需求說明 如下,當我們點擊商店詳 ......

    uj5u.com 2023-04-20 08:24:03 more
  • day02-短信登錄

    功能實作02 2.功能01-短信登錄 2.1基于Session實作登錄 2.1.1思路分析 2.1.2代碼實作 2.1.2.1發送短信驗證碼 發送短信驗證碼: 發送驗證碼的介面為:http://127.0.0.1:8080/api/user/code?phone=xxxxx<手機號> 請求方式:PO ......

    uj5u.com 2023-04-20 08:23:11 more