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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM