系列文章html
MySql Binlog初識java
MySql Binlog事件介紹篇mysql
Mysql通信協議分析數據庫
前言
前兩篇文章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事件數據讀取。