导读
本来打算table_map_event和row_event一起写的. 但table_map_event的信息还是太多了, 就先写一部分. 其实之前有提过的 https://www.modb.pro/db/1763358489816174592
但现在要系统的更新下, 尽量更完善.
table_map_event 是在row event前面的, 主要是记录元数据信息的, 比如库名,表名,字段类型等
TABLE_MAP_EVENT
5.7 版本和8.0 版本是存在区别的, 8.0 新增了opt optional metadata fields 来描述更多信息, 比如符号之类的
先看下结构吧
对象 | 大小(字节) | 描述 |
---|---|---|
table_id | 6 | 表id |
flags | 2 | |
dbname_length | 1 | 库名字长度 |
dbname | dbname_length | 库名(x00结尾, 不计入length) |
tablename_length | 1 | 表名字长度(表名长度限制64字节) |
table_name | tablename_length | 表名字(x00结尾, 不计入length) |
column_count | pack_int | 字段数量 |
column_type | column_count | 字段类型, 每个字段使用1字节表示 |
metadata_length | pack_int | 元数据信息长度 |
metadata | metadata_length | 元数据信息 |
null_bits | int((self.column_count+7)/8) | 是否允许为空 |
opt | tlv | 8.0新增的 |
pack_int
先来看看 pack_int 其实之前讲过, 变长类型
代码参考:
# @mysys/pack.cc net_field_length_size
def read_net_int(self,):
"""
1 3 4 9 (不含第一字节)
"""
data = self.read_uint(1)
if data < 251:
return data
elif data == 251:
return self.read_uint(1)
elif data == 252:
return self.read_uint(2)
elif data == 253:
return self.read_uint(3)
else:
return self.read_uint(8)
column_type
字段类型. 先看看吧
# @include/field_types.h
MYSQL_TYPE_DECIMAL = 0
MYSQL_TYPE_TINY = 1
MYSQL_TYPE_SHORT = 2
MYSQL_TYPE_LONG = 3
MYSQL_TYPE_FLOAT = 4
MYSQL_TYPE_DOUBLE = 5
MYSQL_TYPE_NULL = 6
MYSQL_TYPE_TIMESTAMP = 7
MYSQL_TYPE_LONGLONG = 8
MYSQL_TYPE_INT24 = 9
MYSQL_TYPE_DATE = 10
MYSQL_TYPE_TIME = 11
MYSQL_TYPE_DATETIME = 12
MYSQL_TYPE_YEAR = 13
MYSQL_TYPE_NEWDATE = 14 #/**< Internal to MySQL. Not used in protocol */
MYSQL_TYPE_VARCHAR = 15
MYSQL_TYPE_BIT = 16
MYSQL_TYPE_TIMESTAMP2 = 17
MYSQL_TYPE_DATETIME2 = 18 #/**< Internal to MySQL. Not used in protocol */
MYSQL_TYPE_TIME2 = 19 #/**< Internal to MySQL. Not used in protocol */
MYSQL_TYPE_TYPED_ARRAY = 20 #/**< Used for replication only */
MYSQL_TYPE_INVALID = 243
MYSQL_TYPE_BOOL = 244 #/**< Currently just a placeholder */
MYSQL_TYPE_JSON = 245
MYSQL_TYPE_NEWDECIMAL = 246
MYSQL_TYPE_ENUM = 247
MYSQL_TYPE_SET = 248
MYSQL_TYPE_TINY_BLOB = 249
MYSQL_TYPE_MEDIUM_BLOB = 250
MYSQL_TYPE_LONG_BLOB = 251
MYSQL_TYPE_BLOB = 252
MYSQL_TYPE_VAR_STRING = 253
MYSQL_TYPE_STRING = 254
MYSQL_TYPE_GEOMETRY = 255
metadata
再来看看元数据信息. 部分字段是存在元数据信息的, 比如varchar(N)
, 这个N就是它的元数据信息, 记录最大值. 具体信息等到 row event 再说. 基本上就是不固定长度的类型才有的.
null_bits
记录字段是否为空的. 和row_event里面的bitmask有关联的(到了再看). 每个字段使用1bit, 所以要使用 int((column_count+7)/8)
字节
opt
8.0 才有的opt optional metadata fields, 但8.0的mysqlbinlog不会解析这个信息(我怀疑是官方偷懒). 作为binlog解析工具, 我们还是要来解析的.
该opt是存在event结尾的, 所以null_bits后面全是opt, 不用记录大小, 直接读就行. 格式是tls的
1字节记录类型, L字节(pack_int)记录元数据长度. V就是记录长度的结果
先来看看1字节的字段类型: 1 表示第一个, 2表示第二个, 依次类推
不是所有类型都有, 有些要求 binlog_row_metadata=FULL
比如字段名字. 这里只看部分信息, 其它信息等row event的时候再看
类型 | 格式 | 描述 |
---|---|---|
IGNEDNESS | 对于有符号的字段, 每个字段使用1bit来表示. | 符号 |
DEFAULT_CHARSET | 字符集 | |
COLUMN_CHARSET | 字段字符集 | |
COLUMN_NAME | 1字节大小,后面就是字段名字 | 字段名字 |
SET_STR_VALUE | set的值 | |
ENUM_STR_VALUE | enum的值 | |
GEOMETRY_TYPE | 空间坐标 | |
SIMPLE_PRIMARY_KEY | 主键 | |
PRIMARY_KEY_WITH_PREFIX | 主键前缀 | |
ENUM_AND_SET_DEFAULT_CHARSET | ||
ENUM_AND_SET_COLUMN_CHARSET | ||
COLUMN_VISIBILITY | 空间坐标 |
验证
现在我们来验证下
使用官方的mysqlbinlog解析信息如下:
就一丢丢, 只有名字…
再使用我们的工具来解析下
我们解析出来的信息就要多很多了. 比如字段名字啊, 符号啊之类的.再和数据库验证下吧
(root@127.0.0.1) [ibd2sql]> desc ddcw_alltype_table;
+---------------+-------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------+-------------------+------+-----+---------+----------------+
| id | int | NO | PRI | NULL | auto_increment |
| int_col | int | YES | | NULL | |
| tinyint_col | tinyint | YES | | 1 | |
| smallint_col | smallint | YES | | NULL | |
| mediumint_col | mediumint | YES | | NULL | |
| bigint_col | bigint | YES | | NULL | |
| float_col | float | YES | | NULL | |
| double_col | double | YES | | NULL | |
| decimal_col | decimal(10,2) | YES | | NULL | |
| date_col | date | YES | | NULL | |
| datetime_col | datetime | YES | | NULL | |
| timestamp_col | timestamp | YES | | NULL | |
| time_col | time | YES | | NULL | |
| year_col | year | YES | | NULL | |
| char_col | char(100) | YES | | NULL | |
| varchar_col | varchar(200) | YES | | aa | |
| binary_col | binary(10) | YES | | NULL | |
| varbinary_col | varbinary(20) | YES | | NULL | |
| bit_col | bit(4) | YES | | NULL | |
| enum_col | enum('A','B','C') | YES | | NULL | |
| set_col | set('X','Y','Z') | YES | | NULL | |
| josn_type | json | YES | | NULL | |
| newcol | varchar(200) | YES | | aa | |
| newcol2 | varchar(200) | YES | | aa | |
| newcoldasdas2 | varchar(300) | YES | | bbaa | |
+---------------+-------------------+------+-----+---------+----------------+
是对应上了的. 说明我们解析正确.
注: 由于数据存储只能按照字节存, 所以null_bits里面实际上可能结尾多几个bit. 可以看null_bit_bool 是按照字段数量来做的.
其它
有了元数据信息后, 我们就可以解析row event了. 然后就能拼接为SQL了. 但5.7版本没得opt信息, 无法区分符号, 也没得字段信息, 所以还是建议转为base64 方便点. (毕竟官方也是使用的base64来做的.)
话说Pymysqlbinlog是用来干嘛的呢. 目前计划是实现如下功能. 数据过滤那一段已经实现了, 主要是比较简单…
注意: binlog的元数据信息可能不包含 字段名字, 字段符号(mysqlbinlog解析的时候也有这个问题, 所以要使用base64,或者知道元数据信息)
除了数据走stdout, 其它均走stderr
rollback 生成回滚SQL. 按照事务顺序 (读两次IO)
metadata 元数据信息的xml文件. 如果有的话, 则自动替换sql/sql2里面的字段名字
sql2 生成sql格式 (非注释), 由于数据类型转换可能存在问题, 建议使用base64格式
sql 生成sql格式 (注释的sql)
sql-complete 对于insert使用完整sql, 含字段名
sql-replace 使用replace替换insert和update
base64 生成base64格式(默认)
base64-disable 不要base64格式的数据, 如果没有sql/sql2, 则自动启动sql选项
debug 展示完整过程, 主要用于调试(stderr)
verbose 显示格外DEUG信息.
# 数据过滤, 优先匹配include, 匹配失败再匹配exclude, 匹配成功(返回False)则跳过
schema-include 同schema
schema-exclude
schema-replace 库名字替换, 所有符合要求的schema换为这个名字
table-include 同table
table-exclude
gtid-skip
gtid-include 同gtid
gtid-exclude
serverid-include
serverid-exclude
start-datetime
stop-datetime
start-position
stop-position
# 审计相关(不统计过滤掉的event), 走stderr
analyze-event 基于event做统计, 各event类型的数量, 大小
analyze-table 基于表做统计 各表的大小, 各表的dml操作数量/行数/大小
analyze-trx 基于事务做统计 大事务(不含gtid event, 但起止pos含gtid和xid).