併發查詢 轉 併發扣款,如何保證資料的一致性?

2021-10-16 03:31:06 字數 2600 閱讀 8184

扣款的業務場景是怎樣的?

使用者購買商品的過程中,要對餘額進行查詢與修改,大致的業務流程如下:

第一步,從資料庫查詢使用者現有餘額:

select money from t_yue where uid=$uid;

不妨設查詢出來的$old_money=100元。

第二步,業務層實施業務邏輯計算,比如:

(1)先查詢購買商品的**,例如是80元;

(2)再查詢產品是否有活動,以及活動折扣,例如是9折;

(3)比對餘額是否足夠,足夠時才往下走;

if($old_money> 80*0.9) else

第三步,將資料庫中的餘額進行修改。

update t_yue set money=$new_money where uid=$uid;

在併發量低的情況下,這個流程沒有任何問題,原有金額100元,購買了80元的九折商品(72元),剩餘28元。

同乙個使用者,併發扣款可能出現什麼問題?

在分布式環境中,如果併發量很大,這種「查詢+修改」的業務有一定概率出現資料不一致。

極限情況下,可能出現這樣的異常流程:

步驟一,業務1和業務2併發查詢餘額,是100元。

畫外音:這些併發查詢,是在不同的站點例項/服務例項上完成的,程序內互斥鎖肯定解決不了。

步驟二,業務1和業務2併發進行邏輯計算,算出各自業務的餘額,假設業務1算出的餘額是28元,業務2算出的餘額是38元。

步驟三,業務1對資料庫中的餘額先進行修改,設定成28元。

業務2對資料庫中的餘額後進行修改,設定成38元。

此時異常出現了,原有金額100元,業務1扣除了72元,業務2扣除了62元,最後剩餘38元。

畫外音:假設業務1先寫回餘額,業務2再寫回餘額。

常見的解決方案?

對於此案例,同乙個使用者,併發扣款時,有小概率會出現異常,可以對每乙個使用者進行分布式鎖互斥,例如:在redis/zk裡搶到乙個key才能繼續操作,否則禁止操作。

這種悲觀鎖方案確實可行,但要引入額外的元件(redis/zk),並且會降低吞吐量。

對於小概率的不一致,有沒有樂觀鎖的方案呢?

對併發扣款進行進一步的分析發現:

(1)業務1寫回時,舊餘額100,這是乙個初始狀態;新餘額28,這是乙個結束狀態。理論上只有在舊餘額為100時,新餘額才應該寫回成功。

而業務1併發寫回時,舊餘額確實是100,理應寫回成功。

(2)業務2寫回時,舊餘額100,這是乙個初始狀態;新餘額28,這是乙個結束狀態。理論上只有在舊餘額為100時,新餘額才應該寫回成功。

可實際上,這個時候資料庫中的金額已經變為28了,所以業務2的併發寫回,不應該成功。

如何低成本實施樂觀鎖?

在set寫回的時候,加上初始狀態的條件compare,只有初始狀態不變時,才允許set寫回成功,compare and set(cas),是一種常見的降低讀寫鎖衝突,保證資料一致性的方法。

此時業務要怎麼改?

使用cas解決高併發時資料一致性問題,只需要在進行set操作時,compare初始值,如果初始值變換,不允許set成功。

具體到這個case,只需要將:

update t_yue set money=$new_money where uid=$uid;

公升級為:

update t_yue set money=$new_money where uid=$uid and money=$old_money;

即可。併發操作發生時:

業務1執行:

update t_yue set money=28 where uid=$uid and money=100;

業務2執行:

update t_yue set money=38 where uid=$uid and money=100;

這兩個操作同時進行時,只可能有乙個執行成功。

怎麼判斷哪個併發執行成功,哪個併發執行失敗呢?

set操作,其實無所謂成功或者失敗,業務能通過affect rows來判斷:

總結

高併發「查詢並修改」的場景,可以用cas(compare and set)的方式解決資料一致性問題。對應到業務,即在set的時候,加上初始條件的比對即可。

優化不難,只改了半行sql,但確實能解決問題。

但希望大家有收穫,思路比結論重要

Oracle如何保證併發操作?

oracle 採用封鎖技術保證併發操作的可序列性。oracle 的鎖分為兩大類 資料鎖 亦稱dml鎖 和字典鎖。字典鎖是oracle dbms內部用於對字典表的封鎖。字典鎖包括語法分析鎖和ddl鎖,由dbms在必要的時候自動加鎖和釋放鎖,使用者無權控制。oracle 主要提供了五種資料鎖 共享鎖 s...

如何保證redis高併發及高可用

1 面試題 如何保證redis的高併發和高可用?redis的主從複製原理能介紹一下麼?redis的哨兵原理能介紹一下麼?2 考點分析 其實問這個問題,主要是考考你,redis單機能承載多高併發?如果單機扛不住如何擴容抗更多的併發?redis會不會掛?既然redis會掛那怎麼保證redis是高可用的?...

併發安全(一) 如何保證執行緒安全

我們在併發程式設計中,首先不可避免的就是如何保證在併發中的執行緒安全問題。我們之前說過太多的併發工具類 併發容器等。這裡我們就分析我們在併發程式設計中,看看在哪些方面才能保證我們的執行緒安全。首先我們在併發中,一般都是對於共享資源等進行操作,可能會有執行緒安全的問題,那我們我們就不使用共享資源,是不...