程序員不要好心辦了壞事

開發(fā)中我們看那些散發(fā)著濃烈的bad smell的代碼,總有一種要修理它的沖動!這當(dāng)然是好事,說明我們有能力識別不好的東西以及維持系統(tǒng)健康運行的意愿。但是,但是總是無處不在,我們好心有時候會辦出壞事來。下面這個真實的案例就是某同學(xué)覺得表的字符集設(shè)計的不合理,在一次需求開發(fā)中就把他改了,然而不幸的是由此導(dǎo)致了一個不小的線上事故,下面分享給大家

1.事故的導(dǎo)火線

你敢想?導(dǎo)致線上事故的是一個簡單的DDL 語句:

ALTER TABLE table_t CONVERT TO CHARACTER SET utf8mb4;

2.事故現(xiàn)場

由于業(yè)務(wù)系統(tǒng)響應(yīng)極慢,使用方反饋(早期系統(tǒng)沒有完善的告警機制),開發(fā)排查日志發(fā)現(xiàn)是sql查詢速度很慢,然后查詢慢日志監(jiān)控,看到了如下的壯觀場景:


慢sql監(jiān)控.png

3.事故原因分析

為什么會有這么多慢查詢呢??因為查詢語句的關(guān)聯(lián)字段的字符集不同,導(dǎo)致索引失效,sql執(zhí)行變成了全表掃描,進而導(dǎo)致數(shù)據(jù)庫實例所在機器的CPU 長時間100%,影響業(yè)務(wù)訪問。

4.事故線下重現(xiàn)

我們使用連接查詢時,兩個表的關(guān)聯(lián)字段都建有索引,但是如果兩個表的關(guān)聯(lián)字段的字符集不同,就會導(dǎo)致索引失效,不會走索引。執(zhí)行下面的建表語句:

CREATE TABLE `t1` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` VARCHAR(64) DEFAULT '',
`code` VARCHAR(16) DEFAULT '',
PRIMARY KEY (`id`),
KEY `idx_code` (`code`),
KEY `idx_name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=UTF8;

CREATE TABLE `t2` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` VARCHAR(64) DEFAULT '',
`code` VARCHAR(16) DEFAULT '',
PRIMARY KEY (`id`),
KEY `idx_code` (`code`),
KEY `idx_name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=UTF8MB4;

然后插入一些數(shù)據(jù):

INSERT INTO `t1` (`id`, `name`, `code`) VALUES (6, 'aa', '');
INSERT INTO `t1` (`id`, `name`, `code`) VALUES (7, 'bb', '');
INSERT INTO `t1` (`id`, `name`, `code`) VALUES (8, '0', '');
INSERT INTO `t1` (`id`, `name`, `code`) VALUES (9, '1', '');
INSERT INTO `t1` (`id`, `name`, `code`) VALUES (10, '2', '');
INSERT INTO `t1` (`id`, `name`, `code`) VALUES (11, '3', '');
INSERT INTO `t1` (`id`, `name`, `code`) VALUES (12, '4', '');
INSERT INTO `t1` (`id`, `name`, `code`) VALUES (13, '5', '');
INSERT INTO `t1` (`id`, `name`, `code`) VALUES (14, '6', '');
INSERT INTO `t1` (`id`, `name`, `code`) VALUES (15, '7', '');
INSERT INTO `t1` (`id`, `name`, `code`) VALUES (16, '8', '');
INSERT INTO `t1` (`id`, `name`, `code`) VALUES (17, '9', '');

INSERT INTO `t2` (`id`, `name`, `code`) VALUES (6, 'ff', '');
INSERT INTO `t2` (`id`, `name`, `code`) VALUES (7, 'hh', '');
INSERT INTO `t2` (`id`, `name`, `code`) VALUES (8, 'gg', '');

線上的sql形式如下:

select * from t2 left join t1 on t1.code = t2.code where t2.name = 'ff';

我們查看執(zhí)行計劃:

explain extended select * from t2 left join t1 on t1.code = t2.code where t2.name = 'ff';

從下圖的執(zhí)行計劃可以看到,查詢條件t2.name = 'ff'使用了索引,而條件t1.code = t2.code并沒有使用表t1的索引:


sql執(zhí)行計劃.png

為什么兩個字段的字符集不一樣就不走索引了呢?這個命令SHOW WARNINGS;會給你詳細的說明分析,這個命令和執(zhí)行計劃配合使用,簡直不能再香了。你一定要去使用!如下:

warning提示信息.png

message全量內(nèi)容如下:

/* select#1 */ select `test`.`t2`.`id` AS `id`,`test`.`t2`.`name` AS `name`,`test`.`t2`.`code` AS `code`,`test`.`t1`.`id` AS `id`,`test`.`t1`.`name` AS `name`,`test`.`t1`.`code` AS `code` from `test`.`t2` left join `test`.`t1` on((convert(`test`.`t1`.`code` using utf8mb4) = `test`.`t2`.`code`)) where (`test`.`t2`.`name` = 'ff')

這時候已經(jīng)非常清楚了,MySQL在關(guān)聯(lián)字段上進行了convert轉(zhuǎn)化,索引當(dāng)然就失效嘍!

5.事故解決

問題的解決也是簡單粗暴,DBA直接改回了原來的字符集:

ALTER TABLE t_test CONVERT TO CHARACTER SET utf8;

6.事故復(fù)盤

平時讓我們說索引失效的場景你可能會咔咔咔的說出不少,但是實際使用的時候卻時常會犯錯,實際上還是意識不強烈。不管怎么說,都要認(rèn)真對待自己寫下的每行代碼,包括任何要上線的資源,如初始化的數(shù)據(jù),腳本等。就拿這次事故來說,開發(fā)同學(xué)本意是覺得utf8字符集不嚴(yán)謹(jǐn),應(yīng)該使用utf8mb4,但實際上表中的code字段存儲的只是數(shù)字和字母組成的字符串,早期歷史原因被設(shè)計成utf8也無可厚非了。但是,我們作為后來接手著,任何改動就要小心了,避免跳坑里了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容