最近開發中遇到的一個 MySQL 主從延遲的坑,記錄並總結,避免再次犯同樣的錯誤。
情景
一個活動資訊需要審批,審批之後才能生效。因為之後活動要編輯,編輯後也可能觸發審批,審批中展示的是編輯前的活動內容,考慮到欄位比較多,也要儲存審批活動的內容,因此設計採用了一張臨時表,審批中的活動寫進審批表(activity_tmp),審批通過之後才把真正的活動內容寫進活動表(activity)。表的簡要設計如下,這裡將活動內容欄位合併為content展示:
1 2 3 4 5 6 7 8 |
activity_tmp() id status // 審批狀態 content // 審批階段提交的活動內容 activity id content // 審批通過後真正展示的活動內容 |
遇到的問題
當時是有編輯觸發審批的情況,發現審批通過之後活動內容是空的,於是開始追查問題的原因。這裡說一句,當程式出問題的時候,95%都是程式碼的問題,先不要去懷疑環境出問題。好好的查日誌,然後看看你的程式碼吧。
追查問題回溯
1、查activity_tmp表,發現當時提交審批的活動內容是正常的,而且狀態也更新為審批通過了,懷疑是寫入activity表失敗 2、查activity表,發現審批後的內容確實沒有寫入,懷疑是程式碼問題 3、檢視程式碼,程式碼邏輯沒看出問題,懷疑資料庫操作失敗,檢視日誌 4、日誌顯示,有一句insert語句的活動內容為空,活動內容來自上一個mysql執行的是select語句,把該select語句拿出來放到線上的備庫查詢,發現活動內容是存在的。執行時查詢為空,執行完畢後查詢時內容存在,初步懷疑是主從延遲問題。 5、報錯只是部分失敗,確定是主從延遲的問題。
當時的問題程式碼
1 2 3 4 |
$intStatus = $arrInput[‘status’]; $this->objActTmp->updateInfoByAId($intActId, $intStatus); // 更新後,馬上查 $arrActContent = $this->objActTmp->getActByStatus($intStatus); |
這就是主從延遲出現的地方,update後,馬上get,這是主從複製架構上開發的一個大忌。
解決方案
這類問題的解決方案有兩種:
- 修改程式碼邏輯
- 修改系統架構
對於修改程式碼邏輯,鄙人有兩點見解:
- 如果第二步獲取的資料不需要第一步更新的status欄位,那就先讀,然後再更新
- 如果第二步獲取的資料需要依賴第一步的status欄位,那就在讀出來的時候先判斷是否為空,如果是空的,報錯,下一次重試。
總結
其實之前也聽到過這樣的例子,但是由於沒有親身經歷,所以只保留了一種理論上的記憶,實際上印象不深,經歷了這麼一次踩坑後,印象特別深刻,現在看到別人寫這樣的程式碼也能馬上發現並指出。還是自己親身去踩坑印象最深。
日誌很重要,詳細的日誌更重要。日誌要記錄有用的資訊,方便追查問題的時候去追溯問題的本質原因。我覺得日誌就應該儘量做成飛機中的黑匣子,幫助我們儲存“事故“發生時的所有相關資訊。
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!
任選一種支付方式