天天看點

【踩坑記錄】記一次MySQL主從複制延遲的坑

最近開發中遇到的一個MySQL主從延遲的坑,記錄并總結,避免再次犯同樣的錯誤。

情景

一個活動資訊需要審批,審批之後才能生效。因為之後活動要編輯,編輯後也可能觸發審批,審批中展示的是編輯前的活動内容,考慮到字段比較多,也要儲存審批活動的内容,是以設計采用了一張臨時表,審批中的活動寫進審批表(activity_tmp),審批通過之後才把真正的活動内容寫進活動表(activity)。表的簡要設計如下,這裡将活動内容字段合并為content展示:

activity_tmp()
id
status // 審批狀态    
content //  審批階段送出的活動内容

activity
id
content // 審批通過後真正展示的活動内容
           

遇到的問題

當時是有編輯觸發審批的情況,發現審批通過之後活動内容是空的,于是開始追查問題的原因。這裡說一句,當程式出問題的時候,95%都是代碼的問題,先不要去懷疑環境出問題。好好的查日志,然後看看你的代碼吧。

追查問題回溯

1、查activity_tmp表,發現當時送出審批的活動内容是正常的,而且狀态也更新為審批通過了,懷疑是寫入activity表失敗

2、查activity表,發現審批後的内容确實沒有寫入,懷疑是代碼問題

3、檢視代碼,代碼邏輯沒看出問題,懷疑資料庫操作失敗,檢視日志

4、日志顯示,有一句insert語句的活動内容為空,活動内容來自上一個mysql執行的是select語句,把該select語句拿出來放到線上的備庫查詢,發現活動内容是存在的。運作時查詢為空,執行完畢後查詢時内容存在,初步懷疑是主從延遲問題。

5、報錯隻是部分失敗,确定是主從延遲的問題。

當時的問題代碼

$intStatus = $arrInput[‘status’];
$this->objActTmp->updateInfoByAId($intActId, $intStatus);
// 更新後,馬上查
$arrActContent = $this->objActTmp->getActByStatus($intStatus);
           

這就是主從延遲出現的地方,update後,馬上get,這是主從複制架構上開發的一個大忌。

解決方案

這類問題的解決方案有兩種:

  • 修改代碼邏輯
  • 修改系統架構

對于修改代碼邏輯,鄙人有兩點見解:

  • 如果第二步擷取的資料不需要第一步更新的status字段,那就先讀,然後再更新
  • 如果第二步擷取的資料需要依賴第一步的status字段,那就在讀出來的時候先判斷是否為空,如果是空的,報錯,下一次重試。

總結

其實之前也聽到過這樣的例子,但是由于沒有親身經曆,是以隻保留了一種理論上的記憶,實際上印象不深,經曆了這麼一次踩坑後,印象特别深刻,現在看到别人寫這樣的代碼也能馬上發現并指出。還是自己親身去踩坑印象最深。

日志很重要,詳細的日志更重要。日志要記錄有用的資訊,友善追查問題的時候去追溯問題的本質原因。我覺得日志就應該盡量做成飛機中的黑匣子,幫助我們儲存“事故“發生時的所有相關資訊。

之後的學習裡,主從延遲的機制和原理也值得去研究一番。

繼續閱讀