[pymysqlbinlog] TABLE_MAP_EVENT
导读
本来打算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).
                        
 
 
                     
                     
                     
                    