1. 程式人生 > >記一次遇到由於重復提交導致的問題

記一次遇到由於重復提交導致的問題

就是 按鈕 說了 ... 修改密碼 into 修改 方案 batis

  • 需求
  • 問題
  • 解決和復盤

這是新手期間第一個上線功能搞出的bug,同時也明白了一個道理:1. 線上環境總是復雜的,不可預知的,一定要做好各種準備; 2. 重要的功能要做放重復提交;3. 基礎要打打牢。

需求

需求其實很簡單,就是一個修改密碼的入口,用戶輸入提交表單後,如果之前沒有設置過密碼,則設置,否則修改。然後驗證密碼的邏輯就不用說了,查出表裏的密碼,然後對比。
項目是ibatis+oralce,之前DAO的邏輯如下

...
if (isNotExists()){
    insertPassword;
} else {
    updatePassword;
}

isNotExists()

方法就是select看是否存在記錄。

public Map queryPassword(Map param) throws DAOException {
    return sqlMapClientTemplate.queryForObject(param);
}

問題

某一天中午突然來了告警日誌,發現在queryPassword這個方法這裏拋出了too many results異常,很顯然,本應該每個用戶只存在一條記錄的,查出了多條。讓運維同事查出數據,果然有一個用戶,存在3條記錄。查看原代碼邏輯,那很顯然是在判斷是否存在記錄這裏同時來了三個請求,然後三個線程都發現該用戶對應的記錄是空的,於是同時插入了三條記錄。(後來通過電話回訪用戶,說當時手機頁面卡了,於是瞎點一通,可能頁面卡了,等反應後一下子發出去多條請求。)

解決和復盤

  • 首先線上問題緊急的解決方案就是刪掉記錄就OK了。
  • 然後這種if ... then...的用法是非常危險的,一來一回,在並發情況下就出現問題了。當時想著改個密碼對同一個用戶哪兒來的多並發,先就這樣幹吧,事實證明線上環境的復雜是永遠超出自己預知的。對於這種場景,最好使用oralce提供的merge方法,把事務處理交給oracle,出錯的概率更小一些。

    merge into pwd_table t
    using dual
    on (t.id = #id#)
    when matched then
        update pwd_table t set t.pwd = #pwd#
        where
    t.id = #id# when not matched then insert into pwd_table t values(#id#, #pwd#)
  • 做防重復提交限制,考慮到用戶的群體,最簡單的是在用戶提交請求得到響應之前,按鈕置為diabled狀態
  • 基礎一定要打好,當時就是對merge了解不深沒有使用,要多學習PL/SQL的東西

記一次遇到由於重復提交導致的問題