2016年8月9號美好的七夕的早上,我精神抖擻地來到公司。一會之后,客服宅宅MM微信我,說一個VIP大店鋪訂單導出報表中一個訂單有重復行。於是,我趕緊開始查探問題所在。經過一天的反復仔細追查(當然還包括各種事項的打斷),終於發現這個問題的原因所在。。。
有個訂單主表 o,以及一個訂單商品表 i ; o 與 i 是一對多的關系:其中一個訂單 d_no 會對應多個商品 t_id,而一個商品 t_id 僅對應一個訂單 d_no. 那么問題在哪里呢? 有經驗的同學可能已經猜到是什么原因了,不過且讓我們一步步來看:
首先,肯定是復現問題。很幸運,在重復執行后,確實如商家所言,這個訂單的相應商品行都重復了一次。那么,怎么排查呢? 顯然要在應用程序的不同地方里打印這個訂單的信息,看看究竟是從哪里開始重復的,通過一步步向源頭追蹤,發現從獲取訂單號的最源頭的地方就有重復了。我開始以為是這個訂單本身有特殊的地方,因此讓商家只導出這個訂單,然而沒有重復;這說明很可能是這個訂單與多個訂單一起導出才導致的問題。我又懷疑是不是這個訂單的重復行有某個細節地方是不一樣的才重復,然而做對比后發現完全一樣;
現在似乎有點撲朔迷離了。於是,我覺得應該同時打印相應的SQL以及出現該訂單的信息。在重復運行多次、打印相關信息並仔細觀察之后,發現這個訂單在以下兩個查詢中分別出現了一次; 在第一個SQL結果的第一個,以及第二個SQL結果的最后一個。邊界出現問題了。
select o.d_no from o, i where o.d_no = i.d_no and `o`.`dianpu_id` = ? and `o`.`xiadan_time` >= ? and `o`.`xiadan_time` <= ? and `i`.`shangpin_id` = ? order by i.d_no desc limit 1400,50; select o.d_no from o, i where o.d_no = i.d_no and `o`.`dianpu_id` = ? and `o`.`xiadan_time` >= ? and `o`.`xiadan_time` <= ? and `i`.`shangpin_id` = ? order by i.d_no desc limit 1350,50;
我終於意識到: 很可能是 Join 分頁導致的問題。是不是代碼分頁有微妙的 Bug ? 看了下代碼,沒看出問題;疑問又來了: 為什么單單這個訂單會重復,其它的不重復呢? 為什么搜索頁面不重復,而導出報表會重復呢?按理來說,這個訂單號沒有特殊之處,那么別的訂單號完全也可能重復;並且搜索與導出共用的相同的接口邏輯;重復抽查了幾個 limit X, 50, 沒發現重復的其他訂單號; 於是,注意力還是轉回到這兩個SQL 。
究竟暗藏什么玄機? 我甚至懷疑與主鍵 id 值的缺漏有關,打印出 id 值后看不出什么特別的規律,很快否定了這個想法。 於是我直接在DBA界面系統打印出這兩個SQL的結果,看着重復的d_no排序輸出在界面上,突然靈光一閃,意識到了。。。
我執行了以下SQL,直接打印出這個查詢條件下的所有 d_no,查看重復訂單號的位置。
select o.d_no from o, i where o.d_no = i.d_no and `o`.`dianpu_id` = ? and `o`.`xiadan_time` >= ? and `o`.`xiadan_time` <= ? and `i`.`shangpin_id` = ? order by i.d_no;
你明白了么? 這個訂單號對應兩個商品,因此查出來這個訂單號會出現兩次,而這兩次正好出現在兩次查詢的邊界處。在 1399 的偏移量時,查出來一次,在 1400 的偏移量時,查出來一次; 因此,這個訂單號會查出兩次,而根據這個訂單號獲取其他信息后也會導出兩次,訂單導出是每個 limit X, 50 都會增量地導出到報表里,從而出現了最終報表中的重復行(在搜索接口中會有去重后返回給前端)。 其他的訂單號之所以沒有出現問題,是因為在臨界處 50*X,50*X-1 處的訂單號正好只對應一個商品,只會出現一次,因此避免了重復行。話說回來,其實導出報表訂單重復行的概率還是比較高的,只要在 50*X, 50*X-1 出現的訂單號是有對應多個商品的,就很可能在最終報表中有重復行。這個還是要看運氣滴~~
問題原因定位了,解決就簡單了。將 select o.d_no 改成 select distinct(o.d_no) 即可;當然,對應的 count 也要改成 count(distinct(o.d_no)) .
排查問題經驗總結:
1. 排查問題的重點是找到:位置和原因;
2. 日志非常重要,在關鍵路徑和關鍵狀態上打關鍵信息日志,好過十打的代碼分析和肯定應該Maybe。關鍵路徑會幫助你排除不出錯的地方,縮小問題范圍,關鍵狀態會減少需要做出的猜測,更肯定地排查問題; 關鍵信息或關鍵詞能讓人很快鎖定問題出現的代碼位置。
3. 根據經驗分析可能的原因,然后采取技術手段去找到位置和原因。 排查的手段通常是: 復現問題,重復執行,靜態分析,猜測原因,斷點運行;
4. 問題對象是否有特殊性?針對對象本身單獨進行探查;
5. 邊界處一定要重視,常常是問題產生的多發地;
6. 先到此為止,睡覺!
ps: 偶滴七夕啊。。。雖然沒有美女作伴,也不能這么開玩笑啊。。。
