何為更新丟失數據問題:假設數據庫中有一條數據,有兩個事物A,B,同時對這條數據操作。事物A,B同時讀到這條數據,事物A對這條數據進行修改並提交,然后事物B對這條數據修該改但晚於事物A提交。這種情況下事物B就會覆蓋掉事物A的更新,事物A的更新就會丟失。這種情況有時會引起比較嚴重的問題。例如重復轉賬問題,例如一個賬戶A有100元余額,要向賬戶B進行轉賬100元的操作,通常會分為兩步,首先讀出賬戶A的余額,判斷是否余額充足,然后進行轉賬扣款操作。如果同時有兩個事物a,b進行這個操作,他們同時讀到賬戶余額余額是100,於是他們都判斷余額充足,事物A首先進行轉賬扣款操作,並提交。這個時候賬戶B已經得到100元轉賬,賬戶A余額是0.然后事物b,也進行轉賬扣款操作,並提交。這個時候,賬戶B再次得到100元轉賬,賬戶A的余額被修改成0.也就是說賬戶A最終扣款100,賬戶B卻得到200元。票超賣問題也是類似的。
解決方案:1.在事物讀取賬戶余額的語句中加排他鎖,這樣兩個事物就無法同時讀取到相同的余額。這種處理方法就是強制事物串行處理。缺點是並發量不高,處理的慢。
2.用樂觀鎖,給數據加樂觀鎖。也就是加一個版本號version字段。事物讀取數據的同時,讀取到版本號,提交的時候把版本號加一,並加上版本號判斷。語句如下
update table set Num=0,Version=Version-1 where Version=讀到的版本號。這樣如果事物A提交成功,版本號加一,事物B后提交時就會修改失敗。可以根據修改影響的行數來判斷。這樣就可以避免更新丟失。可以根據業務進行重試。缺點並發量不能太高。
以上兩種解決方案都是數據庫層面的。
下面在以電商搶購,下單與扣庫存的業務場景提出如下兩種解決方案:
3.將數據放到redis 緩存中。用分布式鎖,強制同一時間只能有一個事務對數據操作,本質上也是將事務串行化操作。缺點並發量不高。
4.利用Redis increment 的原子操作(就是更新與讀取是原子性的)和redis的單線程特點(這兩個特點就可以保證不同的事務不會讀到相同余額的問題),保證庫存安全。 事先需要把庫存的數量等其他信息保存到Redis,並保證更新庫存的時候,更新Redis。 進來的時候 先 get 庫存數量是否充足,再執行 increment。以 increment > 0 為准。 檢查庫存 與 減少庫存 不是原子性的。 檢查庫存的時候技術庫存充足也不可下單;否則造成庫存不安全,原來類似 方法1. increment 是個原子操作,已這個為准。
redisService.increment(key, -req.getNum().longValue()) >= 0 說明庫存充足,可以下單。
redisService.increment(key, -req.getNum().longValue()) < 0 的時候 不能下單,次數庫存不足。並且需要 回加剛剛減去的庫存數量,否則會導致剛才減扣的數量 一直買不出去。數據庫與緩存的庫存不一致。
參考文章:https://blog.csdn.net/mifffy_java/article/details/95201752
次方法可以滿足 高並搶購等一些方案,真正減扣庫存和下單可以異步執行。
訂單時效問題,訂單取消等 為保證商家利益,同時把商品賣給有需要的人,訂單下單成功后,往往會有個有效時間。超過這個時間,訂單取消,庫存回滾。
訂單取消后,可利用MQ 回退庫存等。缺點是如果redis 服務停止,將無法下單。
