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

  • 相关阅读:
    基于Metaweblog API 接口一键发布到国内外主流博客平台
    uva144 Student Grants
    Uva 10452
    Uva 439 Knight Moves
    Uva 352 The Seasonal War
    switch语句
    java——基础知识
    我的lua学习2
    codeforces 431 D. Random Task 组合数学
    codeforces 285 D. Permutation Sum 状压 dfs打表
  • 原文地址:https://www.cnblogs.com/zhoujinyi/p/3094978.html
Copyright © 2011-2022 走看看