MySql Binlog事件數據篇

系列文章html

MySql Binlog初識java

MySql Binlog事件介紹篇mysql

MySql Binlog事件數據篇sql

Mysql通信協議分析數據庫

基於Netty模擬解析Binlog數組

前言
前兩篇文章MySql Binlog初識MySql Binlog事件介紹篇分別從Binlog入門和Binlog事件如何產生的兩個角度來介紹Binlog,本文將從Binlog事件的數據來更深刻的瞭解Binlog。服務器

Binlog事件數據
1.QUERY_EVENT
執行更新語句時會生成此事件,包括:create,insert,update,delete;
Fixed data part,總長度13字節:
4字節:執行sql的線程id;
4字節:執行sql的時間;
1字節:數據庫名稱的長度;
2字節:執行sql產生的錯誤碼;
2字節:狀態變量的長度,具體內容在Variable part;dom

Variable part:
可變字節:狀態變量,每一個狀態變量key爲一個字節,後面跟着value,不一樣的key對應不一樣長度的value,可是總長度在Fixed data part中已經定義;
可變字節:數據庫名稱
可變字節:sql語句,經過事件的總長度-header長度-Fixed data-狀態變量,剩餘的字節數組經過utf-8編碼便可獲取;ide

2.STOP_EVENT
當mysqld中止時生成此事件;
Fixed data part:空的函數

Variable part:空的

3.ROTATE_EVENT
當mysqld切換到新的binlog文件生成此事件;
Fixed data part,總長度8字節:
8字節:下一個binlog文件的第一個事件的position,這個值一直是4,由於魔數佔用了4字節;

Variable data part:
可變字節:下一個binlog的名稱,它的長度=事件總長度-header長度-Fixed data

4.INTVAR_EVENT
當sql語句中使用了AUTO_INCREMENT的字段或者LAST_INSERT_ID()函數;
Fixed data part:空的

Variable part:
1字節:一個變量類型的值:LAST_INSERT_ID_EVENT = 1 或者 INSERT_ID_EVENT = 2;
8字節:LAST_INSERT_ID()函數調用,或者AUTO_INCREMENT字段生成的一個無符號的整型;

5.RAND_EVENT

| bin-log.000003 |  438 | RAND               |         1 |         473 | rand_seed1=223769196,rand_seed2=1013907192

執行包含RAND()函數的語句產生此事件,此事件沒有被用在binlog_format爲ROW模式的狀況下;
Fixed data part:空的

Variable part:
8字節:第一個種子值(ex:rand_seed1=223769196)
8字節:第二個種子值(ex:rand_seed2=1013907192)

6.USER_VAR_EVENT

| bin-log.000003 |  711 | User var           |         1 |         756 | @`age`=50

執行包含了用戶變量的語句產生此事件,此事件沒有被用在binlog_format爲ROW模式的狀況下;
Fixed data part:空的

Variable part:
4字節:用戶變量名的大小;
可變字節:用戶變量名,具體長度上一個4字節的數據指定了;
1字節:若是是變量值是NULL,那麼此值是非0的;若是是此值是0,那麼纔有接下來的其餘數據;應該是對有空值狀況的一種優化;
1字節:用戶變量類型,包括:(STRING_RESULT=0, REAL_RESULT=1, INT_RESULT=2, ROW_RESULT=3, DECIMAL_RESULT=4);
4字節:用戶變量字符的數量;
4字節:用戶變量值的長度;
可變字節:變量的值,經過變量類型和變量值的長度,能夠解析出具體的變量值;

7.FORMAT_DESCRIPTION_EVENT

| bin-log.000003 |    4 | Format_desc        |         1 |         107 | Server ver: 5.5.29-log, Binlog ver: 4

描述事件,被寫在每一個binlog文件的開始位置,用在MySQL5.0之後的版本中,代替了START_EVENT_V3;
Fixed data part:
2字節:binlog版本,Mysql5.0以及以上的版本值爲:4
50字節:Mysql Server版本;
4字節:事件建立的時間戳;
1字節:header的長度,binlog版本爲4的狀況下header長度是19;
可變字節:從START_EVENT_V3開始到第27個Event,每一個Event的fixed part lengths,每一個事件一個字節,總共27個字節;

Variable part:空的

8.XID_EVENT

| bin-log.000003 |  315 | Xid                |         1 |         342 | COMMIT /* xid=32 */

事務提交產生的XID_EVENT事件;
Fixed data part:空的

Variable part:
8字節:事務編號;

9.BEGIN_LOAD_QUERY_EVENT

| bin-log.000003 |  964 | Begin_load_query   |         1 |        1008 | ;file_id=3;block_len=21

執行LOAD DATA INFILE 語句時產生此事件
Fixed data part:
4字節:加載Data File的ID,防止加載的Data File內容是相同的;

Variable part:
加載數據的第一個塊,若是文件大小超過某個閥值,後面會有多個APPEND_BLOCK_EVENT事件,每個包含一個數據塊;可變字節長度 = 事件的總長度 – header長度 – Fixed data;由於測試數據量比較少(999, 101, ‘zhaohui’)總共就21個字節,因此一個塊足夠了;

10.EXECUTE_LOAD_QUERY_EVENT

| bin-log.000003 | 1008 | Execute_load_query |         1 |        1237 | use `test`; LOAD DATA INFILE 'D:/btest.sql' INTO TABLE `btest` FIELDS TERMINATED BY ',' ENCLOSED BY '' ESCAPED BY '\\' LINES TERMINATED BY '\n' (`id`, `age`, `name`) ;file_id=3 |

執行LOAD DATA INFILE產生的事件,相似QUERY_EVENT事件,Fixed data的前13個字節和QUERY_EVENT相似;
Fixed data part:
4字節:執行sql的線程id;
4字節:執行sql的時間;
1字節:數據庫名稱的長度;
2字節:執行sql產生的錯誤碼;
2字節:狀態變量的長度,具體內容在Variable part;
4字節:加載Data File的ID;
4字節:文件名替換語句中的起始位置;
4字節:文件名替換語句中的結束位置;
1字節:如何處理重複數據,三個選項:LOAD_DUP_ERROR = 0, LOAD_DUP_IGNORE = 1, LOAD_DUP_REPLACE = 2

Variable part:
1.狀態變量,每一個狀態變量key爲一個字節,後面跟着value,不一樣的key對應不一樣長度的value,可是總長度在Fixed data part中已經定義;
2.sql語句,經過事件的總長度-header長度-Fixed data-狀態變量,剩餘的字節數組經過utf-8編碼便可獲取;

11.TABLE_MAP_EVENT

| bin-log.000004 |  844 | Table_map   |         1 |         892 | table_id: 33 (test.btest)

將表的定義映射到一個數字,在行操做事件以前記錄(包括:WRITE_ROWS_EVENT,UPDATE_ROWS_EVENT,DELETE_ROWS_EVENT);
Fixed data part:
6字節:表Id;
2字節:保留字段爲未來使用;

Variable part:
1字節:數據庫名字的長度;
可變字節:數據庫名字,根據前一個字節記錄的名字長度,獲取的字節數組經過utf-8編碼便可獲取;
1字節:表名的長度;
可變字節:表名,根據前一個字節記錄的名字長度,獲取的字節數組經過utf-8編碼便可獲取;
Packed integer:用來記錄表中字段的數量;
注:Packed integer是一個可變字節的類型,根據數據大小字節大小不同,
更多詳細:https://dev.mysql.com/doc/internals/en/event-content-writing-conventions.html
可變字節:表字段類型數組,每一個字段一個字節;
Packed integer:用來記錄表元數據的長度;
可變字節:元數據塊,根據前一個字節記錄的名字長度,獲取的字節數組經過utf-8編碼便可獲取;
可變字節:用位域表示每個字段是否爲null,一個字節有8位,因此N個字段須要(N+7)/8個字節;

12.WRITE_ROWS_EVENT,UPDATE_ROWS_EVENT和DELETE_ROWS_EVENT
binlog_format爲ROW模式下,執行insert,update和delete操做產生的事件;
Fixed data part:
6字節:表Id;
2字節:保留字段爲未來使用;

Variable part:
Packed integer:記錄表中字段的數量;
可變字節:用位域表示每一個字段是否被使用(好比只有更新、插入的字段纔是被使用的),N個字段須要(N+7)/8個字節;
可變字節:僅用在UPDATE_ROWS_EVENT事件中,用位域表示每一個字段更新以後是否被使用(值只有真正被更新了纔是被使用的),N個字段須要(N+7)/8個字節;
接下來是記錄的每一行的數據:
可變字節:當前行中的字段值是否爲NULL,只有這個字段被標識爲被使用,纔會出如今這;
可變字節:當前行全部字段的值,只有這個字段被標識爲被使用,而且值不爲NULL纔會有值;

13.INCIDENT_EVENT
主服務器發生了不正常的事件,通知從服務器並告知可能會致使數據處於不一致的狀態;
Fixed data part:
1字節:不正常事件的編號;
1字節:消息的長度;

Variable part:
消息的內容,根據Fixed data part中指定的消息長度讀取消息的內容;

14.HEARTBEAT_LOG_EVENT
主服務器告訴從服務器,主服務器還活着,不寫入到日誌文件中;
Fixed data part:空的

Variable part:空的

更多參考:https://dev.mysql.com/doc/internals/en/event-data-for-specific-event-types.html

Java讀取簡單實例
1.建立表,並插入數據,產生binlog日誌文件;

2.查看binlog中的事件;

mysql> show binlog events in 'bin-log.000001';
+----------------+-----+-------------+-----------+-------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Log_name       | Pos | Event_type  | Server_id | End_log_pos | Info                                                                                                                                                                                                           |
+----------------+-----+-------------+-----------+-------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| bin-log.000001 |   4 | Format_desc |         1 |         107 | Server ver: 5.5.29-log, Binlog ver: 4                                                                                                                                                                          |
| bin-log.000001 | 107 | Query       |         1 |         364 | use `test`; CREATE TABLE `btest` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `age` int(11) DEFAULT NULL,
  `name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 |
| bin-log.000001 | 364 | Query       |         1 |         432 | BEGIN                                                                                                                                                                                                          |
| bin-log.000001 | 432 | Query       |         1 |         536 | use `test`; insert into btest values(1,100,'zhaohui')                                                                                                                                                          |
| bin-log.000001 | 536 | Xid         |         1 |         563 | COMMIT /* xid=30 */                                                                                                                                                                                            |
| bin-log.000001 | 563 | Stop        |         1 |         582 |                                                                                                                                                                                                                |
+----------------+-----+-------------+-----------+-------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

3.經過java代碼來讀取binlog日誌,具體代碼以下:

public class BinlogRead {
 
    private static RandomAccessFile file;
    /** 魔數的字節長度 **/
    private static final int MAGIN_LEN = 4;
    /** 事件header長度 **/
    private static final int EVENT_HEADER_LEN = 19;
    /** Query_Event fix data長度 **/
    private static final int QUERY_EVENT_FIX_LEN = 13;
 
    public static void main(String[] args) throws Exception {
        file = new RandomAccessFile(new File("D://bin-log.000001"), "rw");
        FileChannel channel = file.getChannel();
 
        /** 1.魔數4字節 **/
        ByteBuffer magic = ByteBuffer.allocate(MAGIN_LEN);
        channel.read(magic);
 
        /** 2.Format_desc_Event事件 **/
        EventHeader header = getEventHeader(channel);
        channel.position(header.getEventLen() + MAGIN_LEN);
 
        /** 3.Query_Event事件 **/
        header = getEventHeader(channel);
        System.out.println(getQueryEventSql(header.getEventLen(), channel));
 
        /** 4.Query_Event事件 **/
        header = getEventHeader(channel);
        System.out.println(getQueryEventSql(header.getEventLen(), channel));
 
        /** 5.Query_Event事件 **/
        header = getEventHeader(channel);
        System.out.println(getQueryEventSql(header.getEventLen(), channel));
 
        /** 6.Xid_Event事件 **/
        header = getEventHeader(channel);
        ByteBuffer xidNumber = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN);
        channel.read(xidNumber);
        xidNumber.flip();
        System.out.println("xidNumber = " + xidNumber.getLong());
 
        /** 7.Stop_Event事件 **/
        header = getEventHeader(channel);
 
    }
 
    /**
     * 獲取事件Header信息
     * 
     * @param channel
     * @return
     * @throws IOException
     */
    private static EventHeader getEventHeader(FileChannel channel) throws IOException {
        ByteBuffer formatDescEventHeader = ByteBuffer.allocate(EVENT_HEADER_LEN).order(ByteOrder.LITTLE_ENDIAN);
        channel.read(formatDescEventHeader);
        formatDescEventHeader.flip();
        EventHeader header = new EventHeader();
        header.setTimestamp(formatDescEventHeader.getInt());
        header.setTypeCode(formatDescEventHeader.get());
        header.setServerId(formatDescEventHeader.getInt());
        header.setEventLen(formatDescEventHeader.getInt());
        header.setNextPosition(formatDescEventHeader.getInt());
        header.setFlags(formatDescEventHeader.getShort());
        System.out.println(header.toString());
        return header;
    }
 
    /**
     * 獲取Query Event sql語句
     * 
     * @param queryEventLen
     * @param channel
     * @return
     * @throws IOException
     */
    private static String getQueryEventSql(int queryEventLen, FileChannel channel) throws IOException {
        /** Query_Event fix data **/
        ByteBuffer queryEventFix = ByteBuffer.allocate(QUERY_EVENT_FIX_LEN).order(ByteOrder.LITTLE_ENDIAN);
        channel.read(queryEventFix);
        queryEventFix.flip();
        queryEventFix.position(11);
 
        /** 狀態變量的長度 **/
        int statusLen = queryEventFix.getShort();
 
        int queryEventValLen = queryEventLen - EVENT_HEADER_LEN - QUERY_EVENT_FIX_LEN;
        ByteBuffer queryEventVal = ByteBuffer.allocate(queryEventValLen).order(ByteOrder.LITTLE_ENDIAN);
        channel.read(queryEventVal);
        queryEventVal.flip();
        queryEventVal.position(statusLen);
 
        /** 數據庫名稱 **/
        queryEventVal.mark();
        int length = 0;
        while ('\0' != queryEventVal.get()) {
            length++;
        }
        queryEventVal.reset();
        byte dbName[] = new byte[length];
        queryEventVal.get(dbName);
        System.out.println("db name : " + new String(dbName, "utf-8"));
 
        /** sql語句 **/
        byte sql[] = new byte[queryEventValLen - statusLen - length - 1];
        queryEventVal.get(sql);
        return new String(sql, "utf-8");
    }
}
public class EventHeader {
 
    private int timestamp;
    private byte typeCode;
    private int serverId;
    private int eventLen;
    private int nextPosition;
    private int flags;
 
    @Override
    public String toString() {
        return "EventHeader [timestamp=" + timestamp + ", typeCode=" + typeCode + ", serverId=" + serverId
                + ", eventLen=" + eventLen + ", nextPosition=" + nextPosition + ", flags=" + flags + "]";
    }
         
        //...get/set方法省略...
}

總結 本文對事件的數據格式作了詳細的介紹,由於全部事件的event header部分都是同樣的,因此文中主要介紹的event data部分,event data主要包括兩個部分:Fixed data part和Variable part;最後經過一個簡單實例來大體瞭解事件數據的讀取方式,後續會提供更詳細的binlog事件數據讀取。

相關文章
相關標籤/搜索