关于mysql:参考MySQL-Internals手册使用Golang写一个简单解析binlog的程序

69次阅读

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

  • GreatSQL 社区原创内容未经受权不得随便应用,转载请分割小编并注明起源。

MySQL 作为最风行的开源关系型数据库,有大量的拥趸。其生态曾经相当欠缺,各项个性在圈内都有大量钻研。每次新个性公布,都会有业界大咖对其进行全面扫视、解读、钻研,本文要讲的 MySQL binlog 解析也有很多的前辈开发过优良的工具进行解析过(例如 canal),本文再提旧案未免有造轮子嫌疑。

然而我作为菜鸟,通过 MySQL Internals 手册来钻研一下 MySQL 的 binlog 的协定、event 类型、存储格局,并通过 MySQL Internals 手册的形容来窥探 MySQL 的数据存储格局,并且学习 Golang 语言个性,应该还有肯定的学习意义。

所以不揣冒昧,对解析 binlog 的过程编辑了以下,来梳理我对 binlog 只知其一; 不知其二,心愿不会贻笑大方之家。

波及到的工具版本信息:

IDE:    GoLand 2021.1.1 试用版
Golang: go1.12.10
DB:     mysql 8.0.23

MySQL Internals 手册:

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

Talk is cheap,show the code.

一、MySQL binlog

binlog 是对数据库执行的所有 DDL、DML 语句以事件(event)模式记录的的日志,事务平安,目标是用来进行复制和复原应用,是 MySQL 重要的个性之一。

在解析 binlog 之前数据库须要开启 binlog。

配置如下参数:

[mysqld]
log-bin=mysql-bin
server-id=1
binlog_format=ROW

~ 注:binlog_format 格局还有 statement 和 mixed 格局,篇幅所限,这里只探讨 RBR 状况。~

解析 binlog,MySQL 提供了 mysqlbinlog 工具来解析 binlog 文件。但这里我是通过程序模仿一个从库(slave)角色来解析流式的 binlog,所以须要创立账号,用来连贯主库和申请 binlog。其中:

  • 1、获取主库 binlog,必须有 REPLICATION SLAVE 权限。
  • 2、获取 binlog filename 和 position 必须有 REPLICATION CLIENT 或 SUPER 权限

倡议的受权:

CREATE USER mysqlsync@'%' IDENTIFIED BY 'mysqlsync';
GRANT REPLICATION SLAVE, REPLICATION CLIENT, SELECT ON *.* TO 'mysqlsync'@'%';

二、解析流程

解析形式时模仿从库(slave),MySQL 的从库在通过 ip、port、user、password 连贯主库后,向主库发送:

binlogPosition
binlogFlag
serverId

而后获取主库 binlog。

程序实现解析 binlog 首先是连贯主库注册 slave 身份。而后向主库发送 COM_BINLOG_DUMP 或 COM_BINLOG_DUMP_GTID 申请 binlog。

COM_BINLOG_DUMP 与 COM_BINLOG_DUMP_GTID 的区别在于 COM_BINLOG_DUMP_GTID 如果发送的通信包内不蕴含 binlog-filename,主库则从已知的第一个 binlog 发送 binlog-stream。

手册 14.9.6 介绍:

If the binlog-filename is empty, the server will send the binlog-stream of the first known binlog.

流程如下:

  • 1、连贯 Master 数据库
    输出数据库 IP:PORT,用户名明码连贯到 Master 数据库。
  • 2、设置相干参数
    master_binlog_checksum:MySQL 在 5.6 版本之后为 binlog 引入了 checksum 机制,从库须要与主库相干参数保持一致。
    server_id:
    解析程序相当于一个从库,须要向主库发送 set 注册
    @master_binlog_checksum= @@global.binlog_checksum.
  • 3、注册从节点
    告知客户端的 host、port、用户名与明码、serverId 发送 COM_BINLOG_DUMP 向 Master 申请 binlog stream.
  • 4、接管 binlog stream
    接管 binlog 后,依据 event type 解析获取数据输入。

三、解析 binlog

3.1 连贯主库

程序应用 tcp 协定来连贯主库,所以须要构建 mysql 协定的通信包来与主库进行三次握手。在通信包构建以及连贯、发送和读取通信包,调用了 kingshard 的源码,见:

https://github.com/flike/king…

其中,kingshard/mysql 蕴含 MySQL 的 client/server 协定的实现,kingshard/backend 蕴含了基于 tcp 连贯的三次握手获取与 MySQL 的连贯以及发送命令,获取并解析后果集等。

协定内容在手册中可参看:

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

程序中调用前先引入:

import (
    "github.com/flike/kingshard/mysql"
    "github.com/flike/kingshard/backend"
)

申明一个一般连贯函数,返回参数为:连接结构体(struct)以及三个字符串,对应连贯,地址,账号,明码。

func newConn(addr string, user string, pass string) *backend.Conn {c := new(backend.Conn)
 if err := c.Connect(addr, user, pass, ""); err != nil {fmt.Println("Connect failed ____________________")
 } else {fmt.Println("Connect success ____________________")
 }
 return c
}

有了下面的连贯函数,就能够在 main 函数中调用 newConn 函数来获取一个连贯,地址,账户,明码。

addr := "127.0.0.1:3306"
user := "mysqlsync"
pass := "mysqlsync"

c := newConn(addr, user, pass)

3.2 设置 checksum

MySQL 在 5.6 之后版本为 binlog 引入了 checksum 机制,例如 crc32,咱们的程序作为 mysql slave,须要与服务端相干参数保持一致,须要执行:set @master_binlog_checksum= @@global.binlog_checksum。

应用获取的连贯执行设置 checksum:

_, err := c.Execute("set @master_binlog_checksum= @@global.binlog_checksum")
if err != nil {fmt.Println(err)
}

3.3 获取 binlog 以后的 binlog_filename,binlog_pos

向 master 发送 show master status 语句查问以后的 binlog_filename,binlog_pos。调用的 ShowMasterStatus 是查问主库以后的 binlog_fileneme 和 binlog_pos。

show master status 后果集的第一行第一列为 inlog_file,第一行第二列为 binlog_pos,返回后果如下:

mysql> show master status;
+---------------+----------+--------------+------------------+--------------------------------------------+
| File          | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set                          |
+---------------+----------+--------------+------------------+--------------------------------------------+
| binlog.000007 |      192 |              |                  | bd3f8dcf-c9d2-11eb-9a2d-080027dcd936:1-278 |
+---------------+----------+--------------+------------------+--------------------------------------------+
1 row in set (0.00 sec)

在 main 函数中增加调用,来接管获取到的 binlog_filename,binlog_pos。代码如下:

binlogFileV, binlogPosV, err := c.ShowMasterStatus("show master status")
if err != nil {panic(err)
}

其中 ShowMasterStatus 函数代码为获取后果集的第一行的前两列并返回:

func (c *Conn) ShowMasterStatus(com string) (string, uint64, error) {
 var res *mysql.Result
 res, err := c.exec(com)
 if err != nil {panic(err)
 }

 var n = len(res.Resultset.Values)
 if n == 1 {fileStr, _ := res.Resultset.GetValue(0, 0)
  posStr, _ := res.Resultset.GetValue(0, 1)
  fileStrV, okf := fileStr.(string)

  if !okf {panic(err)
  }

  posStrV, okp := posStr.(uint64)
  if !okp {panic(err)
  }
  return fileStrV, posStrV, nil
 }
 return "", 0, fmt.Errorf("invalid resultset")
}

3.4 封装 COM_BINLOG_DUMP 通信 packet

取得了 Master 的 binlog_filename 和 binlog_pos,通过发送 COM_BINLOG_DUMP 到 Matser 申请 binlog。在 internal 手册中,COM_BINLOG_DUMP 解释:

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

1              [12] COM_BINLOG_DUMP
4              binlog-pos
2              flags
4              server-id
string[EOF]    binlog-filename

此时,须要咱们本人来封装 packet,在组成发送到 Master 通信的 packet 中,第 1 个字节地位存储 COM_BINLOG_DUMP 标识,第 2 到第 5 字节存储 binlog-pos。

第 6 到第 7 字节存储 slave 的 server-id,残余变长字节用来存储 binlog-filename。

在 main 函数中,创立字节数组,用获取到的 binlog-filename、binlog-pos,与 COM_BINLOG_DUMP、server-id 组成申请 binlog 通信 packet 向 Master 申请 binlog。

在 main 函数中减少如下代码:

binlogFile := []byte(binlogFileV)
var binlogFlag uint16 = 0
var serverId uint32 = 698148981

length := len(binlogFile)
data := make([]byte, 1+4+2+4+length)

data[0] = mysql.COM_BINLOG_DUMP

data[1] = byte(binlogPosV & 0xFFFFFFFF) // binlogPosV & 0xFFFFFFFF  uint64 --> uint32
data[2] = byte((binlogPosV & 0xFFFFFFFF) >> 8)
data[3] = byte((binlogPosV & 0xFFFFFFFF) >> 16)
data[4] = byte((binlogPosV & 0xFFFFFFFF) >> 24)

data[5] = byte(binlogFlag)
data[6] = byte(binlogFlag >> 8)

data[7] = byte(serverId)
data[8] = byte(serverId >> 8)
data[9] = byte(serverId >> 16)
data[10] = byte(serverId >> 24)

copy(data[1+4+2+4:], binlogFile)

至此,申请 binlog 的 packet 组装实现,将其发送给 Master,Master 就会继续一直向本程序通信应用的 IP 和端口发送 binlog network stream,除非遇到命令进行或者网络问题中断。

在 main 函数中减少调用 BinlogDump 函数申请 binlog stream 的代码:

c.BinlogDump(data, addr, user, pass)

上面就是 main 函数调用的申请 binlog 的函数(此为局部,后续该函数中还将减少解析 binlog 代码):

func (c *Conn) BinlogDump(arg []byte, addr string, user string, pass string) ([]byte, error) {if err := c.writeCommandBuf(arg[0], arg[1:]); err != nil {fmt.Println("WriteCommandBuf failed")
 } else {fmt.Println("WriteCommandBuf success")
 }
}

其中 writeCommandBuf 函数是依据 tcp 通信协议封装 packet:

func (c *Conn) writeCommandBuf(command byte, arg []byte) error {
 c.pkg.Sequence = 0
 length := len(arg) + 1
 data := make([]byte, length+4)
 data[4] = command
 copy(data[5:], arg)
 return c.writePacket(data)
}

3.5 解析 event

MySQL Binlog 的 event packet 分为 event header 和 event data 两局部。在不同的 Binlog Version 中 event header 长度不同。

在 MySQL 5.0.0+ 版本中,应用的是 Version 4,其 event header 占用字节长度是 19。event data 则依据 event 类型不同,占用字节长度也不尽相同,后文会对局部 event 进行解析。

binlog header 的组成在 internal 手册中解释:

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

4              timestamp
1              event type
4              server-id
4              event-size
   if binlog-version > 1:
4              log pos
2              flags

在 binlog 的 event header 之前,应用一个字节位来存储 packet 标识,蕴含 OK_Packet,EOF_Packet。EOF_Packet 用来标识一个查问操作的完结,在 MySQL 5.7.5 当前的版本曾经过期。手册中对此解释:

These rules distinguish whether the packet represents OK or EOF:

OK: header = 0 and length of packet > 7

EOF: header = 0xfe and length of packet < 9

具体请参考:https://dev.mysql.com/doc/int…

依据手册介绍,咱们对 event stream 进行接管、解析。对上文中的 BinlogDump 函数进行扩大,golang 没有提供 while 循环,这里应用 for 循环继续接管 binlog event。

减少的解析 event header 的代码:

func (c *Conn) BinlogDump(arg []byte, addr string, user string, pass string) ([]byte, error) {if err := c.writeCommandBuf(arg[0], arg[1:]); err != nil {fmt.Println("WriteCommandBuf failed")
 } else {fmt.Println("WriteCommandBuf success")
 }

 for {data, err := c.readPacket()
  if err != nil {return nil, err}

  if data[0] == mysql.EOF_HEADER && len(data) < 9 {fmt.Println("ReadEOF Success" + "Length:" + strconv.Itoa(len(data)))
   return nil, nil
  }

  if data[0] == mysql.OK_HEADER {if _, err := c.handleOKPacket(data); err != nil {fmt.Println("ReadOk failed" + "length:" + strconv.Itoa(len(data)))
    return nil, err
   } else {timeStamp := data[1:5]
    eventType := data[5]
    serverId := data[6:10]
    eventSize := data[10:14]
    logPos := data[14:18]
    flags := data[18:20]
   }
  }
 }
}

这样,就实现了对 event header 的解析,这里取得的重要信息就是 event type,这将决定后续对 event 的解析形式。

依据 eventType,就能够 event data 进行解析了。

MySQL binlog 中,共有三十几种 event type,在 MySQL 8.0.23 中,在开启 GTID 时,执行一个事务,在 binlog 中会写入以下几种 event:

  • GTID_LOG_EVENT:蕴含 last_committed,sequence_number 等组提交信息的 event。
  • QUERY_EVENT:在 binlog_format=statement 时,执行的语句即存储在 QUERY_EVENT 中,例如 BEGIN、START TRANSACTION 等。
  • ROWS_QUERY_LOG_EVENT:记录原始 SQL 语句,并蕴含语句对应的行变更具体内容
  • TABLE_MAP_EVENT:蕴含事务波及的表的 ID 和内部结构定义信息
  • WRITE_ROWS_EVENT_V2:MySQL 5.6.x 当前版本的 ROWS_EVENT
  • UPDATE_ROWS_EVENT_V2:MySQL 5.6.x 当前版本的 ROWS_EVENT
  • DELETE_ROWS_EVENT_V2:MySQL 5.6.x 当前版本的 ROWS_EVENT
  • XID_EVENT:用来标识 xa 事务的,在两阶段提交中,当执行 commit 时,会在 binlog 中记录 XID_EVENT

3.5.1 筛选 event(对事务波及的 event 进行解析)

在本文中,着重对:

  • QUERY_EVENT
  • TABLE_MAP_EVENT
  • WRITE_ROWS_EVENT_V2
  • UPDATE_ROWS_EVENT_V2
  • DELETE_ROWS_EVENT_V2

几个类型的 event 进行解析,也是事务波及到的几种 event type。

在 BinlogDump 函数中的 OK_HEADER 判断中减少 golang 的 switch 代码,来判断过滤 event type,便于前面的解析。switch 示例如下:

switch eventType {
  case mysql.XID_EVENT:
    fmt.Printf("EVENT TYPE: %v\n", "XID_EVENT")
  default:
    fmt.Printf("EVENT TYPE: %v\n", "This event will ignored!")
}

在每一个蕴含 event 的 packet 中,第 1 个字节为 OK_Packet 标识(值为 0),第 2 -19 字节为 event header,而第 20 当前的字节则为 event data 局部。

在程序中减少两个全局变量 schema,table:

var schema string // global para
var table string  // global para

3.5.2 解析 QUERY_EVENT

首先对 QUERY_EVENT 进行解析。在 QUERY_EVENT 中存储 DDL 及 begin 等语句,局部信息略过没有解析。代码解析如下:case mysql.QUERY_EVENT:
  fmt.Printf("EVENT TYPE: %v\n", "QUERY_EVENT")
  pos := 20
  // threadId := data[20:24]   // 4 bytes.
  // createTime := data[24:28] // 4 bytes.
  pos += 8
  schemaLen := data[pos : pos+1] // 1 byte.
  // errorCode := data[29:31]  // 2 bytes.
  pos += 3
  staVarLen := data[pos : pos+2] // 2 bytes (not present in v1, v3).
  pos += 2
  //statusVar := data[33 : 33+Byte2Int(staVarLen)]                                    
  database := data[pos+utils.Byte2Int(staVarLen) : pos+utils.Byte2Int(staVarLen)+utils.Byte2Int(schemaLen)]
  query := data[pos+utils.Byte2Int(staVarLen)+utils.Byte2Int(schemaLen)+1:]                                 
  schema = string(database)
  sql = string(query)

  fmt.Printf("SCHEMA: %v\n", schema)
  fmt.Printf("SQL: %v\n", sql)

解析 QUERY_EVENT,次要获取波及到的 DDL 等语句。

3.5.3 解析 TABLE_MAP_EVENT

对 TABLE_MAP_EVENT 进行解析,获取操作的 schema、table。

MySQL 在 binlog 中的 TABLE_MAP_EVENT 记录了操作波及的 schema 和 table 名,但未记录表的 column 信息。

在失常的主从同步中,这些表的 column 信息是能够从 slave 的数据字典中查问到的。然而程序模仿的 slave 则须要通过 schema 和 table 名查问 Master 来获取这些信息。

当然为了防止每次解析都要查问 Master,也能够对其进行缓存。为了缩小程序复杂度,本文没有连贯 Master 获取 column 信息。

TABLE_MAP_EVENT 的 Payload 信息在手册中形容:

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

post-header:
    if post_header_len == 6 {4              table id} else {6              table id}
  2              flags

payload:
  1              schema name length
  string         schema name
  1              [00]
  1              table name length
  string         table name
  1              [00]
  lenenc-int     column-count
  string.var_len [length=$column-count] column-def
  lenenc-str     column-meta-def
  n              NULL-bitmask, length: (column-count + 8) / 7

依据形容咱们解析第 28 个字节获取 schema 名称长度(schema name length),在而后依据长度解析出 schema 名称。

依据手册再顺延程序向后,用雷同的形式解析出 table 名称,并赋值给全局变量。

代码参考如下:

case mysql.TABLE_MAP_EVENT:
  fmt.Printf("EVENT TYPE: %v\n", "TABLE_MAP_EVENT")
  pos := 20
  // tableId := data[20:26]
  pos += 6
  pos += 2
  schemaNameLen := data[pos : pos+1]
  pos += 1 // 1 byte  schema name length
  schema = string(data[pos : pos+utils.Byte2Int(schemaNameLen)])
  pos += utils.Byte2Int(schemaNameLen)
  pos += 1 // 0x00 skip 1 byte
  tableNameLen := data[pos : pos+1]
  pos += 1 // 1 byte  table name length
  table = string(data[pos : pos+utils.Byte2Int(tableNameLen)])
  pos += utils.Byte2Int(tableNameLen) // table name
  pos += 1                            // 0x00 skip 1 byte

  columnCount := utils.Byte2Int(data[pos : pos+1])
  pos += 1 // column-count

  colType := data[pos : pos+columnCount]

  pos += columnCount

  // var n int
  var err error
  var metaData []byte
  if metaData, _, _, err = GetColumnMetaArray(data[pos:]); err != nil {return err}

  colMeta, err := GetMeta(metaData, columnCount, colType)
  if err != nil {return err}

  ColumnType = colType
  ColumnMeta = colMeta
  schema = string(schema)
  table = string(table)

  fmt.Printf("SCHEMA: %v\n", string(schema))
  fmt.Printf("TABLE: %v\n", string(table))
  fmt.Printf("columnCount: %v\n", columnCount)
  fmt.Println("-----------------")
  for i := 0; i < len(colType); i++ {fmt.Printf("ColumnType: %v\n", colType[i])
  }
  fmt.Println("-----------------")
  fmt.Println("&&&&&&&&&&&&&&&&&")
  for i := 0; i < len(colMeta); i++ {fmt.Printf("ColumnMeta: %v\n", colMeta[i])
  }
  fmt.Println("&&&&&&&&&&&&&&&&&")
  fmt.Printf("TABLE: %v\n", string(table))

对 TABLE_MAP_EVENT 解析次要是获取事务波及的 schema 和 table 信息。

3.5.4 解析 ROWS_EVENT

对 ROWS_EVENT 的三个 event(

WRITE_ROWS_EVENT_V2 /

UPDATE_ROWS_EVENT_V2 /

DELETE_ROWS_EVENT_V2)进行解析。

手册对 event 形容如下:

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

header:
  if post_header_len == 6 {4                    table id} else {6                    table id}
2                    flags
  if version == 2 {
2                    extra-data-length
string.var_len       extra-data
  }

body:
lenenc_int           number of columns
string.var_len       columns-present-bitmap1, length: (num of columns+7)/8
  if UPDATE_ROWS_EVENTv1 or v2 {string.var_len       columns-present-bitmap2, length: (num of columns+7)/8
  }

rows:
string.var_len       nul-bitmap, length (bits set in 'columns-present-bitmap1'+7)/8
string.var_len       value of each field as defined in table-map
  if UPDATE_ROWS_EVENTv1 or v2 {string.var_len       nul-bitmap, length (bits set in 'columns-present-bitmap2'+7)/8
string.var_len       value of each field as defined in table-map
  }
  ... repeat rows until event-end    

ROWS_EVENT 的三种 event,其 packet 的格局是雷同的。第 20 到第 26 字节存储的 table id,后续 4 字节与本文解析日志内容关联不大,跳过。

第 30 到 31 字节存储表中列的数量。其后存储的是 SQL 语句用到的列,是应用 bitmap 来存储的。

在 UPDATE_ROWS_EVENT_V2 中,存储了更新前后的列的数据,所以应用两个 bitmap。bitmap 长度依照手册形容为:

length: (num of columns+7)/8

列的数量和列是否用到 bitmap 组成了 event 的 body 局部,前面就是 event 真正存储的数据局部 event data 了,如果是多行,则循环排列,直至 event 完结。

注:这里代码中没有思考列应用 unsigned 状况,在理论利用场景中这须要思考。

在 main 函数的对 event type 的 switch 判断中,减少如下 case:

case mysql.WRITE_ROWS_EVENT_V2, mysql.UPDATE_ROWS_EVENT_V2, mysql.DELETE_ROWS_EVENT_V2:
     switch eventType {
     case mysql.WRITE_ROWS_EVENT_V2:
      fmt.Printf("EVENT TYPE: %v\n", "WRITE_ROWS_EVENT_V2")
     case mysql.UPDATE_ROWS_EVENT_V2:
      fmt.Printf("EVENT TYPE: %v\n", "UPDATE_ROWS_EVENT_V2")
     case mysql.DELETE_ROWS_EVENT_V2:
      fmt.Printf("EVENT TYPE: %v\n", "DELETE_ROWS_EVENT_V2")
     }
     pos := 20
     // tableId := data[pos : pos+6] // 6 bytes
     pos += 6
     // flags := data[pos:pos+2]    // 2 byte
     pos += 2
     extraDataLen := data[pos : pos+2] // 2 bytes
     pos += utils.Byte2Int(extraDataLen)

     columnLen := data[pos : pos+1] // 1 byte
     fmt.Printf("columnLen: %v\n", columnLen)
     pos += 1
     colPreBitmap1 := data[pos : pos+(utils.Byte2Int(columnLen)+7)/8] // columns-present-bitmap1, length: (num of columns+7)/8
     pos += (utils.Byte2Int(columnLen) + 7) / 8

     var colPreBitmap2 []byte
     if eventType == mysql.UPDATE_ROWS_EVENT_V2 {colPreBitmap2 = data[pos : pos+(utils.Byte2Int(columnLen)+7)/8] // columns-present-bitmap2, length: (num of columns+7)/8
      pos += (utils.Byte2Int(columnLen) + 7) / 8
     }

     var rows1 [][]interface{}
     var rows2 [][]interface{}
     for pos < len(data) {
      // repeat rows until event-end
      rows1Re, n1, err := GetRows(rows1, data[pos:], utils.Byte2Int(columnLen), ColumnType, colPreBitmap1, ColumnMeta)
      if err != nil {return err}
      pos += n1
      for i := 0; i < len(rows1Re); i++ {fmt.Printf("ColumnMeta: %v\n", rows1Re[i])
      }

      if len(colPreBitmap2) > 0 {rows2Re, n2, err := GetRows(rows2, data[pos:], utils.Byte2Int(columnLen), ColumnType, colPreBitmap2, ColumnMeta)
       if err != nil {return err}
       pos += n2
       for i := 0; i < len(rows2Re); i++ {fmt.Printf("ColumnMeta: %v\n", rows2Re[i])
       }
      }
     }

代码中,for pos < len(data)局部就是在解析 event body 后对真正存储的行数据进行循环解析。

解析是调用 GetRows()函数进行的,在 GetRows 办法中,对不同数据类型,因占用不同字节长度,以及局部类型应用了压缩算法,采纳了不同的解码形式。

解码这部分代码繁冗,干燥,本文篇幅所限就不介绍了,有趣味可参考手册以及其余开源工具,例如阿里开源的 canal 等

这里对解析后果间接进行了控制台输入,在理论利用中能够格式化为 json 或其余格局输入,以利于浏览或者利用。

3.6 测试程序

在数据库中执行简略 sql:

mysql> use wl
Database changed
mysql> update name set first='202106171325' where id = 48;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

控制台输入:

TABLE: name
EVENT TYPE: UPDATE_ROWS_EVENT_V2
columnLen: [3]
ColumnMeta: [48 20210617 <nil>]
ColumnMeta: [48 202106171325 <nil>]
EVENT TYPE: XID_EVENT

能够看到 UPDATE_ROWS_EVENT_V2 记录了更改前后的数据值,列值为空时,记录为 <nil>

至此,对 binlog 的简略解析就完结了。

后记

代码中,对字符串类型转换,通信 packet 的封装,字节解码别离参考了:

https://github.com/siddontang…

https://github.com/flike/king…

https://github.com/alibaba/canal

感激开源社区!

Enjoy GreatSQL :)

文章举荐:

GreatSQL MGR FAQ
https://mp.weixin.qq.com/s/J6…

万答 #12,MGR 整个集群挂掉后,如何能力主动选主,不必手动干涉
https://mp.weixin.qq.com/s/07…

『2021 数据技术嘉年华·ON LINE』:《MySQL 高可用架构演进及实际》
https://mp.weixin.qq.com/s/u7…

一条 sql 语句慢在哪之抓包剖析
https://mp.weixin.qq.com/s/AY…

万答 #15,都有哪些状况可能导致 MGR 服务无奈启动
https://mp.weixin.qq.com/s/in…

技术分享 | 为什么 MGR 一致性模式不举荐 AFTER
https://mp.weixin.qq.com/s/rN…

对于 GreatSQL

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

Gitee:
https://gitee.com/GreatSQL/Gr…

GitHub:
https://github.com/GreatSQL/G…

Bilibili:
https://space.bilibili.com/13…

微信 &QQ 群:
可搜寻增加 GreatSQL 社区助手微信好友,发送验证信息“加群”退出 GreatSQL/MGR 交换微信群

QQ 群:533341697
微信小助手:wanlidbc

本文由博客一文多发平台 OpenWrite 公布!

正文完
 0