mysql binlog的row模式数据解析【转】

本文来自:mysql binlog的row模式数据解析

drc-mysql是一种支持多master 多slave的快速并行复制的解决方案,基于mysql的binlog,目前支持binlog的STATEMENT模式。为了实现drc-mysql对 ROW模式的支持,本文对此展开研究,分析了binlog的事件格式,并针对不同的数据类型进行解析。

本文的目的是为了展示如何从row模式事件中解析数据,因此事件中一些记录其他信息的字节会直接略过,感兴趣的同学可以看看log_event.h以及log_event.cc两个文件。

 

获取Binlog事件:

Mysql对Binlog的处理是以事件为单位的,每一次DML操作可能会产生多次事件,例如对于innodb存储引擎,会额外产生一条QUERY_EVENT(事务的begin语句)以及XID_EVENT(事务提交)。

通过调用libmysql.so库中的cli_safe_read()函数可以获取一次binlog事件:

cli_safe_read(mm);   // mm类型为MYSQL*

net =  &mm->net;

buf = (const char*) net->read_pos + 1;

Binlog的事件类型大约有27种,这里只介绍与ROW模式相关的事件

1)       QUERY_EVENT:与STATEMENT模式处理相同,存储的是SQL,主要是一些与数据无关的操作,eg: begin、drop table;

2)       TABLE_MAP_EVENT:记录了下一条事件所对应的表信息,在其中存储了数据库名和表名;

3)       WRITE_ROWS_EVENT:操作类型为insert;

4)       UPDATE_ROWS_EVENT:操作类型为update;

5)       DELETE_ROWS_EVENT:操作类型为delete;

6)       XID_EVENT, 用于标识事务提交。

在buf[EVENT_TYPE_OFFSET]中记录了事件的类型 (EVENT_TYPE_OFFSET = 4),根据其中记录的整数,对比log_event.h中的Log_event_type,可以找到相应的事件类型。

以一条insert语句为例,包含4个事件:

TABLE_MAP_EVENT

QUERY_EVENT  (begin)

WRITE_ROWS_EVENT

XID_EVENT

 

事件时间戳:

buf[0] ~ buf[3]的四个字节,存储了执行操作前的时间戳。

 

事件长度:

Buf[9]开始的四个字节构成的整数,可以使用如下的方式来进行整数转换:

#define UCHAR(ptr) ((*(ptr)+256)%256)

const char *ptr = buf + 9;

unsigned int data_len = UCHAR(ptr) + (UCHAR(ptr+1)<<8) + (UCHAR(ptr+2)<<16)  + (UCHAR(ptr+3)<<24);

 

获取数据库和表名:

由于在insert/delete/update事件中不记录表的相关信息,因此每次DML操作都会产生一个TABLE_MAP_EVENT事件,其中存储了获取数据库名和表名。

例如对于数据库名:tt0001;表名:x18,从buf[27]开始表示为如下格式:

27 28 29 30 31 32 33 34 35 36 37 38 39
6 t t 0 0 0 1 \0 3 x 1 8 \0

 

解析数据:

在buf中记录了很多信息,但我们的目的是为了解析出数据,因此可以跳过一些字节,直接到达我们的目标数据头部。

cols = buf[27]; //在insert/delete/update事件中,buf[27]表示列的个数

bits = (cols+7)/8

对于WRITE_ROWS_EVENT、DELETE_ROWS_EVENT: ptr  =  buf +28+bits

对于UPDATE_ROWS_EVENT:ptr  =  buf +28+bits * 2

从ptr开始,记录了我们需要解析的数据。

1)       UPDATE_ROWS_EVENT

Old record New record Old record New record Old record ……

每更新了多少行,就有多少对 old/new record,当一个事件包存储不下所有记录时,将会拆分成多个 UPDATE_ROWS_EVENT事件。

2)  WRITE_ROWS_EVENT

包含一条插入的数据record

3)  DELETE_ROWS_EVENT

包含被删除的数据record,格式为:

record record record ……

从上面的分析可以看出,要想从binlog中解析出数据,除了辅助信息外,关键是要从record中获取得到行数据,因为DML操作对应的事件类型,都以record为记录单位。

 

Record结构:

在Record的前几位,会用多个字节来表示值为NULL的列,record的结构可表示为

bit_map Col1 Col2 Col3 ……

其中bit_map的所占字节数为(cols+7)/8

例如,执行:insert into xx values(1, NULL, NULL, 15, “ssss”);

xx表有5列,需要(cols +7)/8 = 1 个字节就可以表示所有的列

bit_map = 230,230转换为二进制:1110 0110

其中,最低位表示第一列,第cols( = 5)位为最后一列,为1表示该列值为NULL,为0表示非NULL,在随后的数据记录中只会记录非NULL的值,例如这里跳过bit_map所占字节之后,只会记录1、15和‘ssss’

再比如,当执行如下语句时:insert into x14(a,b) values (NULL,”dsda”);

这里有9列,因此需要2个字节记:

ptr[0] = -4; ptr[1] = -1

转换为二进制  (ptr[1])1111 1111  (ptr[0])1111 1100

注意,这里虽然在SQL语句中a值为NULL,但由于a列是自增类型,因此存储在binlog中的就是一个整数,而非NULL值.

对于值为NULL的列,我们可以通过表的定义得到该列的默认值。

 

解析不同的数据类型:

在record中bit_map之后的列数据中,针对不同的数据类型,可能在record中占用不同的字节,因此需要针对每种数据类型进行处理,为 了获取到每一列的信息,我们可以调用MYSQL的接口函数mysql_fetch_field()。这里需要注意一种特殊情形,即对于set和enum类 型,在调用该API时,会被转换为MYSQL_TYPE_STRING类型,可以调用show columns from 来得到这两种类型的定义。

这里列出了大部分常用数据类型的字节数和解析方法:

1. MYSQL_TYPE_LONG

Int类型,占用4个字节,sint4korr(ptr)

2.  MYSQL_TYPE_TINY

Tinyint类型 ,占用1个字节

3. MYSQL_TYPE_SHORT

smallint 类型, 2个字节, sint2korr(ptr)

4. MYSQL_TYPE_INT24

MEDIUMINT类型,3个字节, sint3korr(ptr)

5. MYSQL_TYPE_LONGLONG

Bigint 类型,8个字节, sint8korr(ptr)

6. MYSQL_TYPE_NEWDECIMAL

Decimal类型,精度限制为65, 字节数与该类型的定义相关,可以参考用户手册 ,对该类型的解析主要是计算出其占用的字节数,调用libmysql.so库中的bin2decimal函数来实现解析。

7. MYSQL_TYPE_FLOAT、MYSQL_TYPE_DOUBLE

直接进行类型的强制转换,分别占4和8个字节,然后根据定义对输出进行精度控制。

8. MYSQL_TYPE_BIT

Bit类型,占用的字节数与其定义相关,计算方式:

byt_len = length%8==0? length/8 : (length/8 + 1);

例如,当定义为bit(M)时,length = M;将byt_len个字节中存储的数据转换为一个整数。

9.  MYSQL_TYPE_SET

SET类型,定义为SET(M),M值为以下范围时:

1 ~8,1个字节

9~16,  2个字节

17~24, 3个字节

25~32, 4个字节

33~64, 8个字节

然后将相应字节内的数转换为整数即可

10.  MYSQL_TYPE_ENUM

Enum类型,当该类型内的元素超过255个时,使用2个字节,否则使用1个字节表示,相应字节内转换为整数M,表示在enum中的第M个元素。

11.  MYSQL_TYPE_STRING、MYSQL_TYPE_VAR_STRING、MYSQL_TYPE_BLOB

包括char()、varchar()以及text类型,其处理方式相同,在record中首先根据其定义的长度,例如:

对于varchar(10),使用一个字节记录长度;而对于varchar(300),则需要使用两个字节来记录字符串的长度;

字符串“abcdef”,在record中被记录为“6abcdef”。

12. MYSQL_TYPE_TIME

Time类型,3个字节,计算方法:

d_int = UCHAR(ptr) + (UCHAR(ptr+1)<<8) + (UCHAR(ptr+2)<<16);

例如对于‘12:01:22’,计算结果为120122

13. MYSQL_TYPE_TIMESTAMP

timestamp时间戳类型,4个字节,直接进行类型的强制转换为整数

14. MYSQL_TYPE_DATE

Date类型,3个字节,计算方法:

d_int = UCHAR(ptr) + (UCHAR(ptr+1)<<8) + (UCHAR(ptr+2)<<16)

例如:

00001111     10110111    00100001

其中,1-5位表示日期,6-9位表示月份,剩余的表示年份,因此上述date类型可转换为2011-09-01

15. MYSQL_TYPE_YEAR

Year类型,1个字节,记录年份,用一个字节记录,从1900年开始

例如,当值为112时,表示112+1900 = 2012年

16. MYSQL_TYPE_DATETIME

Datetime类型,8个字节,直接类型转换为long long,

例如对于 2011-08-27 19:32:46

计算结果值为20110827193246

其实,不管是什么数据类型,我们只要知道其占有的字节数,就能推敲出他们在文件中存储的格式。

 

未来可能的应用

1. 通过解析binlog中的行数据,进行增量数据dump;

2. 结合handlersocket进行replication。

http://www.taobaodba.com/html/585_mysql-binlog%E7%9A%84row%E6%A8%A1%E5%BC%8F%E6%95%B0%E6%8D%AE%E8%A7%A3%E6%9E%90.html

posted @ 2013-05-23 15:34  jyzhou  阅读(6089)  评论(0编辑  收藏  举报