詳見:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt395
緣起:在高並發的分布式環境下,對於數據的查詢與修改容易引發一致性問題,本文將分享一種非常簡單但有效的優化方法。
一、業務場景
業務場景為,購買商品的過程要對余額進行查詢與修改,大致的業務流程如下:
(1)從數據庫查詢用戶現有余額 SELECT money FROM t_yue WHERE uid=$uid,不妨設查詢出來的$old_money=100元
(2)業務層實施業務邏輯,比如購買一個80元的商品,並且打九折
if($old_money> 80*0.9) $new_money=$old_money-80*0.9=28
(3)將數據庫中的余額進行修改 UPDAtE t_yue SET money=$new_money WHERE uid=$uid
在並發量低的情況下,這個流程沒有任何問題,原有金額100元,購買了80元的九折商品(72元),剩余28元。
二、潛在的問題
在分布式環境中,如果並發量很大,這種“查詢+修改”的業務很容易出現數據不一致。極限情況下,可能出現這樣的異常流程:
(1)業務1和業務2同時查詢余額,是100元
(2)業務1和業務2進行邏輯計算,算出各自業務的余額,假設業務1算出的余額是28元,業務2算出的余額是38元
(3)業務1對數據庫中的余額先進行修改,設置成28元。
業務2對數據庫中的余額后進行修改,設置成38元。
此時異常出現了,原有金額100元,業務1扣除了72元,業務2扣除了62元,最后剩余38元。
三、問題原因
高並發環境下,對同一個數據的並發讀(兩邊都讀出余額是100)與並發寫(一個寫回28,一個寫回38)導致的數據一致性問題。
四、原因分析
業務1的寫回:原有金額100,這是一個初始狀態,寫回金額28,理論上只有在原有金額為100的時候才允許寫回成功,這一步沒問題。
業務2的寫回:的原有金額10038100的時候才允許寫回成功,可實際上,這個時候數據庫中的金額已經變為28了,這一步的寫操作不應該成功。
五、簡易解決方案
在set寫回的時候,加上初始狀態的條件compare,只有初始狀態不變時,才允許set寫回成功,這正是大家常說的“Compare And Set”(CAS),是一種常見的降低讀寫鎖沖突,保證數據一致性的方法。
六、業務的升級
業務線使用CAS解決高並發時數據一致性問題,只需要在進行set操作時,compare一下初始值,如果初始值變換,不允許set成功。
對於上文中的業務場景,只需要將“UPDAtEt_yue SET money=$new_money WHERE uid=$uid”升級為
UPDAtE t_yue SETmoney=$new_money WHERE uid=$uid AND money=$old_money”即可。
並發操作發生時:
業務1執行 => UPDAtE t_yue SET money=28 WHERE uid=$uid AND money=100
業務2執行SET money=38 WHERE uid=$uid
【這兩個操作同時進行時,只能有一個執行成功】。
七、怎么判斷哪個執行成功,哪個執行失敗
set操作,其實無所謂成功或者失敗,業務能通過affect rows得知哪個修改沒有成功:
執行成功的業務,為1
執行失敗的業務,為0
八、總結
高並發“查詢並修改”的場景,可以用CAS(Compare and Set)的方式解決數據一致性問題。對應到業務,即在set的時候,加上初始條件的比對。