一個詭異Bug,我差點背上P0事故!
圖片來自 Pexels
通過日志發(fā)現(xiàn),從凌晨 5:26 分開始到 5:56 任務(wù)執(zhí)行了三次,三次都因為 SQL 查詢超時而執(zhí)行失敗,而詭異的是,任務(wù)到凌晨 6:00 多就執(zhí)行成功了。
每天都是凌晨五點多失敗,凌晨六點執(zhí)行成功。
點開異常日志一看是這樣的:
總結(jié)來說就是 MySQL 查詢超時。像這種穩(wěn)定復(fù)現(xiàn)的 Bug,我原以為只需三分鐘能定位,沒有想到卻耗費了我半天的時間。
01排查之路
①Explain
看到超時 SQL,大多數(shù)人第一反應(yīng)就是這個 SQL 沒有走索引,我也不例外,我當(dāng)時的第一反應(yīng)就是這條 SQL 沒有走索引。
于是,我將日志里面的 SQL 復(fù)制了出來,脫敏處理一下大概是這樣的一條 SQL:
select * from table_a where status_updated_at >= ? and status_updated_at < ?
SQL 里面有兩個日期參數(shù),這兩個起始日期是某種商品的可交易時間區(qū)間,相隔三到五天,我取了 17 天的時間間隔的保守值,Explain 了一下這條 SQL。
從圖上可以看到這條 SQL 的執(zhí)行還是走了索引的。走的是根據(jù) status_updated_at 字段建立的索引。執(zhí)行了一下也只耗時了 135 毫秒。
根據(jù) Explain 結(jié)果,我當(dāng)時的推斷是:這條 SQL 肯定走了索引,如果沒有走索引,那六點多鐘的查詢肯定也會超時,因為這個表的數(shù)據(jù)是千萬級別的。
為了驗證這一推斷,我找 DBA 幫我導(dǎo)出了一下凌晨 5 點到早上 7 點關(guān)于這個表的慢 SQL,DBA 告訴我那個時間段沒有關(guān)于這個表的慢 SQL。
這也進一步驗證了我說推斷:這條 SQL 走了索引,只是在五點多的時候因為一些神秘原因?qū)е铝顺瑫r。
接下來,需要做的就是找出這個神秘的原因。按照以往的經(jīng)驗,我認為有這幾點因素會導(dǎo)致查詢超時:
- MySQL 資源競爭
- 數(shù)據(jù)庫備份
- 網(wǎng)絡(luò)
②MySQL 資源競爭
首先,我通過監(jiān)控系統(tǒng)查看了那段時間 MySQL 的運行情況,連接數(shù)和 CPU 負載等指標(biāo)都非常正常。所以,因為 MySQL 負載導(dǎo)致超時首先就可以被排除。
那會不會是其他業(yè)務(wù)操作這個表影響的呢?首先,我們線上數(shù)據(jù)庫事務(wù)隔離級別設(shè)置的是 RR(可重復(fù)讀),因為 MVCC 的存在,簡單的修改肯定是不會影響查詢至超時的。
要想影響唯一的可能性就是別的業(yè)務(wù)在 update 這個表數(shù)據(jù)的時候,更新條件沒有走索引,導(dǎo)致行鎖升級成表鎖,并且,這個操作要剛好在凌晨 5 點多執(zhí)行,且持續(xù)了半個小時。
這個條件非??量?,我檢查了相關(guān)的代碼,問了相關(guān)負責(zé)人,并沒有這種情況,所有的更新都是根據(jù) Id 主鍵更新的。
關(guān)鍵是,如果更新 SQL 的更新條件沒有走索引,肯定會是一個慢 SQL 的,那么,我們在慢 SQL 日志文件里面就能找到它,實際上并沒有。
③備份
是不是因為凌晨 5 點多,數(shù)據(jù)庫在備份的原因呢?
- 首先備份鎖表不會鎖這么久,這個任務(wù)是前前后后半個小時都執(zhí)行失敗了。
- 其次我們是備份的從庫,并不是備份的主庫。
- 最后,我們的備份任務(wù)都不是凌晨五點執(zhí)行的。
所以,因為備份導(dǎo)致超時可以排除了。
④網(wǎng)絡(luò)
是不是網(wǎng)絡(luò)波動的原因呢?我找運維同學(xué)幫忙看了一下執(zhí)行任務(wù)的那臺機器那段時間的網(wǎng)絡(luò)情況,非常平緩沒有絲毫問題,機房也沒有出現(xiàn)什么網(wǎng)絡(luò)抖動的情況。
再者,如果是網(wǎng)絡(luò)問題,肯定會影響其他任務(wù)和業(yè)務(wù)的,事實上,從監(jiān)控系統(tǒng)中查看其他業(yè)務(wù)并沒有什么異常。
所以,因為網(wǎng)絡(luò)波動導(dǎo)致超時也可以排除了。
02轉(zhuǎn)機
我先后排除了索引、網(wǎng)絡(luò)、備份、業(yè)務(wù)競爭 MySQL 資源等因素,在腦海里模擬了 N 種情況,腦補了一條 SQL 整個執(zhí)行過程,想不到會有什么其他原因了。
這個事情變得詭異了起來,DBA 勸我暫時放棄,建議我把任務(wù)執(zhí)行時間延后,加一些監(jiān)控日志再觀察觀察。畢竟,又不是不能用。
放棄是不可能放棄的,我是一個鐵頭娃,遇到 Bug 不解決睡不著覺。
理清思路,從頭來過,我向 DBA 要了一份線上五點到六點的慢 SQL 的文件,自己重新找了一遍,還是沒有找到這個表相關(guān)的慢 SQL。
在我突然要放棄的時候,我突然發(fā)現(xiàn) SQL 日志記錄里面的時區(qū)都是標(biāo)準(zhǔn)時區(qū)的,而我那個任務(wù)執(zhí)行的時候是北京時間,要知道標(biāo)準(zhǔn)時區(qū)和北京時區(qū)是差了 8 個小時的!
好家伙!我都要想到量子力學(xué)了,結(jié)果發(fā)現(xiàn)時區(qū)沒對上?會不會是 DBA 找慢 SQL 的時候時間找錯了啊?
我將這個“重大發(fā)現(xiàn)”告訴了 DBA,DBA 幫我重新跑一份慢 SQL,好家伙,出現(xiàn)了我想要那個表的慢 SQL。
從日志上面可以看到,查詢的日期區(qū)間從 2020 年 9 月到 2021 年 4 月,時間跨度 7 個月。
MySQL 成本計算的時候認為區(qū)間太大,走索引還不如直接掃描全表,最終沒有走索引掃描了 1800W 條數(shù)據(jù)。
說好的時間區(qū)間最多七天呢?怎么變成了七個月?
趕緊定位代碼,定位發(fā)現(xiàn)底層在取時間區(qū)間時,調(diào)了一個 RPC 接口,這個接口預(yù)期返回的時間區(qū)間只有幾天,結(jié)果返回了七個月的時間區(qū)間。這段邏輯是 18 年上線的。
于是聯(lián)系提供這個 RPC 接口的相關(guān)人員,通過查找驗證確定這是底層數(shù)據(jù)的問題,應(yīng)該返回幾天結(jié)果返回了幾個月。
最后修復(fù)了相關(guān)數(shù)據(jù),增加了相應(yīng)的校驗和監(jiān)控,重新發(fā)布系統(tǒng),這個存在了兩年的 Bug 也就得以解決了。
這個故事到這里也就結(jié)束了。再回顧一下,還有幾個問題需要回答一下:
①不走索引,那為什么六點多執(zhí)行就沒有超時呢?
原因就是六點基本上沒有業(yè)務(wù)在調(diào)用 MySQL,那個時候的 MySQL 的資源是非常充足的,加上 MySQL 的機器也配置非常的高,所以這條 SQL 硬生生跑成功了。聽起來有點離譜,但確實是這樣的。
②為什么這個 Bug 在線上這么久了,現(xiàn)在才發(fā)現(xiàn)?
這個時間區(qū)間底層數(shù)據(jù)用的不多,目前只發(fā)現(xiàn)只有這個超時 SQL 任務(wù)在調(diào)用。
原來業(yè)務(wù)量沒有這么大,加上機器配置高,掃描整個表也花不了多久時間。凌晨五六點執(zhí)行,沒有對線上的服務(wù)造成影響。
任務(wù)失敗是很正常的,因為還依賴一些其他數(shù)據(jù),其他數(shù)據(jù)提供的時間不確定,所以任務(wù)會一直跑直到成功。
03總結(jié)
復(fù)盤一下整個過程,對于這個查詢超時 SQL 問題的排查,我從索引、網(wǎng)絡(luò)、備份、業(yè)務(wù)競爭 MySQL 資源等方面一一分析,卻忽略了最重要的因素——執(zhí)行的到底是哪一條 SQL。
我想當(dāng)然的認為執(zhí)行的 SQL 就是我想象中的那樣并對此深信不疑,后面的努力也就成了徒勞。
這本是一個簡單的問題,我卻把他復(fù)雜化了,這是不應(yīng)該的。
這是一個典型的例子,業(yè)務(wù)量不大的時候埋下的坑,業(yè)務(wù)發(fā)展迅速的時候就暴露了,萬幸的是,沒有影響到核心交易系統(tǒng),如果是核心交易系統(tǒng)的話,可能就會導(dǎo)致一次 P0 的事故。
雖然這個代碼不是我寫的,但我從中得到的教訓(xùn)就是「對線上環(huán)境要有敬畏之心,對依賴數(shù)據(jù)要有懷疑之心,對問題排查要有客觀之心」。
線上的環(huán)境極其復(fù)雜,有著各自版本遷移和業(yè)務(wù)變更遺留下來的數(shù)據(jù),這些情況開發(fā)人員是無法全部考慮到的。
測試也很難覆蓋測試,帶著主觀的想法去寫代碼很容易導(dǎo)致 Bug,有些 Bug 在業(yè)務(wù)量還不大的時候不容易引起重視,但隨著業(yè)務(wù)的發(fā)展,這些欠下的債終究要還。
你可以保證你寫的邏輯沒有問題,但是你不能保證服務(wù)上游提供的數(shù)據(jù)都符合預(yù)期。
多想一下如果上游數(shù)據(jù)異常,自己寫的服務(wù)會不會出問題,多加一些數(shù)據(jù)校驗和報警會省去很多不必要的麻煩。
排查問題的時候,一定要客觀,不要帶著主觀感受相當(dāng)然的認為是這樣。本來就是因為主觀操作而導(dǎo)致的 Bug,你還想當(dāng)然的代入去查找問題,這當(dāng)然會加大排查問題的難度。
作者:CoderW
編輯:陶家龍
出處:轉(zhuǎn)載自公眾號CoderW(ID:MHXJ_0810)