天天看點

Binlog詳解

Mysql binlog是二進制日志檔案,用于記錄mysql的資料更新或者潛在更新(比如DELETE語句執行删除而實際并沒有符合條件的資料),在mysql主從複制中就是依靠的binlog。可以通過語句“show binlog events in 'binlogfile'”來檢視binlog的具體事件類型。binlog記錄的所有操作實際上都有對應的事件類型的,MySQL binlog的三種工作模式:

(1)Row level(用到MySQL的特殊功能如存儲過程、觸發器、函數,又希望資料最大化一直則選擇Row模式,我們公司選擇的是row)

  簡介:日志中會記錄每一行資料被修改的情況,然後在slave端對相同的資料進行修改。

  優點:能清楚的記錄每一行資料修改的細節

  缺點:資料量太大

(2)Statement level(預設)

  簡介:每一條被修改資料的sql都會記錄到master的bin-log中,slave在複制的時候sql程序會解析成和原來master端執行過的相同的sql再次執行。在主從同步中一般是不建議用statement模式的,因為會有些語句不支援,比如語句中包含UUID函數,以及LOAD DATA IN FILE語句等

  優點:解決了 Row level下的缺點,不需要記錄每一行的資料變化,減少bin-log日志量,節約磁盤IO,提高新能

  缺點:容易出現主從複制不一緻

(3)Mixed(混合模式)

  簡介:結合了Row level和Statement level的優點,同時binlog結構也更複雜。

binlog結構解析

binlog結構圖如下:

Binlog詳解

image.png

binlog類似位元組碼一樣定義了很多種類型,每個類型又有特定的結構。檢視Mysql源碼(mysql-5.7.14/libbinlogevents/include/binlog_event.h)對event類型的定義如下:

enum Log_event_type
{
  /**
    Every time you update this enum (when you add a type), you have to
    fix Format_description_event::Format_description_event().
  */
  UNKNOWN_EVENT= 0,
  START_EVENT_V3= 1,
  QUERY_EVENT= 2,
  STOP_EVENT= 3,
  ROTATE_EVENT= 4,
  INTVAR_EVENT= 5,
  LOAD_EVENT= 6,
  SLAVE_EVENT= 7,
  CREATE_FILE_EVENT= 8,
  APPEND_BLOCK_EVENT= 9,
  EXEC_LOAD_EVENT= 10,
  DELETE_FILE_EVENT= 11,
  /**
    NEW_LOAD_EVENT is like LOAD_EVENT except that it has a longer
    sql_ex, allowing multibyte TERMINATED BY etc; both types share the
    same class (Load_event)
  */
  NEW_LOAD_EVENT= 12,
  RAND_EVENT= 13,
  USER_VAR_EVENT= 14,
  FORMAT_DESCRIPTION_EVENT= 15,
  XID_EVENT= 16,
  BEGIN_LOAD_QUERY_EVENT= 17,
  EXECUTE_LOAD_QUERY_EVENT= 18,

  TABLE_MAP_EVENT = 19,

  /**
    The PRE_GA event numbers were used for 5.1.0 to 5.1.15 and are
    therefore obsolete.
   */
  PRE_GA_WRITE_ROWS_EVENT = 20,
  PRE_GA_UPDATE_ROWS_EVENT = 21,
  PRE_GA_DELETE_ROWS_EVENT = 22,

  /**
    The V1 event numbers are used from 5.1.16 until mysql-trunk-xx
  */
  WRITE_ROWS_EVENT_V1 = 23,
  UPDATE_ROWS_EVENT_V1 = 24,
  DELETE_ROWS_EVENT_V1 = 25,

  /**
    Something out of the ordinary happened on the master
   */
  INCIDENT_EVENT= 26,

  /**
    Heartbeat event to be send by master at its idle time
    to ensure master's online status to slave
  */
  HEARTBEAT_LOG_EVENT= 27,

  /**
    In some situations, it is necessary to send over ignorable
    data to the slave: data that a slave can handle in case there
    is code for handling it, but which can be ignored if it is not
    recognized.
  */
  IGNORABLE_LOG_EVENT= 28,
  ROWS_QUERY_LOG_EVENT= 29,

  /** Version 2 of the Row events */
  WRITE_ROWS_EVENT = 30,
  UPDATE_ROWS_EVENT = 31,
  DELETE_ROWS_EVENT = 32,

  GTID_LOG_EVENT= 33,
  ANONYMOUS_GTID_LOG_EVENT= 34,

  PREVIOUS_GTIDS_LOG_EVENT= 35,

  TRANSACTION_CONTEXT_EVENT= 36,

  VIEW_CHANGE_EVENT= 37,

  /* Prepared XA transaction terminal event similar to Xid */
  XA_PREPARE_LOG_EVENT= 38,
  /**
    Add new events here - right above this comment!
    Existing events (except ENUM_END_EVENT) should never change their numbers
  */
  ENUM_END_EVENT /* end marker */
};
           

下面描述幾個重要的EVENT類型:

QUERY_EVENT

QUERY_EVENT以文本的形式來記錄事務的操作。QUERY_EVENT類型的事件通常在以下幾種情況下使用:

  1. 事務開始時,執行的BEGIN操作。
  2. STATEMENT格式中的DML操作。
  3. ROW格式中的DDL操作。

固定資料部分:

  • 4位元組表示發起這個語句的線程id,對于臨時表來說是必須的。這也有助于DBA知道誰在master上幹了啥。
  • 4位元組表示語句執行的時長,機關為秒。隻對于DBA的監控有用。
  • 1位元組表示語句執行對應的資料庫名稱的長度。名稱在可變資料部分。
  • 2位元組表示在master執行語句的錯誤碼。錯誤碼定義在include/mysqld_error.h檔案中。0表示沒有錯誤。
  • 2位元組表示狀态變量長度。

可變資料部分:

  • 大于等于0的狀态變量。每個狀态變量包含一個位元組碼,辨別存儲變量,後面跟着變量的值。變量的值的位元組長度是根據變量規定好的。詳見下面變量說明
  • 預設資料庫名
  • SQL語句。slave知道變量中其他字段的長度,是以通過減法計算,可以知道語句的長度。

下面的清單提供了每個變量的基本資訊:

  • Q_FLAGS2_CODE=0。值為一個4位元組的位字段。這個變量隻會在5.0中寫入。
  • Q_SQL_MODE_CODE = 1。 他是一個每一位代表SQL_MODE中的一個值,參考最後源碼的解釋。
  • Q_CATALOG_CODE = 2.。隻在MYSQL 5.0.0到5.0.3使用不考慮
  • Q_AUTO_INCREMENT = 3。 2 bytes非負整數用于表示參數auto_increment_increment和auto_increment_offset,這個隻會在auto_increment大于1的時候出現。
  • Q_CHARSET_CODE = 4。 6 bytes用于表示character_set_client,collation_connection和collation_server參數(totally 2+2+2=6 bytes)參考最後源碼解釋
  • Q_TIME_ZONE_CODE = 5。用于描述time zone資訊
  • Q_CATALOG_NZ_CODE = 6。用于描述catalog name,長度占用一個位元組,随後這個值為std
  • Q_LC_TIME_NAMES_CODE = 7。 2 bytes 非負整數,隻有當lc_time_names不設定為en_US的時候使用
  • Q_CHARSET_DATABASE_CODE = 8。2 bytes 非負整數為collation_database系統變量,5.7源碼解釋說這部分新版本不一定使用。
  • Q_TABLE_MAP_FOR_UPDATE_CODE = 9。

FORMAT_DESCRIPTION_EVENT

FORMAT_DESCRIPTION_EVENT是binlog version 4中為了取代之前版本中的START_EVENT_V3事件而引入的。它是binlog檔案中的第一個事件,而且,該事件隻會在binlog中出現一次。MySQL根據FORMAT_DESCRIPTION_EVENT的定義來解析其它事件。

它通常指定了MySQL Server的版本,binlog的版本,該binlog檔案的建立時間。

  • 2位元組表示binlog格式版本,對于5.0及以上版本,是4.
  • 50位元組表示Mysql伺服器版本,後面帶着0x00。
  • 4位元組表示事件建立的時間戳。多餘的參數,和頭中的時間戳重複。
  • 1位元組表示頭長度。目前的值為19.
  • 空。

ROWS_EVENT

對于ROW格式的binlog,所有的DML語句都是記錄在ROWS_EVENT中。

ROWS_EVENT分為三種:WRITE_ROWS_EVENT,UPDATE_ROWS_EVENT,DELETE_ROWS_EVENT,分别對應insert,update和delete操作。

對于insert操作,WRITE_ROWS_EVENT包含了要插入的資料

對于update操作,UPDATE_ROWS_EVENT不僅包含了修改後的資料,還包含了修改前的值。

對于delete操作,僅僅需要指定删除的主鍵(在沒有主鍵的情況下,會給定所有列)

對于QUERY_EVENT事件,是以文本形式記錄DML操作的。而對于ROWS_EVENT事件,并不是文本形式,是以在通過mysqlbinlog檢視基于ROW格式的binlog時,需要指定-vv --base64-output=decode-rows。

XID_EVENT

在事務送出時,不管是STATEMENT還是ROW格式的binlog,都會在末尾添加一個XID_EVENT事件代表事務的結束。該事件記錄了該事務的ID,在MySQL進行崩潰恢複時,根據事務在binlog中的送出情況來決定是否送出存儲引擎中狀态為prepared的事務。

  • 8位元組,XID事務号

ROTATE_EVENT

當binlog檔案的大小達到max_binlog_size的值或者執行flush logs指令時,binlog會發生切換,這個時候會在目前的binlog日志添加一個ROTATE_EVENT事件,用于指定下一個日志的名稱和位置。

  • 8位元組。下個日志檔案的第一個事件的位置。經常包含數字4(表示下個binlog的下個事件從位置4開始)。這個字段在v1中不存在。可以推測,這個值是4。
  • 下個binlog檔案的名字。檔案名不是以null結尾的,他的長度是事件長度-固定資料長度。

GTID_LOG_EVENT

GTID

即全局事務ID(global transaction identifier)是Mysql5.6版本引入的,GTID實際上是由UUID+TID組成的。其中UUID是一個MySQL執行個體的唯一辨別。TID代表了該執行個體上已經送出的事務數量,并且随着事務送出單調遞增,是以GTID能夠保證每個MySQL執行個體事務的執行(不會重複執行同一個事務,并且會補全沒有執行的事務)。在啟用GTID模式後,MySQL實際上為每個事務都配置設定了個GTID。

譬如:

# at 448
#160818  5:37:32 server id 1  end_log_pos 496 CRC32 0xaeb24aac     GTID [commit=yes]
SET @@SESSION.GTID_NEXT= 'cad449f2-5d4f-11e6-b353-000c29c64704:3'/*!*/;
# at 496
#160818  5:37:32 server id 1  end_log_pos 571 CRC32 0x042ca092     Query    thread_id=2    exec_time=0    error_code=0
SET TIMESTAMP=1471469852/*!*/;
BEGIN
/*!*/;
# at 571
#160818  5:37:32 server id 1  end_log_pos 674 CRC32 0xa35beb37     Query    thread_id=2    exec_time=0    error_code=0
SET TIMESTAMP=1471469852/*!*/;
insert into test.t1 values(2,'b')
/*!*/;
# at 674
#160818  5:37:32 server id 1  end_log_pos 705 CRC32 0x1905d8c6     Xid = 12
COMMIT/*!*/;
           

PREVIOUS_GTIDS_LOG_EVENT

開啟GTID模式後,每個binlog開頭都會有一個PREVIOUS_GTIDS_LOG_EVENT事件,它的值是上一個binlog的PREVIOUS_GTIDS_LOG_EVENT+GTID_LOG_EVENT,實際上,在資料庫重新開機的時候,需要重新填充gtid_executed的值,該值即是最新一個binlog的PREVIOUS_GTIDS_LOG_EVENT+GTID_LOG_EVENT。

mysql-bin.000033日志中的Previous_gtids是cad449f2-5d4f-11e6-b353-000c29c64704:1,GTID是cad449f2-5d4f-11e6-b353-000c29c64704:2和cad449f2-5d4f-11e6-b353-000c29c64704:3,這樣,在下一個日志,即mysql-bin.000034中的Previous_gtids是cad449f2-5d4f-11e6-b353-000c29c64704:1-3。

STOP_EVENT

當MySQL資料庫停止時,會在目前的binlog末尾添加一個STOP_EVENT事件表示資料庫停止。

HEARTBEAT_LOG_EVENT

這個事件是由master發給slave的,讓slave知道master還活着。這類事件不會再binlog或relay log中出現。他們由master的dump事件線程産生,然後直接發給了slave。slave收到後,校驗事件内容後,直接抛棄這個事件,而不會寫到relay log中。

固定資料部分

  • 可變資料部分