binlog简介
binlog是二进制日志文件,用于记录mysql的数据更新或者潜在更新(比如DELETE语句执行删除而实际并没有符合条件的数据),在mysql主从复制中就是依靠的binlog。
需要注意下innodb引擎中的redo/undo log与mysql binlog是完全不同的日志,它们主要有以下几个区别:
-
a)层次不同。redo/undo log是innodb层维护的,而binlog是mysql server层维护的,跟采用何种引擎没有关系,记录的是所有引擎的更新操作的日志记录。innodb的redo/undo log更详细的说明可以参见姜承尧的《mysql技术内幕-innodb存储引擎》一书中相关章节。
-
b)记录内容不同。redo/undo日志记录的是每个页的修改情况,属于物理日志+逻辑日志结合的方式(redo log物理到页,页内采用逻辑日志,undo log采用的是逻辑日志),目的是保证数据的一致性。binlog记录的都是事务操作内容,比如一条语句
DELETE FROM TABLE WHERE i > 1
之类的,不管采用的是什么引擎,当然格式是二进制的,要解析日志内容可以用这个命令mysqlbinlog -vv BINLOG
。 -
c)记录时机不同。redo/undo日志在事务执行过程中会不断的写入;binlog是在事务最终commit前写入的。当然,binlog什么时候刷新到磁盘跟参数
sync_binlog
相关。
event结构
binlog格式分为statement,row以及mixed三种,mysql5.5默认的还是statement模式,当然我们在主从同步中一般是不建议用statement模式的,因为会有些语句不支持,比如语句中包含UUID函数,以及LOAD DATA IN FILE语句
等,一般推荐的是mixed格式。暂且不管这三种格式的区别,看看binlog的存储格式是什么样的。binlog是一个二进制文件集合,当然除了我们看到的mysql-bin.xxxxxx这些binlog文件外,还有个binlog索引文件mysql-bin.index。如官方文档中所写,binlog格式如下:
-
binlog文件以一个值为0Xfe62696e的魔数开头,这个魔数对应0xfe 'b''i''n'。
-
binlog由一系列的binlog event构成。每个binlog event包含header和data两部分。
-
header部分提供的是event的公共的类型信息,包括event的创建时间,服务器等等。
-
data部分提供的是针对该event的具体信息,如具体数据的修改。
-
-
从mysql5.0版本开始,binlog采用的是v4版本,第一个event都是
format_desc event
用于描述binlog文件的格式版本,这个格式就是event写入binlog文件的格式。 -
接下来的event就是按照上面的格式版本写入的event。
-
最后一个
rotate event
用于说明下一个binlog文件。 -
binlog索引文件是一个文本文件,其中内容为当前的binlog文件列表。比如下面就是一个mysql-bin.index文件的内容。
/var/log/mysql/mysql-bin.000019
/var/log/mysql/mysql-bin.000020
/var/log/mysql/mysql-bin.000021
接下来分析下几种常见的event,其他的event类型可以参见官方文档。event数据结构如下:
+=====================================+
| event | timestamp 0 : 4 |
| header +----------------------------+
| | type_code 4 : 1 |
| +----------------------------+
| | server_id 5 : 4 |
| +----------------------------+
| | event_length 9 : 4 |
| +----------------------------+
| | next_position 13 : 4 |
| +----------------------------+
| | flags 17 : 2 |
| +----------------------------+
| | extra_headers 19 : x-19 |
+=====================================+
| event | fixed part x : y |
| data +----------------------------+
| | variable part |
+=====================================+
在mysql中创建一个表,依次执行插入、删除、更新操作
mysql> show create table test.t\G
*************************** 1. row ***************************
Table: t
Create Table: CREATE TABLE `t` (
`a` int(11) DEFAULT NULL,
`b` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin
1 row in set (0.00 sec)
mysql> insert into test.t values(2,5),(3,6);
Query OK, 2 rows affected (0.00 sec)
Records: 2 Duplicates: 0 Warnings: 0
mysql> delete from test.t where a=2;
Query OK, 1 row affected (0.00 sec)
mysql> update test.t set a=4 where b=6;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
使用hexdump来查看各event的结构
Format_desc event
# Format_desc 偏移4,长度120
[teledb@k8s-master data8841]$ hexdump -s 4 -n $((124 - 4)) -C mysql-bin.000010
00000004 6f 72 dc 5f 0f 09 a2 69 05 78 00 00 00 7c 00 00 |or._...i.x...|..|
00000014 00 01 00 04 00 38 2e 30 2e 31 38 00 00 00 00 00 |.....8.0.18.....|
00000024 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00000044 00 00 00 00 00 00 00 00 00 00 00 13 00 0d 00 08 |................|
00000054 00 00 00 00 04 00 04 00 00 00 60 00 04 1a 08 00 |..........`.....|
00000064 00 00 08 08 08 02 00 00 00 0a 0a 0a 2a 2a 00 12 |............**..|
00000074 34 00 0a 01 a7 33 f4 59 |4....3.Y|
0000007c
先看前19个字节的event头部,
前4个字节是时间戳;
0f表示event类型,对应FORMAT_DESCRIPTION_EVENT=15;
紧接着的09 a2 69 05是server_id,对应程序中配置的十进制数90808841(0x0569a209,x86架构是小端序);
之后的4个字节是event长度,00 00 00 78表示该event长度为120;
再然后是下一个event起始点,00 00 00 7c表示下一个event起始点为124;
接着的2个字节的00 01是flag(1为LOG_EVENT_BINLOG_IN_USE_F,标识binlog还没有关闭,binlog关闭后,flag会被设置为0。
到此公共头的部分就结束了。
然后是这个event的data部分,event的data分为Fixed data
和Variable data
两部分,其中Fixed data
是event的固定长度和格式的数据,Variable data
则是长度变化的数据,比如format_desc event的Fixed data长度是0x60=96个字节。下面看下这96=2+50+4+1+39个字节的分配:开始的2个字节0x0004为binlog的版本号4,接着的50个字节为mysql-server版本,该binlog显示是8.0.18,与SELECT version();
查看的结果一致。接下来4个字节是binlog创建时间,这里是0;然后的1个字节0x13是指之后所有event的公共头长度,这里都是19;接着的39个字节中每个字节为mysql已知的event(共39个)的Fixed data的长度;可以发现format_desc event自身的Variable data部分为5。
type_code为15的Format_desc event的fix data长度为0x60(96)
Query event
[teledb@k8s-master data8841]$ hexdump -s 274 -n $((345 - 274)) -C mysql-bin.000010
00000112 01 73 dc 5f 02 09 a2 69 05 47 00 00 00 59 01 00 |.s._...i.G...Y..|
00000122 00 08 00|5e|00 00 00 00 |00 00 00 00|00 00|1d 00| |...^............|
00000132 00 00 00 00 00 01 00 00 a0 45 00 00 00 00 06 03 |.........E......|
00000142 73 74 64 04 ff 00 ff 00 2e 00 12|ff 00 00|42 45 |std...........BE|
00000152 47 49 4e 72 c1 2b e0 |GINr.+.|
00000159
头部跟之前的event一样,只是query event的type为0x02,长度为0x47=71,下一个event位置为0x 01 59 = 345。flag为8,接着是data部分,从format_desc event我们可以知道query event的Fixed data
部分为13个字节,因此也可以算出Variable data
部分为71-19-13=39字节。
-
Fixed data:首先的4个字节0x 00 00 00 5e为执行该语句的thread id,接下来的4个字节是执行的时间0(以秒为单位),接下来的1个字节0x04是语句执行时的默认数据库名字的长度(这里没有指定默认数据库)。接着的2个字节0x0000是错误码(注:通常情况下错误码是0表示没有错误,但是在一些非事务性表如myisam表执行
INSERT...SELECT
语句时可能插入部分数据后遇到duplicate-key错误会产生错误码1062,或者是事务性表在INSERT...SELECT出错不会插入部分数据,但是在执行过程中CTRL+C终止语句也可能记录错误码。slave db在复制时会执行后检查错误码是否一致,如果不一致,则复制过程会中止),接着2个字节0x001d为状态变量块的长度29。 -
Variable data:从0x001d之后的29个字节为状态变量块,然后是默认数据库名,以0x00结尾,然后是sql语句BEGIN,最后4个字节是校验码。
Rows_query event
[teledb@k8s-master data8841]$ hexdump -s 345 -n $((405 - 345)) -C mysql-bin.000010
00000159 01 73 dc 5f 1d 09 a2 69 05 3c 00 00 00 95 01 00 |.s._...i.<......|
00000169 00 80 00 24 69 6e 73 65 72 74 20 69 6e 74 6f 20 |...$insert into |
00000179 74 65 73 74 2e 74 20 76 61 6c 75 65 73 28 32 2c |test.t values(2,|
00000189 35 29 2c 28 33 2c 36 29 6a c1 02 50 |5),(3,6)j..P|
00000195
该种类型的event比较简单,type是0x1d,除了公共头和最后的校验码以外 ,中间的就是执行的sql语句。
Table_map event
一、介绍
TABLE_MAP_EVENT用于描述即将发生变化的表的结构。ROW模式下,当用户提交一条修改语句时(如, insert, update, delete),MySQL会产生2个Binlog事件: 第一个就是TABLE_MAP_EVENT,用于描述改变对应表的结构(表名, 列的数据类型等信息);紧接着的是ROWS_EVENT,用于描述对应表的行的变化值
table_map_event对应的class是Table_map_log_event(对应源码sql/log_event.cc)
二、格式
table_map_log_event的格式如下图所示:
- pack_length
首先介绍一个常用的术语pack_length。它是一种数值的存储方式,在Binary Log和MySQL的通讯包中经常用到。为了减少存储和传输时占用的空间,长度字段和整形的数值采用变长的方式存储。具体算法是:
- 小于251的数值
用1个字节直接存储该数值
- 大于等于251,小于等于65535(0xFF)的数值
先用1个字节存储数值252,再用2个字节存储该数值。
- 大于65535,小于等于16777215(0xFFF)的数值
先用1个字节存储数值253,再用3个字节存储该数值。
- 大于16777215的数值
先用1个字节存储数值254,再用8个字节存储该数值。
如果大多数情况下,数值小于251,这种存储方法比较节约空间。同时,又能够支持数值很大的情况。
- 参考代码
sql-common/pack.c中的net_store_length().
- Event Header
和其他 event一样。
- table id
table id就像一个表的主键,唯一识别一个table_map_log_event。为了节省空间,rows event中不会记录数据库名和表名,只会记录一个table id。通过table id来关联到一个table_map_log_event,就可以知道是哪个表产生的。
- flags
目前未使用,内容始终为0,占2字节。
- db name length
数据库名的长度,不包含'\0'。采用pack_lenght存储。
- database name
数据库名,以'\0'结尾,因此占用的空间是db name length+1字节。
- table name length
表名长度,不包含'\0'。采用pack_lenght存储。
- table name
表名名,以'\0'结尾,因此占用的空间是table name length+1字节。
- column count 字段的个数。采用pack_lenght存储。
- column types
这里的字段类型不是SQL语句中的类型而是MySQL内部的数据类型。每个字段的类型占一个字节,总长度等于字段的个数(column count)。类型存储的顺序是按照master上该表中字段的顺序产生的。所以如果slave上表的字段顺序变了,就会导致错误。
- metadata length
字段的元数据长度。采用pack_lenght存储。
- metadata
除了类型,字段还有长度等属性。这些属性信息存储在metadata中,后边会详细介绍。
- null flags
字段是否可以为空的属性,通过位图方式记录,1个bit记录一个字段。
[teledb@k8s-master data8841]$ hexdump -s 405 -n $((453 - 405)) -C mysql-bin.000010
00000195 01 73 dc 5f 13 09 a2 69 05 30 00 00 00 c5 01 00 |.s._...i.0......|
000001a5 00 00 00|60 00 00 00 00 00|01 00|04|74 65 73 74 |...`........test|
000001b5 00|01|74 00|02|03 03|00| 03 01 01 00|fb 05 bf 96 |..t.............|
000001c5
type: 0x13,公共头之后fix data部分长度为8,前6个字节0x 00 00 00 00 00 60是table id,然后的2个字节0x 00 01是flag。Variable data部分,首先1个字节0x04为数据库名test的长度,然后5个字节是数据库名test + \0
。接着1个字节0x01为表名长度,接着2个字节为表名t + \0
。接着1个字节0x02为列的数目。而后是2个列的类型定义,此处都是0x03(列的类型MYSQL_TYPE_LONG为0x03)。接着是列的元数据定义,0x00表示元数据长度为0,因为MYSQL_TYPE_LONG没有元数据。然后是metadata和null flag信息,以及校验码。
Write_rows event
[teledb@k8s-master data8841]$ hexdump -s 453 -n $((506 - 453)) -C mysql-bin.000010
000001c5 01 73 dc 5f 1e 09 a2 69 05 35 00 00 00 fa 01 00 |.s._...i.5......|
000001d5 00 00 00|60 00 00 00 00 00|01 00|02 00|02|ff|00| |...`............|
000001e5 02 00 00 00|05 00 00 00| 00|03 00 00 00|06 00 00 |................|
000001f5 00|59 59 8f 73 |.YY.s|
000001fa
type: 0x1e,公共头之后的6个字节0x 00 00 00 00 00 60是table id,然后的2个字节0x 00 01是flag,接着的2个字节0x 00 02 是extra_data_len(extra_data_len - 2是extra_data的长度,此处为空)接着的1个字节0x 02表示列的数量,然后1个字节0xff各个bit标识各列是否存在值,这里表示都存在?。
接着的就是插入的各行数据了,第1个字节0x00的各个bit标识该行变化之后各列是否为NULL,为NULL的bit记为0,(00表示全部非null),MYSQL_TYPE_LONG长度为4个字节,故可以看出受到影响的列为(0x 00 00 00 02,0x 00 00 00 05),(0x 00 00 00 03,0x 00 00 00 06),也就是插入的值。
Delete_rows event
[teledb@k8s-master data8841]$ hexdump -s 787 -n $((831 - 787)) -C mysql-bin.000010
00000313 48 73 dc 5f 20 09 a2 69 05 2c 00 00 00 3f 03 00 |Hs._ ..i.,...?..|
00000323 00 00 00 60 00 00 00 00 00 01 00 02 00 02 ff 00 |...`............|
00000333 02 00 00 00 05 00 00 00 58 7c a8 9f |........X|..|
0000033f
type: 0x20,分析基本和Write_rows event一致,只是event类型变了。
Update_rows event
[teledb@k8s-master data8841]$ hexdump -s 1124 -n $((1178 - 1124)) -C mysql-bin.000010
00000464 5e 73 dc 5f 1f 09 a2 69 05 36 00 00 00 9a 04 00 |^s._...i.6......|
00000474 00 00 00|60 00 00 00 00 00|01 00|02 00|02|ff|ff| |...`............|
00000484 00|03 00 00 00|06 00 00 00|00|04 00 00 00|06 00 |................|
00000494 00 00|56 f2 7f e4 |..V...|
0000049a
type: 0x1f,受到影响的列为(0x 00 00 00 03,0x 00 00 00 06),变为(0x 00 00 00 04,0x 00 00 00 06)
Xid event
[teledb@k8s-master data8841]$ hexdump -s 506 -n $((537 - 506)) -C mysql-bin.000010
000001fa 01 73 dc 5f 10 09 a2 69 05 1f 00 00 00 19 02 00 |.s._...i........|
0000020a 00 00 00 85 98 0a 00 00 00 00 00 84 01 6f 55 |.............oU|
00000219
xid event为COMMIT语句。前19个字节是通用头部,type是0x10(16)。data部分中Fixed data为空,而variable data为8个字节,这8个字节0x85 98 0a 00 00 00 00 00是事务编号,最后4个字节 为校验码。