共计 5841 个字符,预计需要花费 15 分钟才能阅读完成。
本篇内容介绍了“怎么理解 MySQL 中的 table_id”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让丸趣 TV 小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!
一 table_id 介绍
当 MySQL 开启日志模式时,binlog 会记录所有对数据库的变更操作。binlog 分两种模式 statement 模式和 row 模式。
当数据库的 binlog format 是 statement 模式时
例子:数据库中执行 一条语句
root@rac2 [yangyi] insert into t1 values(9);
Query OK, 1 row affected (0.00 sec)
root@rac2 [yangyi] show binlog events in mysql-bin.000003
+——————+—–+————-+———–+————-+—————————————-+
| Log_name | Pos | Event_type | Server_id | End_log_pos | Info |
+——————+—–+————-+———–+————-+—————————————-+
| mysql-bin.000003 | 4 | Format_desc | 2 | 106 | Server ver: 5.1.68-log, Binlog ver: 4 |
| mysql-bin.000003 | 106 | Query | 2 | 176 | BEGIN |
| mysql-bin.000003 | 176 | Query | 2 | 265 | use `yangyi`; insert into t1 values(8) |
| mysql-bin.000003 | 265 | Xid | 2 | 292 | COMMIT /* xid=12 */ |
| mysql-bin.000003 | 292 | Query | 2 | 369 | use `yangyi`; flush tables |
| mysql-bin.000003 | 369 | Query | 2 | 439 | BEGIN |
| mysql-bin.000003 | 439 | Query | 2 | 528 | use `yangyi`; insert into t1 values(9) |
| mysql-bin.000003 | 528 | Xid | 2 | 555 | COMMIT /* xid=15 */ |
+——————+—–+————-+———–+————-+—————————————-+
8 rows in set (0.00 sec)
binlog 的 log event 记录如下:
#140511 14:44:12 server id 2 end_log_pos 439 Query thread_id=1 exec_time=0 error_code=0
SET TIMESTAMP=1399790652/*!*/;
BEGIN
/*!*/;
# at 439
#140511 14:44:12 server id 2 end_log_pos 528 Query thread_id=1 exec_time=0 error_code=0
SET TIMESTAMP=1399790652/*!*/;
insert into t1 values(9)
/*!*/;
# at 528
#140511 14:44:12 server id 2 end_log_pos 555 Xid = 15
COMMIT/*!*/;
从日志分析来看,DML 会记录为原始的 SQL,也就是记录在 QUERY_EVENT 中。
当数据库的 binlog format 是 row 模式时
执行 insert 操作
root@rac2 [yangyi] insert into t1 values(6);
Query OK, 1 row affected (0.00 sec)
root@rac2 [yangyi] show binlog events in mysql-bin.000002
+——————+—–+————-+———–+————-+—————————————+
| Log_name | Pos | Event_type | Server_id | End_log_pos | Info |
+——————+—–+————-+———–+————-+—————————————+
| mysql-bin.000002 | 4 | Format_desc | 2 | 106 | Server ver: 5.1.68-log, Binlog ver: 4 |
| mysql-bin.000002 | 106 | Query | 2 | 176 | BEGIN |
| mysql-bin.000002 | 176 | Table_map | 2 | 219 | table_id: 18 (yangyi.t1) |
| mysql-bin.000002 | 219 | Write_rows | 2 | 253 | table_id: 18 flags: STMT_END_F |
| mysql-bin.000002 | 253 | Xid | 2 | 280 | COMMIT /* xid=61 */ |
+——————+—–+————-+———–+————-+—————————————+
5 rows in set (0.00 sec)
binlog 中记录的信息:
BEGIN
/*!*/;
# at 176
# at 219
#140511 14:31:43 server id 2 end_log_pos 219 Table_map: `yangyi`.`t1` mapped to number 18
#140511 14:31:43 server id 2 end_log_pos 253 Write_rows: table id 18 flags: STMT_END_F
BINLOG
TxlvUxMCAAAAKwAAANsAAAAAABIAAAAAAAEABnlhbmd5aQACdDEAAQMAAQ==
TxlvUxcCAAAAIgAAAP0AAAAAABIAAAAAAAEAAf/+BgAAAA==
/*!*/;
### INSERT INTO `yangyi`.`t1`
### SET
### @1=6 /* INT meta=0 nullable=1 is_null=0 */
# at 253
#140511 14:31:43 server id 2 end_log_pos 280 Xid = 61
COMMIT/*!*/;
从解析的 binlog 中可以看出 row 模式下,DML 操作会记录为:TABLE_MAP_EVENT+ROW_LOG_EVENT(包括 WRITE_ROWS_EVENT,UPDATE_ROWS_EVENT,DELETE_ROWS_EVENT).
为什么一个 update 在 ROW 模式下需要分解成两个 event:一个 Table_map,一个 Update_rows。我们想象一下,一个 update 如果更新了 10000 条数据,那么对应的表结构信息是否需要记录 10000 次? 其实是对同一个表的操作,所以这里 binlog 只是记录了一个 Table_map 用于记录表结构相关信息, 而后面的 Update_rows 记录了更新数据的行信息。他们之间是通过 table_id 来联系的。
二 table_id 的特性
1 table_id 并不是固定的, 它是当表被载入内存(table_definition_cache) 时,临时分配的,是一个不断增长的变量。
2 当有新的 table 变更时,在 cache 中没有,就会触发一次 load table def 的操作,此时就会在原先最后一次 table_id 基础上 +1,做为新的 table def 的 id。
3 flush tables,之后对表的更新操作也会触发 table_id 的增长。
4 如果 table def cache 过小,就会出现频繁的换入换出,从而导致 table_id 增长比较快。
例子
root@rac2 [yangyi] show binlog events in mysql-bin.000002
+——————+—–+————-+———–+————-+—————————————+
| Log_name | Pos | Event_type | Server_id | End_log_pos | Info |
+——————+—–+————-+———–+————-+—————————————+
| mysql-bin.000002 | 4 | Format_desc | 2 | 106 | Server ver: 5.1.68-log, Binlog ver: 4 |
| mysql-bin.000002 | 106 | Query | 2 | 176 | BEGIN |
| mysql-bin.000002 | 176 | Table_map | 2 | 219 | table_id: 18 (yangyi.t1) |
| mysql-bin.000002 | 219 | Write_rows | 2 | 253 | table_id: 18 flags: STMT_END_F |
| mysql-bin.000002 | 253 | Xid | 2 | 280 | COMMIT /* xid=61 */ |
| mysql-bin.000002 | 280 | Query | 2 | 357 | use `yangyi`; flush tables |
| mysql-bin.000002 | 357 | Query | 2 | 427 | BEGIN |
| mysql-bin.000002 | 427 | Table_map | 2 | 470 | table_id: 19 (yangyi.t1) |
| mysql-bin.000002 | 470 | Write_rows | 2 | 504 | table_id: 19 flags: STMT_END_F |
| mysql-bin.000002 | 504 | Xid | 2 | 531 | COMMIT /* xid=65 */ |
+——————+—–+————-+———–+————-+—————————————+
10 rows in set (0.00 sec)
三 table_id 在主从复制过程中转变
每一个 dml 操作表的信息都被会记录 table_mapping 的 hash 数据结构中,hash 的 key 就是 ulong 型的 table_id,hash 的值就是 TABLE* 的数据结构 (包含了表的各种信息,包括数据库名,表名,字段数,字段类型等),通过 set_table() 方法来 hash, 通过 get_table()方法来根据 table_id 获得对应的表信息。
当主库的日志传递到备库时,每一个 log_event 都是通过 do_apply_event()方法来将 event 应用到本地数据库中。在 apply relay log 中的 event 时,do_apply_event()将 ulong 型的 m_table_id(binlog 记录的 table_id)赋值给 RPL_TABLE_LIST 结构中的 uint 型的 table_id。核心问题出现了: 如果 binlog 中的 table_id 的值大于 max(uint),在变量传递是,就会发生截断。
而 MySQL 内部使用 set_table(table_id)构造 hash,使用 get_table(m_table_id)从 hash 表中取值, 在两个阶段用到的 key 因为发生了数据截断, 所以必然也就不能取到预期的值。也就是说之前用 uint 型的 table_id 构建出来的 key-value 的 hash 对,用 ulong 型的 m_table_id 是无法查询到的。
四 风险与解决
从第二,三点我们知道当 table_id 过快增长, 会导致从库应用 binlog 无法解析到对应的表,造成数据不一致的情况。
解决方法:
1 加大 table cache 的大小。
2 重启主库使 table_id 归 0,缺点 成本比较高,出现此问题的时候,主备已经不一致,线上环境 不能完成切换。
3 修改 MySQL 源码, 将 RPL_TABLE_LIST 结构中的 uint 型的 table_id 修改为 ulong 型,一劳永逸。
“怎么理解 MySQL 中的 table_id”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注丸趣 TV 网站,丸趣 TV 小编将为大家输出更多高质量的实用文章!