MySQL MyISAM引擎是什么?有什么致命缺陷?為何現(xiàn)在都不使用了?
上一篇《MySQL:MyISAM 鎖表致千萬損失!穿越工程師如何逆天改命》,我發(fā)現(xiàn)自己穿越到了 過去,這個年代的 MySQL 居然還在用 MyISAM……次日上午,技術(shù)部緊急會議
"林工,你說要換引擎就換?"
首席 DBA 老張拍案而起,"這系統(tǒng)跑了三年都沒事,你才來三天就搞事情?"
林淵默然調(diào)出昨晚的監(jiān)控數(shù)據(jù):
# 昨夜事故報告
Lock_time_avg: 12.7s # 表鎖平均等待時間
Table_locks_immediate=2345
Table_locks_waited=8765 # 鎖等待率高達(dá)78%
"各位請看,"林淵點擊投影,"這不是故障,而是架構(gòu)級癌癥。"
連接池危機
詭異現(xiàn)象:
SHOW PROCESSLIST;
+-----+------+-----------+------+---------+------+-------+------------------+
| Id | User | Host | db | Command | Time | State | Info |
+-----+------+-----------+------+---------+------+-------+------------------+
| 101 | app | 10.0.0.5 | prod | Sleep | 632 | | NULL |
| 102 | app | 10.0.0.5 | prod | Sleep | 587 | | NULL |
| 103 | app | 10.0.0.5 | prod | Sleep | 524 | | NULL |
(超過 300 個僵尸連接,消耗 1GB 內(nèi)存)
技術(shù)解析:
圖片
- 線程泄漏原理: MySQL 4.0 采用"每連接每線程"模型,線程執(zhí)行完不會銷毀而是進(jìn)入
thread_cache
。 - 但當(dāng)
wait_timeout
設(shè)置過大時(默認(rèn) 8 小時),大量空閑線程堆積。
林淵的解法:
// 修改mysqld.cc的線程管理邏輯
void handle_one_connection(THD *thd) {
while (!abort_loop) {
if (thd->net.vio->read_packet() == 0) { // 無數(shù)據(jù)時主動釋放
thread_scheduler.end_thread(thd, true);
break;
}
do_command(thd);
}
}
操作結(jié)果: 內(nèi)存占用從 3.2GB 降至 1.8GB,QPS 提升 40%。
SQL 執(zhí)行過程
驚魂時刻:當(dāng)林淵試圖優(yōu)化慢查詢時,系統(tǒng)突然報錯:
ERROR 1064 (42000): You have an error in your SQL syntax...
——用戶輸入的SELECT * FORM orders
竟然未被攔截!
解剖流程:
圖片
關(guān)鍵發(fā)現(xiàn):
- 查詢緩存陷阱:
query_cache_type=ON
導(dǎo)致頻繁緩存失效(命中率僅 12%) - 解析器漏洞:未啟用嚴(yán)格模式(
sql_mode
未設(shè)置)允許錯誤語法通過 - 優(yōu)化器缺陷:缺乏直方圖統(tǒng)計,錯誤選擇全表掃描
林淵的急救包:
SET GLOBAL query_cache_size=0; -- 關(guān)閉毒藥級查詢緩存
SET GLOBAL sql_mode='STRICT_TRANS_TABLES'; -- 啟用嚴(yán)格模式
ANALYZE TABLE orders; -- 手動更新統(tǒng)計信息
變更存儲引擎
驚險時刻:當(dāng)林淵嘗試在線更換存儲引擎時
ALTER TABLE orders ENGINE=InnoDB;
系統(tǒng)突然僵死!SHOW PROCESSLIST
顯示:
| 145 | system user | NULL | NULL | alter table | 89 | copy to tmp table |
引擎切換原理:
圖片
林淵的破局操作:
- 使用
pt-online-schema-change
工具在線變更(提前 20 年發(fā)明) - 分階段遷移數(shù)據(jù):
# 步驟1:創(chuàng)建影子表
CREATE TABLE _orders_new LIKE orders ENGINE=InnoDB;
# 步驟2:分批拷貝(每次10萬條)
INSERT INTO _orders_new SELECT * FROM orders WHERE id BETWEEN ? AND ?;
# 步驟3:原子切換(0.01秒鎖定)
RENAME TABLE orders TO _orders_old, _orders_new TO orders;
引擎插件的秘密
林淵在ha_myisam.cc
中發(fā)現(xiàn)關(guān)鍵結(jié)構(gòu):
struct st_mysql_storage_engine myisam_storage_engine = {
"MyISAM",
"MySQL AB",
"Default engine with fast read speed",
{ /* 函數(shù)指針表 */
myisam_create_handler,
myisam_hton_commit,
NULL // 事務(wù)相關(guān)為空
}
};
"原來 MyISAM 的事務(wù)支持是先天殘疾..."他若有所思。
下節(jié)預(yù)告:
"當(dāng)我啟動 InnoDB 引擎時,服務(wù)器內(nèi)存突然耗盡..." —— 林淵如何用 Buffer Pool 優(yōu)化化解內(nèi)存危機?
且看下一章節(jié)《InnoDB 架構(gòu)設(shè)計:行級鎖原理、預(yù)寫日志(WAL)、Change Buffer》!