zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    Java中返回参数值的几种状态
    Java中的二维数组
    Java foreach操作(遍历)数组
    Arrays 类操作 Java 的数组排序
    Java循环语句 for
    Java循环语句 while
    Java条件语句 switch case
    Java多重if....else if
    margin优化的一种思路
    5.命名规则
  • 原文地址:https://www.cnblogs.com/zhoujinyi/p/3094978.html
Copyright © 2011-2022 走看看