MySQL數據orderby時間排序bug

懶得看我廢話的同學直接看結論:
MySQL數據庫中datetimedatetime(n) 0<=n<=6 是有精度區別的,如果你默認datetime,那其實只是精確到了秒,如果此時基於這個字段排序,同一秒的請求排序很有可能不是你期望的結果。下面開始介紹這次踩坑的經歷:

最近爲公司做了一個簡單的用戶賬戶項目中臺,其中涉及了用戶積分,轉賬,提現等,由於第一次做缺乏經驗,再加上一旦涉及錢的項目肯定是要求正確率極高,所以踩了不少坑,這裏記錄其中一個關於MySQL數據排序的坑。

賬戶項目很重要的一點是要記清楚賬戶流水,這裏我們的設計是將流水錶和用戶賬戶表合二爲一,因爲本身項目是基於一個類似於邀請好友得金幣換現金的活動做的,一個用戶存在待解凍,可用,體現中,已使用四種金幣類型,這裏我們就設計成了一個用戶存在四種賬戶類型,設計一張流水錶記錄四種賬戶類型的每一筆流水,當新流水來的時候一般是通過userId和賬戶類型去查詢數據,只查最新的一條,數據中的總額就是用戶當前賬戶類型的最新數額,看似很簡單的一個東西實際做起來卻是特別多的坑。

賬戶首要考慮的就是數據的準確和安全,這就考驗了併發和鎖的設計,這個也踩了不少坑,後續打算單開一篇文章講;

這次踩的另外一個坑是我上面提到的“最新一條記錄”這個概念,當時在寫代碼的時候也沒多想,隨手寫了order by create_time desc,直到線上出了詭異的bug才明白這麼寫的危害。上線不久之後發現有人利用我們的漏洞薅羊毛,刷了大量金幣,我們技術團隊也是第一時間修復了漏洞,但是我在整理數據的時候發現了一個現象,有些薅羊毛用戶的金幣數似乎少了很多
例如:原本用戶有100金幣,假如邀請一個好友給10金幣,我發現在有的時候會有連續兩條流水,明明是兩次邀請,但是加完10金幣後的總金額都是110,看到這裏肯定第一反應就是鎖和併發出問題了唄,出現了線程不安全的情況導致同時讀取了舊數據,但是仔細檢查了代碼和測試之後發現應該不會出現線程安全的問題,然後就開始陷入了翻日誌和測試的代碼的死循環中,幾乎浪費了一下午時間,其實原因很簡單,就是在選擇所謂的“最新一條記錄”時使用了order by create_time,而create_time在表中是一個dateTime,也許你會奇怪,誒我也用過時間比較啊,沒問題啊。

注意,我設計的dateTime只是dateTime,而你們數據庫的表中dateTime正確做法應該是dateTime(n) 0<=n<=6,這樣規定了精確度之後才能真正通過時間比較,像我這種沒有設置精確度其實默認是到秒,這個你可以通過如下sql自己看一下結果:

select now();
select now(3);
select now(6);
select now(7); -- 報錯

由於我們的單位是一秒,雖然很短,但是對於併發系統來說已經是很長很長的時間了,這也就是爲什麼薅羊毛的用戶出了問題,因爲他們大多是利用接口漏洞瘋狂刷金幣,導致經常出現一秒內多條的情況,所以order by自然容易丟失數據,幸好及時發現而且大部分影響了非正常用戶,沒有造成更大的損失。

這裏也確實提醒了我,在使用數據庫字段的時候,不管是當作條件還是分組還是排序,都應該儘量使用穩妥或者有明確業務意義的字段,例如排序完全可以使用主鍵id排序,天然的自增字段,並且具有唯一性。

果然啊,知識學的再多,沒有經驗把他們串起來,還是差得遠啊。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章