背景:
最近,線上Row Based Replication(下稱RBR)環境中遇到了一個Bug。這個bug簡單的描述就是:RBR對於DML需要通過table-map的event來標注每一個有更新的表。
而當一個DML同時操作多個表,且其中2個表的mapid相同時(通常為0),會導致slave執行這個event時crash,並重啟mysqld實例
可見這個bug的毀滅性極大。
那么table-map-id 究竟從何而來?有什么辦法知道每個表table-map-id,從而進行一些必要的監控呢?
下文將用幾個例子來進行分析說明。
1. table-map-id 和 Innodb的table-id是否是同一個概念?
其實這個問題的答案是顯而易見的。因為並非Innodb的表才支持RBR,如果這個問題答案為“是”,那么非innodb的表在RBR中的table-map-id從何而來呢?又怎么保證和innodb的map-id不重復呢?
所以,顯然table-map-id和Innodb數據字典中的table-id是完全不同的兩個概念。
即便如此,下面還是用一個實例進行驗證
create table map_id_test (ID int primary key); insert into map_id_test values (1); show binlog events in 'log-prefix.000025';
輸出結果:
Log_name Pos Event_type Server_id End_log_pos Info log-prefix.000025 2156 Query 15757 2224 BEGIN log-prefix.000025 2224 Table_map 15757 2274 table_id: 88 (test.map_id_test) log-prefix.000025 2274 Write_rows 15757 2308 table_id: 88 flags: STMT_END_F log-prefix.000025 2308 Xid 15757 2335 COMMIT /* xid=346 */
查看Innodb的table-id:
select TABLE_ID from INNODB_SYS_TABLESTATS where `SCHEMA`='test' and NAME='map_id_test';
得到TABLE_ID = 170
2. table-map-id是否和物理文件有綁定關系
雖然table-map-id和Innodb的table-id是完全不同的概念。而我們知道Innodb中的table-id和物理文件有綁定關系,即rename table的操作不會改變dict-table中的table-id。
那么binlog中的table-map-id是不是有可能借鑒了這種實現方式,也有這個特性呢?
下面是具體測試過程
set global binlog_format='row'; create table map_id_test1 (ID int primary key); create table map_id_test2 (ID int primary key); insert into map_id_test1 values (1); insert into map_id_test2 values (1); show binlog events in 'log-prefix.000025';
輸出結果如下:此時table1 對應table_id:83 , tabl2 對應table_id:84
Log_name Pos Event_type Server_id End_log_pos Info log-prefix.000025 1157 Query 15757 1225 BEGIN log-prefix.000025 1225 Table_map 15757 1276 table_id: 83 (test.map_id_test1) log-prefix.000025 1276 Write_rows 15757 1310 table_id: 83 flags: STMT_END_F log-prefix.000025 1310 Xid 15757 1337 COMMIT /* xid=327 */ log-prefix.000025 1337 Query 15757 1405 BEGIN log-prefix.000025 1405 Table_map 15757 1456 table_id: 84 (test.map_id_test2) log-prefix.000025 1456 Write_rows 15757 1490 table_id: 84 flags: STMT_END_F log-prefix.000025 1490 Xid 15757 1517 COMMIT /* xid=330 */
執行rename table,交換table1和table2
rename table map_id_test1 to map_id_test1_bak,map_id_test2 to map_id_test1, map_id_test1_bak to map_id_test2;
查看binlog:此時table1 對應table_id:86 , tabl2 對應table_id:87。
Log_name Pos Event_type Server_id End_log_pos Info log-prefix.000025 1688 Query 15757 1756 BEGIN log-prefix.000025 1756 Table_map 15757 1807 table_id: 86 (test.map_id_test1) log-prefix.000025 1807 Write_rows 15757 1841 table_id: 86 flags: STMT_END_F log-prefix.000025 1841 Xid 15757 1868 COMMIT /* xid=334 */ log-prefix.000025 1868 Query 15757 1936 BEGIN log-prefix.000025 1936 Table_map 15757 1987 table_id: 87 (test.map_id_test2) log-prefix.000025 1987 Write_rows 15757 2021 table_id: 87 flags: STMT_END_F log-prefix.000025 2021 Xid 15757 2048 COMMIT /* xid=335 */
從實驗可以得出結論,RBR中的table_id 不僅和物理文件沒有綁定關系,在MySQL實例的運行過程中也不是靜態不變的。
因此,大膽猜測,table_id 和file handler有關系。下面的測試將進行驗證。
3. table_id 和file handler是否有直接聯系?
insert into map_id_test1 values (3); flush tables; insert into map_id_test1 values (4); show binlog events in 'log-prefix.000025';
執行結果: 從結果可以看出,flush table導致了,file handler的重新打開。同時也使table-map-id 發生了變化,且線性遞增。
Log_name Pos Event_type Server_id End_log_pos Info log-prefix.000025 2424 Query 15757 2492 BEGIN log-prefix.000025 2492 Table_map 15757 2543 table_id: 89 (test.map_id_test1) log-prefix.000025 2543 Write_rows 15757 2577 table_id: 89 flags: STMT_END_F log-prefix.000025 2577 Xid 15757 2604 COMMIT /* xid=383 */ log-prefix.000025 2604 Query 15757 2679 use `test`; flush tables log-prefix.000025 2679 Query 15757 2747 BEGIN log-prefix.000025 2747 Table_map 15757 2798 table_id: 90 (test.map_id_test1) log-prefix.000025 2798 Write_rows 15757 2832 table_id: 90 flags: STMT_END_F log-prefix.000025 2832 Xid 15757 2859 COMMIT /* xid=385 */
4 代碼分析
出現table_map_id = 0 的主要原因在於下列代碼標紅部分 (sql_base.cc: line 3079)
alter table,且不涉及數據更新時,新表的table_map_id 使用了原表已經關閉的table_map_id (此時值為 ~0UL) 。
mysql表示在5.1.53后修復了這個bug
/* This list copies variables set by open_table */ tmp.tablenr= table->tablenr; tmp.used_fields= table->used_fields; tmp.const_table= table->const_table; tmp.null_row= table->null_row; tmp.maybe_null= table->maybe_null; tmp.status= table->status; tmp.s->table_map_id= table->s->table_map_id; /* Get state */ tmp.in_use= thd; tmp.reginfo.lock_type=table->reginfo.lock_type; tmp.grant= table->grant;
結論:
1. RBR中的Table_ID 和Innodb中的table_id 沒有關系,且和物理文件沒有對應關系。
2. Flush Table 可以重置RBR中的Table_ID ,如果有表遇到了map_id=0 的情況,可以使用這個方法嘗試解決問題。
3. 雖然和File Handler 有關,但是和 /proc/$PID/fd/ 中的fd數值沒有直接聯系