MySQL binlog知识总结

Binlog概述

MySQL binlog(binary log)包含所有数据更新的语句和可能更新数据的语句(例如,没有匹配行的DELETE),语句以描述修改的事件(Event)形式存储的,此外,日志中还包含每个更新数据语句所花时间的信息,以及一些元数据信息:

  • 服务器状态信息,这是正确复制语句所需的
  • 错误代码
  • 维护binlog本身所需的元数据

MySQL binlog中包含的事件是对服务器在运行期间全局状态变化的追踪,基于此日志信息,可以重现服务器上发生的变化,这也就是binlog最重要的两个目的:

  • 主从复制,通过将主服务器上的binlog包含的事件发送给从服务器,从服务器执行这些事件后,就能实现与主服务器上相同的数据状态修改。从服务器会将收到来自主节点的事件存储到其relay log中,直到它们可以被执行。relay log具有和binlog一样的日志格式。
  • 数据恢复,数据库进行数据恢复时,备份文件中只包含了数据截止到备份点的状态,这就需要通过binlog将数据从备份节点更新到最新状态。

Binlog具有两种类型,一种是基于语句的日志记录,即事件中包含产生数据变化的SQL语句(INSERT, UPDATE, DELETE);另一种是基于行的日志记录,即事件描述单行数据的变化。自MySQL 5.1版本之后,支持混合类型日志记录,即默认使用基于语句的日志记录,在必要时自动切换到基于行的日志记录。

Binlog结构和内容

Binlog是一组文件,包含对MySQL服务器实例进行数据修改的信息:

  • Binlog由一组二进制日志文件和一个索引文件组成;
  • 每个二进制日志文件包含一个4字节的数字,和描述数据修改的一组事件:
    • 此4字节数字是0xfe 0x62 0x69 0x6e = 0xfe ‘b’’i’’n’,被称为log_event.h中的BINLOG_MAGIC常数;
    • 每个事件包含头字节和数据字节两部分。头字节提供了关于事件类型的信息,即事件由在何时由哪个服务器产生等等,数据字节提供了事件类型的具体信息,例如某个特定数据的修改信息;
    • 第一个事件是描述符事件,描述了文件的格式版本(用于在文件中写入事件的格式),其余事件根据此版本格式来解释,最后一个事件是日志旋转事件,用于指定下一个日志文件名。
  • 索引文件是一个文本文件,其列出了当前所有的二进制文件。

日志文件使用.NNNNNNN的后缀进行顺序编号,索引文件后缀是.index。所有文件使用一个共同的基名,默认基名是“HOSTNAME-bin”,在此默认基名下,日志文件和索引文件的命名如下:

1
2
3
4
5
6
...
HOSTNAME-bin.000101
HOSTNAME-bin.000102
HOSTNAME-bin.000103
...
HOSTNAME-bin.index

前面说到relay log具有与binlog一样的日志格式,其文件命名也与binlog日志文件命名类似,默认基名是“HOSTNAME-relay”,在此默认基名下,relay log文件命名如下:

1
2
3
4
5
6
...
HOSTNAME-relay.0000101
HOSTNAME-relay.0000102
HOSTNAME-relay.0000103
...
HOSTNAME-relay.index

与Binlog相关源文件

Mysql源文件地址:https://github.com/mysql/mysql-server,本节将介绍其中与binlog处理最相关的文件。

sql目录

  • log.h/log.cc: 将事件组织成一个序列的高级二进制日志机制,以便成为binlog,是一个创建、写入和删除binlog文件的程序。
  • log_event.h/log_event.cc: 将数值序列化为记录的低级二进制日志机制。Log_event类和子类用于创建、写入、读取、打印和应用每种事件类型的事件。这里的读和写都是低级别的,也就是将值序列化为记录。
  • rpl_constants.h: 包含INCIDENT_EVENT事件类型的代码。
  • rpl_injector.h/rpl_injector.cc: 包含允许外部注入binlog的注入器类,用于集群复制二进制日志。
  • rpl_record.h/rpl_record.cc: 用于将表的行编码和解码为行事件所使用的格式。
  • rpl_tblmap.h/rpl_tblmap.cc: 包含一个从数字到表的映射,此映射被行日志系统用于识别表。
  • rpl_utility.h/rpl_utility.cc: 包含用于Table_map_events的辅助类和函数,以及一个用于智能指针的辅助类。
  • sql_binlog.h/sql_binlog.cc: 执行BINLOG语句的代码(mysqlbinlog看到行事件时打印的base64编码值)。
  • sql_base.h/sql_base.cc: 函数decision_logging_format()决定语句是否应该使用基于行或者基于语句格式写入binlog。

client目录

  • mysqlbinlog.h/mysqlbinlog.cc: 工具mysqlbinlog的源代码,mysqlbinlog可用于读取二进制的binlog文件并以文本格式展示。

事件类和类型

MySQL服务器使用C++类来表示binlog日志事件,原型在log_event.h中,这些类的方法代码在log_event.cc中。

Log_event是基类,其他更具体的事件子类是由它派生而来。类型代码与子类相关联,因为类的实例内容被写入到binlog或者relay log或通过主节点发送到从节点。在这种情况下,一个事件只是一个字节序列,而不是一个类结构,所以需要一个类型代码来允许从字节序列中识别出事件类型。

一个事件的字节序列有一个头部分和一个数据部分,其中类型代码在头部分。

有些类没有与任何类型代码相关联,因为它们只被用作派生子类的基类,或者因为它们从来没有被写入到binlog或者relay log或者从主节点发送到从节点。例如Log_event没有类型代码,因为它只作为基类使用。

相反地,一个类也可以与多个类型代码相关联,例如Load_log_event可以包含LOAD_EVENT或NEW_LOAD_EVENT的类型代码。

事件含义

本节将简要描述每种事件类型的含义:

  • UNKNOWN_EVENT

    这种事件类型不应该发生,它永远不会写入到binlog中,如果从binlog中读取的事件不能被识别为其他事件类型,那么将被视为UNKOWN_EVENT类型。

  • START_EVENT_V3

    一个描述符事件,被写入到每个binlog文件的开头(在MySQL 4.0和4.1中,该事件只会被写入到服务器启动后创建的第一个binlog文件中),该事件在MySQL 3.23到4.1中使用,在MySQL 5.0中被FORMAT_DESCRIPTION_EVENT所取代。

  • QUERY_EVENT

    在更新语句完成时写入此事件到binlog中。

  • STOP_EVENT

    在mysqld停止时写入此事件。

  • ROTATE_EVENT

    当mysqld切换到一个新的binlog文件时写入,即当执行FLUSH LOGS语句或者当前binlog文件大小超过max_binlog_size。

  • INTVAR_EVENT

    每次语句使用AUTO_INCREMENT列或者LAST_INSERT_ID()函数时写入,此事件写在语句的其他事件之前,并且只写在QUERY_EVENT之前,不用于基于行的日志记录。INTVAR_EVENT在事件数据部分写有子类型:

    • INSERT_ID_EVENT: 表示在下一条语句中用于AUTO_INCREMENT列中的值
    • LAST_INSERT_ID: 表示在写一条语句中用于LAST_INSERT_ID()函数的值
  • LOAD_EVENT

    在MySQL 3.23中用于LOAD DATA INFILE语句。

  • SLAVE_EVENT

    不使用

  • CREATE_FILE_EVENT

    在MySQL 4.0和4.1中用于LOAD DATA INFILE语句。

  • APPEND_BLOCK_EVENT

    在MySQL 4.0中用于LOAD DATA INFILE语句。

  • EXEC_LOAD_EVENT

    在MySQL 4.0和4.1中用于LOAD DATA INFILE语句。

  • DELETE_FILE_EVENT

    在MySQL 4.0中用于LOAD DATA INFILE语句。

  • NEW_LOAD_EVENT

    在MySQL 4.0和4.1中用于LOAD DATA INFILE语句。

  • RAND_EVENT

    每次语句使用RAND()函数时,在语句的其他事件写入之前写入此事件。表示在下一条语句中使用RAND()生成随机数时要使用的种子值。这只写在QUERY_EVENT之前,不用于基于行的日志记录中。

  • USER_VAR_EVENT

    每次语句使用用户变量时,在语句的其他事件之前写入此事件。表示在写一条语句中使用用户变量的值。这只写在QUERY_EVENT之前,不用于基于行的日志记录。

  • FORMAT_DESCRIPTION_EVENT

    一个描述符事件,被写入在每个binlog文件的开头,此事件从MySQL 5.0开始使用,其取代了START_EVENT_V3。

  • XID_EVENT

    为修改一个或多个具有XA功能的存储引擎的表的事务的提交而产生的。正常的事务是通过发送一个包含BEGIN语句的QUERY_EVENT和一个包含COMMIT语句的QUERY_EVENT来实现的(如果事务被回滚,则是ROLLBACK语句)。

  • BEGIN_LOAD_QUERY_EVENT

    在MySQL 5.0中用于LOAD DATA INFILE语句。

  • EXECUTE_LOAD_QUERY_EVENT

    在MySQL 5.0中用于LOAD DATA INFILE语句。

  • TABLE_MAP_EVENT

    用于基于行的二进制日志记录。该事件在每个行操作事件之前写入到binlog中,它将一个表的定义映射到一个数字,其中表的定义由数据库和表名以及列的定义组成。这个事件的目的是当一个表在主节点和从节点有不同的定义时,能够进行复制。属于同一个事务的行操作事件可以被分组为序列,每个事件序列以TABLE_MAP_EVENT事件开始:序列中的事件使用的每个表都有一个。

  • PRE_GA_WRITE_ROWS_EVENT

    WRITE_ROWS_EVENT的过时版本。

  • PRE_GA_UPDATE_ROWS_EVENT

    UPDATE_ROWS_EVENT的过时版本。

  • PRE_GA_DELETE_ROWS_EVENT

    DELETE_ROWS_EVENT的过时版本。

  • WRITE_ROWS_EVENT

    用于基于行的二进制日志记录,此事件记录了在单个表插入行的情况。

  • UPDATE_ROWS_EVENT

    用于基于行的二进制日志记录,此事件记录了单个表中的行的更新。

  • DELETE_ROWS_EVENT

    用于基于行的二进制日志记录,此事件记录了单个表中行的删除情况。

  • INCIDENT_EVENT

    用于记录在主节点上发生的非正常事件,通知从节点主节点上发生的事情可能导致数据状态不一致。

  • HEARTBEAT_LOG_EVENT

    从主节点发送至从节点的事件,让从节点知道主节点仍然活着,此事件不写入日志文件中。

事件结构

本节描述了事件作为字节序列写入binlog或relay log时的一般属性,所有事件有一个共同的结构,即包括事件头和事件数据。

MySQL不同版本,binlog总共有三种事件结构,v1、v3和v4。v4版本的事件结构在MySQL 5.0和之后版本使用,下面主要对此版本结构进行介绍。

![event structure](/Users/gupp/Documents/pictures/event structure.png)

事件头有x个字节,事件数据有event_length - x个字节;

固定数据长度有y字节,可变数据长度有event_length - (x+y)个字节。

x由格式描述事件(Format Description Event, FDE)中header_length字段给出,目前x是19,所以extra_headers字段是空;y是针对事件类型的,同样由FDE给出,对于一个给定类型的所有事件,固定部分的长度是相同的,但是不同事件类型的长度可能不同。事件数据的固定部分有时被称为“后头”部分,可变部分有时被称为“有效载荷”或“主体”。

  • timestamp

    语句开始执行的时间

  • type_code

    事件类型

  • server_id

    最初创建该事件的mysqld服务器的ID

  • event_length

    事件总大小,包括header和data部分

  • next_position

    主节点binlog中下一个事件的位置

  • flags

    v3和之后的格式中包含的事件标识

基于行的二进制日志

最初binlog是基于语句日志记录的,在MySQL 5.1.5中添加了基于行的日志记录。基于语句的日志记录,事件包含产生数据变化(插入、更新、删除)的SQL语句;基于行的日志记录,事件描述单个行的变化。

以下几种事件类型专门用于基于语句日志记录:

  • TABLE_MAP_EVENT
  • WRITE_ROWS_EVENT
  • UPDATE_ROWS_EVENT
  • DELETE_ROWS_EVENT

本文主要是对MySQL的开发者官方文档中Binlog部分的翻译总结,原文档地址为:https://dev.mysql.com/doc/internals/en/binary-log.html