
我们两年前有讲过QUERY_EVENT的结构, 但对于其中的status_var讲得比较浅. 所以本次来完善完善,为后面的使用打下基础.
明文的SQL,ROW模式下通常是DDL,STATEMENT就主要是DML了. 官方文档里面的名字和mysqlbinlog解析出来的名字有区别的, 我就按照mysqlbinlog解析的名字来(更好理解)
文档里面的slave_proxy_id和本文的thread_id是一样的, 但感觉没得thread_id好理解...
对象 | 大小 | 描述 |
|---|---|---|
thread_id | 4 | 产生该SQL的thread_id, 就是show processlist看到的那个id |
exec_time | 4 | 该SQL执行的时间. 单位是秒, 所以精度也就到秒了. |
schema_len | 1 | 数据库名长度,执行该SQL时默认的schema名字.(如果没有use db的话, 就是0) |
error_code | 2 | 该SQL对应的错误码 |
status_vars_len | 2 | 如果binlog-version>4的话, 才有这玩意. 记录有多少个状态码.每个状态码1字节 |
status_vars | status_vars_len | 具体的状态码 |
schema | schema_len+1 | 数据库名, 即使有数据库名也不一定要输出, 还得看event_header的flags是否没有LOG_EVENT_SUPPRESS_USE_F. 不管有没有数据库名,都是\x00结尾 |
query | 剩下的都是(除了checksum) | DDL |
那么状态码status_vars有哪些呢?
status_vars是key/value对应的, key固定1字节, value根据key来
key的各value大小如下:
keyid | key对象名 | 对应的value大小 | 描述 |
|---|---|---|---|
0x00 | Q_FLAGS2_CODE | 4 | 一些flag |
0x01 | Q_SQL_MODE_CODE | 8 | sql_mode相关的 |
0x02 | Q_CATALOG_CODE | 1+n+1 | 5.0.0-3才有的,应该是元数据信息. |
0x03 | Q_AUTO_INCREMENT | 2+2 | 如果自增(auto_increment)大于1,就有这玩意,起始就是auto_increment_increment+auto_increment_offset |
0x04 | Q_CHARSET_CODE | 2+2+2 | 字符集相关的 |
0x05 | Q_TIME_ZONE_CODE | 1+n | 时区相关的 |
0x06 | Q_CATALOG_NZ_CODE | 1+n | catalog相关的 |
0x07 | Q_LC_TIME_NAMES_CODE | 2 | 和LC_TIME 相关的 |
0x08 | Q_CHARSET_DATABASE_CODE | 2 | 库级别的字符集 |
0x09 | Q_TABLE_MAP_FOR_UPDATE_CODE | 8 | row格式下多表的更新的table_map |
0x0a | Q_SOURCE_DATA_WRITTEN_CODE | 4 | 以前叫Q_MASTER_DATA_WRITTEN_CODE |
0x0b | Q_INVOKERS | 1+n+1+n | invoker相关的, 就是User+host |
0x0c | Q_UPDATED_DB_NAMES | 1+n*nul-term-string | 受影响的数据库信息, 1字节数量, 跟上这么多个以\x00结尾的库 |
0x0d | Q_MICROSECONDS | 3 | 时间精度 |
0x0e | Q_COMMIT_TS | 8 | 同G_COMMIT_TS |
0x0f | Q_COMMIT_TS2 | 8 | 替代Q_COMMIT_TS的,同G_COMMIT_TS2 |
0x10 | Q_EXPLICIT_DEFAULTS_FOR_TIMESTAMP | 1 | 就是explicit_defaults_for_timestamp |
0x11 | Q_DDL_LOGGED_WITH_XID | 8 | xid |
0x12 | Q_DEFAULT_COLLATION_FOR_UTF8MB4 | 2 | utf8mb4默认的collation |
0x13 | Q_SQL_REQUIRE_PRIMARY_KEY | 2 | 参数sql_require_primary_key |
0x14 | Q_DEFAULT_TABLE_ENCRYPTION | 2 | 参数default_table_encryption |
来看看各key对应的value的取值:
Q_FLAGS2_CODE 记录一些query_options. 4字节能记录32个, 但实际上有37种(8.0有40种), 不过后面的都用不到, 实际上前面的也就几种会出现在Binlog里面(binlog记录写,不记录读). 其实就是一些变量值
id | 对象 | 描述 |
|---|---|---|
0 | SELECT_DISTINCT | |
1 | SELECT_STRAIGHT_JOIN | |
2 | SELECT_DESCRIBE | |
3 | SELECT_SMALL_RESULT | |
4 | SELECT_BIG_RESULT | |
5 | OPTION_FOUND_ROWS | |
6 | OPTION_TO_QUERY_CACHE | |
7 | SELECT_NO_JOIN_CACHE | |
8 | OPTION_AUTOCOMMIT | |
9 | OPTION_BIG_SELECTS | |
10 | OPTION_LOG_OFF | |
11 | OPTION_QUOTE_SHOW_CREATE | |
12 | TMP_TABLE_ALL_COLUMNS | |
13 | OPTION_WARNINGS | |
14 | OPTION_AUTO_IS_NULL | 对应sql_auto_is_null |
15 | OPTION_FOUND_COMMENT | |
16 | OPTION_SAFE_UPDATES | |
17 | OPTION_BUFFER_RESULT | |
18 | OPTION_BIN_LOG | |
19 | OPTION_NOT_AUTOCOMMIT | 对应autocommit |
20 | OPTION_BEGIN | |
21 | OPTION_TABLE_LOCK | |
22 | OPTION_QUICK | |
23 | OPTION_NO_CONST_TABLES | |
24 | SELECT_ALL | |
25 | SELECT_NO_SEMI_JOIN | |
26 | OPTION_NO_FOREIGN_KEY_CHECKS | 对应foreign_key_checks |
27 | OPTION_RELAXED_UNIQUE_CHECKS | 对应unique_checks |
28 | SELECT_NO_UNLOCK | |
29 | OPTION_SCHEMA_TABLE | |
30 | OPTION_SETUP_TABLES_DONE | |
31 | OPTION_SQL_NOTES | |
32 | TMP_TABLE_FORCE_MYISAM | |
33 | OPTION_PROFILING | |
34 | SELECT_HIGH_PRIORITY | |
35 | OPTION_MASTER_SQL_ERROR | |
36 | OPTION_ALLOW_BATCH | |
37 | OPTION_SELECT_FOR_SHOW | |
38 | OPTION_DD_UPDATE_CONTEXT | |
39 | OPTION_NO_SUBQUERY_DURING_OPTIMIZATION |
实际上写binlog的就4个, 即
OPTIONS_WRITTEN_TO_BIN_LOG = (OPTION_AUTO_IS_NULL|OPTION_NO_FOREIGN_KEY_CHECKS|OPTION_RELAXED_UNIQUE_CHECKS|OPTION_NOT_AUTOCOMMIT)再来看看Q_SQL_MODE_CODE,其实看名字都知道是sql_mode了.
这玩意使用8字节, 也就是能表示64种sql_mode值. 我们之前也有讲过sql_mode有哪些:https://cloud.tencent.com/developer/article/2600365
但实际上就32种: 如下
bitmask | name | 描述 |
|---|---|---|
1<<0 | MODE_REAL_AS_FLOAT | |
1<<1 | MODE_PIPES_AS_CONCAT | |
1<<2 | MODE_ANSI_QUOTES | |
1<<3 | MODE_IGNORE_SPACE | |
1<<4 | MODE_NOT_USED | |
1<<5 | MODE_ONLY_FULL_GROUP_BY | |
1<<6 | MODE_NO_UNSIGNED_SUBTRACTION | |
1<<7 | MODE_NO_DIR_IN_CREATE | |
1<<8 | MODE_POSTGRESQL | |
1<<9 | MODE_ORACLE | |
1<<10 | MODE_MSSQL | |
1<<11 | MODE_DB2 | |
1<<12 | MODE_MAXDB | |
1<<13 | MODE_NO_KEY_OPTIONS | |
1<<14 | MODE_NO_TABLE_OPTIONS | |
1<<15 | MODE_NO_FIELD_OPTIONS | |
1<<16 | MODE_MYSQL323 | |
1<<17 | MODE_MYSQL40 | |
1<<18 | MODE_ANSI | |
1<<19 | MODE_NO_AUTO_VALUE_ON_ZERO | |
1<<20 | MODE_NO_BACKSLASH_ESCAPES | |
1<<21 | MODE_STRICT_TRANS_TABLES | |
1<<22 | MODE_STRICT_ALL_TABLES | |
1<<23 | MODE_NO_ZERO_IN_DATE | |
1<<24 | MODE_NO_ZERO_DATE | |
1<<25 | MODE_INVALID_DATES | |
1<<26 | MODE_ERROR_FOR_DIVISION_BY_ZERO | |
1<<27 | MODE_TRADITIONAL | |
1<<28 | MODE_NO_AUTO_CREATE_USER | |
1<<29 | MODE_HIGH_NOT_PRECEDENCE | |
1<<30 | MODE_NO_ENGINE_SUBSTITUTION | |
1<<31 | MODE_PAD_CHAR_TO_FULL_LENGTH |
5.0.0-5.0.3版本才有的, 结构如下:
对象 | 大小 | 描述 |
|---|---|---|
len | 1 | 大小 |
data | len | 值 |
end | 1 | \x00 |
就是记录自增的起始值和步长. 使用2+2字节. 所以自增起始值auto_increment_offset最大是65535, 步长auto_increment_increment也是最大65536.
所以我们解析binlog的时候,也要像mysqlbinlog那样列出关键信息(SET @@session.auto_increment_increment=65535, @@session.auto_increment_offset=1234/*!*/;)
对象 | 大小 | 描述 |
|---|---|---|
auto_increment_increment | 2 | 自增步长 |
auto_increment_offset | 2 | 自增初始值 |
比如:
# 完整的event paload
b'\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00%\x00\x00\x00\x00\x00\x00\x01 \x00\xa0U\x00\x00\x00\x00\x06\x03std\x03\xff\xff\xd2\x04\x04!\x00!\x00!\x00\x0c\x01db1\x00\x00create table db1.t20260327_02(id int auto_increment,c1 int,key(id))\xe3\x82[\xdf'
# status_vars
b'\x00\x00\x00\x00\x00\x01 \x00\xa0U\x00\x00\x00\x00\x06\x03std\x03\xff\xff\xd2\x04\x04!\x00!\x00!\x00\x0c\x01db1\x00'
# Q_AUTO_INCREMENT
b'\x03\xff\xff\xd2\x04'
03
65536
1234字符集相关的.
对象 | 大小 | 描述 |
|---|---|---|
character_set_client | 2 | 客户端字符集 |
collation_connection | 2 | 连接的collation |
collation_server | 2 | server得到collation |
时区相关的.
对象 | 大小 | 描述 |
|---|---|---|
len | 1 | 长度 |
time_zone | len | 时区 |
5.0.4及其之后才有的,
对象 | 大小 | 描述 |
|---|---|---|
len | 1 | 长度 |
catalog | len | catalog |
比如:
b'\x06\x03std'
len:03
catalog:std和LC_TIME相关的,定义怎么解析week-,day-name之类的. 固定2字节.
对象 | 大小 | 描述 |
|---|---|---|
lc_code | 2 | lc_time_names |
虽然使用2字节, 但实际上就111种. 代码里面判断的时候就考虑255, 但非要2字节,不知道为啥...
if (lc_time_names_number) {
assert(lc_time_names_number <= 0xFF);
*start++ = Q_LC_TIME_NAMES_CODE;
int2store(start, lc_time_names_number);
start += 2;
}如下: 0就是my_locale_en_US, 1就是my_locale_en_GB 依此类推
&my_locale_en_US, &my_locale_en_GB, &my_locale_ja_JP, &my_locale_sv_SE,
&my_locale_de_DE, &my_locale_fr_FR, &my_locale_ar_AE, &my_locale_ar_BH,
&my_locale_ar_JO, &my_locale_ar_SA, &my_locale_ar_SY, &my_locale_be_BY,
&my_locale_bg_BG, &my_locale_ca_ES, &my_locale_cs_CZ, &my_locale_da_DK,
&my_locale_de_AT, &my_locale_es_ES, &my_locale_et_EE, &my_locale_eu_ES,
&my_locale_fi_FI, &my_locale_fo_FO, &my_locale_gl_ES, &my_locale_gu_IN,
&my_locale_he_IL, &my_locale_hi_IN, &my_locale_hr_HR, &my_locale_hu_HU,
&my_locale_id_ID, &my_locale_is_IS, &my_locale_it_CH, &my_locale_ko_KR,
&my_locale_lt_LT, &my_locale_lv_LV, &my_locale_mk_MK, &my_locale_mn_MN,
&my_locale_ms_MY, &my_locale_nb_NO, &my_locale_nl_NL, &my_locale_pl_PL,
&my_locale_pt_BR, &my_locale_pt_PT, &my_locale_ro_RO, &my_locale_ru_RU,
&my_locale_ru_UA, &my_locale_sk_SK, &my_locale_sl_SI, &my_locale_sq_AL,
&my_locale_sr_RS, &my_locale_ta_IN, &my_locale_te_IN, &my_locale_th_TH,
&my_locale_tr_TR, &my_locale_uk_UA, &my_locale_ur_PK, &my_locale_vi_VN,
&my_locale_zh_CN, &my_locale_zh_TW, &my_locale_ar_DZ, &my_locale_ar_EG,
&my_locale_ar_IN, &my_locale_ar_IQ, &my_locale_ar_KW, &my_locale_ar_LB,
&my_locale_ar_LY, &my_locale_ar_MA, &my_locale_ar_OM, &my_locale_ar_QA,
&my_locale_ar_SD, &my_locale_ar_TN, &my_locale_ar_YE, &my_locale_de_BE,
&my_locale_de_CH, &my_locale_de_LU, &my_locale_en_AU, &my_locale_en_CA,
&my_locale_en_IN, &my_locale_en_NZ, &my_locale_en_PH, &my_locale_en_ZA,
&my_locale_en_ZW, &my_locale_es_AR, &my_locale_es_BO, &my_locale_es_CL,
&my_locale_es_CO, &my_locale_es_CR, &my_locale_es_DO, &my_locale_es_EC,
&my_locale_es_GT, &my_locale_es_HN, &my_locale_es_MX, &my_locale_es_NI,
&my_locale_es_PA, &my_locale_es_PE, &my_locale_es_PR, &my_locale_es_PY,
&my_locale_es_SV, &my_locale_es_US, &my_locale_es_UY, &my_locale_es_VE,
&my_locale_fr_BE, &my_locale_fr_CA, &my_locale_fr_CH, &my_locale_fr_LU,
&my_locale_it_IT, &my_locale_nl_BE, &my_locale_no_NO, &my_locale_sv_FI,
&my_locale_zh_HK, &my_locale_el_GR, &my_locale_rm_CH, nullptr};库级别的字符集, 2字节表示
对象 | 大小 | 描述 |
|---|---|---|
charset | 2 | 字符集 |
多表更新时候使用的, 比如update db1.t20260327 as a, db1.t20260327_02 as b set a.id=666601,b.id=666602;
使用8字节表示, 每个bit对应1张表
对象 | 大小 | 描述 |
|---|---|---|
table_map_bitmask | 8 |
比如:
# sql: update db1.t20260327 as a, db1.t20260327_02 as b set a.id=666601,b.id=666602;
# payload
b'\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00+\x00\x00\x00\x00\x00\x00\x01 \x00\xa0U\x00\x00\x00\x00\x06\x03std\x03\xff\xff\xd2\x04\x04!\x00!\x00!\x00\x07n\x00\t\x03\x00\x00\x00\x00\x00\x00\x00\x00BEGINm\x9c\x1bh'
# Q_TABLE_MAP_FOR_UPDATE_CODE
b'\t\x03\x00\x00\x00\x00\x00\x00\x00'
b'\x03\x00\x00\x00\x00\x00\x00\x00'
也就是3, 即'00000011' 1bit对应1张表.所以是更新的前面2张表
# sql: update db1.t20260327 as a, db1.t20260327_02 as b set b.id=777702;
# payload
b'\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00+\x00\x00\x00\x00\x00\x00\x01 \x00\xa0U\x00\x00\x00\x00\x06\x03std\x03\xff\xff\xd2\x04\x04!\x00!\x00!\x00\x07n\x00\t\x02\x00\x00\x00\x00\x00\x00\x00\x00BEGIN3\x9d\xab\xcc'
# Q_TABLE_MAP_FOR_UPDATE_CODE
b'\t\x02\x00\x00\x00\x00\x00\x00\x00'
b'\x02\x00\x00\x00\x00\x00\x00\x00'
也就是2,即'00000010' 1bit对应1张表, 所以是更新的第二张表.感觉多此一举
这条SQL产生了多少日志? 也没啥用
对象 | 大小 | 描述 |
|---|---|---|
size | 4 |
记录INVOKERS的. 如下:
对象 | 大小 | 描述 |
|---|---|---|
username_length | 1 | 用户名大小 |
username | username_length | 用户名 |
hostname_length | 1 | 主机名大小 |
hostname | hostname_length | 主机名 |
受影响的库信息
对象 | 大小 | 描述 |
|---|---|---|
count | 1 | 数据库数量 |
dbname | count(dbname+1) | 具体的库信息 |
比如:
b'\x0c\x01db1\x00'
12 0x0c
count=1 0x01
db1 db1\x00如果是statement之类的, 则使用3字节来记录精度, 比如执行SQLinsert into db1.t20260327_03 values(4,now(6));
对象 | 大小 | 描述 |
|---|---|---|
frac | 3 | 时间的精度. |
比如:
# status_vars
b'\x00\x00\x00\x00\x00\x01 \x00\xa0U\x00\x00\x00\x00\x06\x03std\x03\xff\xff\xd2\x04\x04!\x00!\x00!\x00\x05\x06SYSTEM\x07n\x00\x0c\x01db1\x00\r\xfe\x14\x05'
# Q_MICROSECONDS
b'\r\xfe\x14\x05'
b'\r' 13
b'\xfe\x14\x05' 33305 # 精度是小端字节序的, 可以在末尾加\x00, 方便使用struct.unpack('<L',)来做QUERY_EVENT的结构虽然比较简单, 但涉及的内容还蛮多的, 我们画个图来总结下(这熟悉的超长图又回来了):

参考: https://dev.mysql.com/doc/dev/mysql-server/8.4.7/page_protocol_replication_binlog_event.html
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。