併發業務是一種非常常見且重要的業務場景。比較典型的業務場景是電商業務,尤其是秒殺場景,這裡面會涉及到非常多的併發事務,像金融業務等等交易相關的業務也都是如此。這在資料庫的應用中稱之為tp場景的業務,與ap業務相對應。
當前主流的網際網路業務架構大致可以分為三層,第一層是前端層,主要與使用者產生互動式關係;第二層可以認為是業務層,所有的業務處理邏輯,諸多if-else判斷邏輯等都可以抽象到此類;第三層便是資料持久層,資料持久層即與資料庫打交道的一層。對於這種tp業務來講,對事務的要求非常高,除了資料庫能夠提供acid外,還要資料庫系統能夠保證高併發效能。
通常,在處理資料庫併發業務上,我們要通過加鎖的方式來保障資料併發修改中的版本一致。通常的方法可以分為樂觀鎖和悲觀鎖,下面我們來**一下樂觀鎖和悲觀鎖這兩種鎖的類別。
資料庫在涉及到併發修改時,就會面臨併發修改後再進行資料讀取時,讀取出來的資料與預期資料是否一致的問題。要想使實際讀取上來的資料與預期資料一致,那麼加鎖是一種比較簡單的方法。值得一提的是,樂觀鎖與悲觀鎖是抽象在思想上的,並不是指具體的加鎖方法,而是一種針對不同業務而提出的併發控制方**。
所謂樂觀鎖就是認為業務場景中併發修改造成的資料衝突的概率不會太高,只需要在業務完成後提交時加鎖即可。說白了就不真正加鎖,而是通過邏輯判斷的方法來實現併發控制,這樣的好處就是輕量,效能好;缺點就是一旦併發衝突超過預期,樂觀鎖就會造成業務上的大量失敗執行緒(更新失敗的執行緒不會等待合適的時機去更新,而是直接報錯)。例如**中下單時正常,但是在付款時卻提示庫存不足。這很明顯是在下單和付款之間並沒有通過一把鎖來保障整個業務的原子性,也就導致了下單時和付款時預期資料與實際資料不一致的問題。這個場景也很好解決,只需要在從下單到付款這個過程中用一把鎖來保障就好了,即在下單時加鎖,在付款結束後釋放鎖。這個過程呼叫資料庫的事務,故而高度依賴資料庫的鎖效能,相對於樂觀鎖更重,效能開銷更大。
在上面我們介紹過悲觀鎖與樂觀鎖,下面我們詳細說一下悲觀鎖(pessimistic concurrency control, pcc)。
我們前面提到過,悲觀鎖要求將整個業務過程作為乙個整體的事務提交,鎖的粒度大且重,也就是在資料被修改之前先加上鎖。
悲觀鎖的具體實現方法主要是:
在資料修改之前加排他鎖(exclusive locking)
我們知道,排它鎖是會對讀、寫都排斥的鎖。這樣在資料被修改之前其他的任務就沒有辦法訪問被加鎖物件,自然可以保障資料不會被併發修改或訪問。如果其他任務發現被訪問物件已經被加鎖,那麼其他任務可以報異常或者一直等著。這樣除了會有效能損耗之外,還可能造成死鎖。
ps. mysql 關閉事務自動提交屬性:
set autocommit = 0;
我們可以通過sql語句來描述:
begin
;select 庫存餘量;
update 庫存餘量 -1;
commit
;
相對於悲觀鎖而言,樂觀鎖(optimistic locking) 不是通過資料庫的方式進行併發控制的,而是在業務邏輯層通過增加一些判斷條件來檢測衝突的。樂觀鎖的乙個假設條件是:一般情況下資料不會造成衝突。因此,樂觀鎖在資料提交後,準備更新時才會對資料進行併發檢測。
cas(compare and swap)是一種常用的樂觀鎖思想,這個過程可以描述如下:
select 庫存餘量 as q0
update 庫存餘量 -
1where 庫存餘量 = 剛剛select的結果q0
上面的sql偽**中第乙個select先查詢,第二個update在更新的時候新增乙個判斷條件,這個判斷條件是上乙個select的返回結果。這樣表面上看第二個update語句會與第乙個select到的結果相關連,二者存在邏輯上的關聯關係,因此是不可分的。
但是,這有個問題,就是:如果在 select 和 update 語句中,存在另外乙個併發任務先把這個select 到的結果q0 更改為 q1 然後又偷偷改回了q0, 這樣對於第二個update語句來講,是不可知的。這就是傳說中的aba問題。
aba問題問題發生了又能怎樣?下面這個例子引用自維基百科:
你拿著乙個裝滿錢的手提箱在飛機場,此時過來了乙個火辣**的美女,然後她很暖昧地挑逗著你,並趁你不注意的時候,把用乙個一模一樣的手提箱和你那裝滿錢的箱子調了個包,然後就離開了,你看到你的手提箱還在那,於是就提著手提箱去趕飛機去了。aba問題是cas自帶的乙個***,可以理解為你表面上認為資料沒有變化,但是資料實際上已經發生了變化。上面的sql語句是乙個具體的常數q0,所以你感覺不到什麼。加入這個q0是乙個指標,指向了某個位址,該位址的內容已經變化了,但是指標的值卻沒有變,你能鑑別出來嗎?當然可以。但是你仍然會存在沒有進行深度鑑別的可能。這就造成了aba隱患。
消除aba隱患的方法也比較簡單,就是在每次更新的時候,加乙個記錄字段,這個記錄字段設定為自增的,沒更新一次就增長乙個。這樣,在update語句中,where條件再多加乙個,就可以避免aba問題了。例如:
select 庫存餘量 as q0, version as v0
update 庫存餘量-
1,version +
1where 庫存餘量 = q0 and version = v0
資料庫中的樂觀鎖與悲觀鎖
樂觀鎖 在關聯式資料庫管理系統裡,樂觀併發控制 又名 樂觀鎖 optimistic concurrency control,縮寫 occ 是一種併發控制的方法。它假設多使用者併發的事務在處理時不會彼此互相影響,各事務能夠在不產生鎖的情況下處理各自影響的那部分資料。在提交資料更新之前,每個事務會先檢查...
資料庫樂觀鎖與悲觀鎖
每次拿資料的時候都會擔心會被別人修改 疑心重很悲觀 所以每次在拿資料的時候都會上鎖。確保自己使用的過程中不會被別人訪問,自己使用完後再解鎖。期間需要訪問該資料的都會等待。每次拿資料的時候都完全不擔心會被別人修改 心態好很樂觀 所以每次在拿資料的時候都不會上鎖。但是在更新資料的時候去判斷該期間是否被別...
資料庫樂觀鎖與悲觀鎖
演示案例 為何需要樂觀鎖,與悲觀鎖這樣的鎖?idname money 1god 1000 假設god同志的賬上有1000元,現在有兩個執行緒同時往他的賬戶上轉錢。1.a執行緒準備向god賬戶上轉200,讀取到賬戶上有1000元,事務還未提交 2.b執行緒準備向god賬戶上轉100,讀取到賬戶上有10...