資料庫的樂觀鎖和悲觀鎖是什麼?

2021-10-06 10:38:39 字數 4468 閱讀 7646

樂觀鎖對應於生活中樂觀的人總是想著事情往好的方向發展,悲觀鎖對應於生活中悲觀的人總是想著事情往壞的方向發展。這兩種人各有優缺點,不能不以場景而定說一種人好於另外一種人。

樂觀鎖

悲觀鎖

使用場景

樂觀鎖一般會使用版本號機制或 cas 演算法實現。

一般是在資料表中加上乙個資料版本號 version 字段,表示資料被修改的次數,當資料被修改時,version 值會加一。當執行緒 a 要更新資料值時,在讀取資料的同時也會讀取 version 值,在提交更新時,若剛才讀取到的 version 值為當前資料庫中的 version 值相等時才更新,否則重試更新操作,直到更新成功。

舉乙個簡單的例子: 假設資料庫中帳戶資訊表中有乙個 version 字段,當前值為 1 ;而當前帳戶餘額字段( balance )為 $100 。

這樣,就避免了操作員 b 用基於 version=1 的舊資料修改的結果覆蓋操作員a的操作結果的可能。

即 compare and swap(比較與交換),是一種有名的無鎖演算法。無鎖程式設計,即不使用鎖的情況下實現多執行緒之間的變數同步,也就是在沒有執行緒被阻塞的情況下實現變數的同步,所以也叫非阻塞同步(non-blockingsynchronization)。cas 演算法涉及到三個操作:

需要讀寫的記憶體值v

進行比較的值a

擬寫入的新值b

當且僅當 v 的值等於 a 時,cas 通過原子方式用新值 b 來更新 v 的值,否則不會執行任何操作(比較和替換是乙個原子操作)。一般情況下是乙個自旋操作,即不斷的重試。

通過資料庫鎖機制實現,即對查詢語句新增for update關鍵字。

如下sql語句 select * from table where id = 1 for update 當乙個請求a開啟事務並執行此sql同時未提交事務時,另乙個執行緒b發起請求,此時b將阻塞在加了鎖的查詢語句上,直到a請求的事務提交或者回滾,b才會繼續執行,保證了訪問的隔離性。

悲觀鎖優缺點分析,優點是每一次行資料的訪問都是獨佔的,只有當正在訪問該行資料的請求事務提交以後,其他請求才能依次訪問該資料,否則將阻塞等待鎖的獲取。悲觀鎖可以嚴格保證資料訪問的安全,但是缺點也明顯,即每次請求都會額外產生加鎖的開銷且未獲取到鎖的請求將會阻塞等待鎖的獲取,在高併發環境下,容易造成大量請求阻塞,影響系統可用性。另外,悲觀鎖使用不當還可能產生死鎖的情況。

使用場景舉例:以mysql innodb為例

商品goods表中有乙個欄位status,status為1代表商品未被下單,status為2代表商品已經被下單,那麼我們對某個商品下單時必須確保該商品status為1。假設商品的id為1。

如果不採用鎖,那麼操作方法如下:

--1.查詢出商品資訊

select

status

from t_goods where id=1;

--2.根據商品資訊生成訂單

insert

into t_orders (id,goods_id)

values

(null,1

);--3.修改商品status為2

update t_goods set

status=2

;

上面這種場景在高併發訪問的情況下很可能會出現問題。

前面已經提到,只有當goods status為1時才能對該商品下單,上面第一步操作中,查詢出來的商品status為1。但是當我們執行第三步update操作的時候,有可能出現其他人先一步對商品下單把goods status修改為2了,但是我們並不知道資料已經被修改了,這樣就可能造成同乙個商品被下單2次,使得資料不一致。所以說這種方式是不安全的。

使用悲觀鎖來實現:

在上面的場景中,商品資訊從查詢出來到修改,中間有乙個處理訂單的過程,使用悲觀鎖的原理就是,當我們在查詢出goods資訊後就把當前的資料鎖定,直到我們修改完畢後再解鎖。那麼在這個過程中,因為goods被鎖定了,就不會出現有第三者來對其進行修改了。

注:要使用悲觀鎖,我們必須關閉mysql資料庫的自動提交屬性,因為mysql預設使用autocommit模式,也就是說,當你執行乙個更新操作後,mysql會立刻將結果進行提交。

我們可以使用命令設定mysql為非autocommit模式:

set autocommit=0;

設定完autocommit後,我們就可以執行我們的正常業務了。具體如下:

--0.開始事務

begin;/

begin

work;/

start

transaction

;(三者選一就可以)

--1.查詢出商品資訊

select

status

from t_goods where id=

1for

update

;--2.根據商品資訊生成訂單

insert

into t_orders (id,goods_id)

values

(null,1

);--3.修改商品status為2

update t_goods set

status=2

;--4.提交事務

commit;/

commit

work

;

注:上面的begin/commit為事務的開始和結束,因為在前一步我們關閉了mysql的autocommit,所以需要手動控制事務的提交,在這裡就不細表了。

上面的第一步我們執行了一次查詢操作:select status from t_goods where id=1 for update;

與普通查詢不一樣的是,我們使用了select…for update的方式,這樣就通過資料庫實現了悲觀鎖。此時在t_goods表中,id為1的 那條資料就被我們鎖定了,其它的事務必須等本次事務提交之後才能執行。這樣我們可以保證當前的資料不會被其它事務修改。

注:需要注意的是,在事務中,只有select … for update 或lock in share mode 同一筆資料時會等待其它事務結束後才執行,一般select … 則不受此影響。拿上面的例項來說,當我執行select status from t_goods where id=1 for update;後。我在另外的事務中如果再次執行select status from t_goods where id=1 for update;則第二個事務會一直等待第乙個事務的提交,此時第二個查詢處於阻塞的狀態,但是如果我是在第二個事務中執行select status from t_goods where id=1;則能正常查詢出資料,不會受第乙個事務的影響。

補充:mysql select…for update的row lock與table lock

上面我們提到,使用select…for update會把資料給鎖住,不過我們需要注意一些鎖的級別,mysql innodb預設row-level lock,所以只有「明確」地指定主鍵,mysql 才會執行row lock (只鎖住被選取的資料) ,否則mysql 將會執行table lock (將整個資料表單給鎖住)。

aba問題是樂觀鎖乙個常見的問題

如果乙個變數 v 初次讀取的時候是 a 值,並且在準備賦值的時候檢查到它仍然是 a 值,那我們就能說明它的值沒有被其他執行緒修改過了嗎?很明顯是不能的,因為在這段時間它的值可能被改為其他值,然後又改回 a,那 cas 操作就會誤認為它從來沒有被修改過。這個問題被稱為 cas 操作的 "aba"問題。

jdk 1.5 以後的 atomicstampedreference 類就提供了此種能力,其中的compareandset 方法就是首先檢查當前引用是否等於預期引用,並且當前標誌是否等於預期標誌,如果全部相等,則以原子方式將該引用和該標誌的值設定為給定的更新值。

自旋 cas(也就是不成功就一直迴圈執行直到成功)如果長時間不成功,會給cpu 帶來非常大的執行開銷。 如果 jvm 能支援處理器提供的 pause 指令那麼效率會有一定的提公升,pause 指令有兩個作用,第一它可以延遲流水線執行指令(de-pipeline),使 cpu 不會消耗過多的執行資源,延遲的時間取決於具體實現的版本,在一些處理器上延遲時間是零。第二它可以避免在退出迴圈的時候因記憶體順序衝突(memory order violation)而引起 cpu 流水線被清空(cpu pipeline flush),從而提高 cpu 的執行效率。

cas 只對單個共享變數有效,當操作涉及跨多個共享變數時 cas 無效。但是從 jdk 1.5 開始,提供了 atomicreference 類來保證引用物件之間的原子性,你可以把多個變數放在乙個物件裡來進行 cas 操作.所以我們可以使用鎖或者利用 atomicreference 類把多個共享變數合併成乙個共享變數來操作。

cas與synchronized的使用情景

簡單的來說 cas 適用於寫比較少的情況下(多讀場景,衝突一般較少),synchronized 適用於寫比較多的情況下(多寫場景,衝突一般較多)。

資料庫的樂觀鎖和悲觀鎖

悲觀鎖 假定會發生併發衝突,遮蔽一切可能違反資料完整性的操作,假設資料肯定會衝突,所以在資料開始讀取的時候就把資料鎖定住。資料鎖定 資料將暫時不會得到修改 樂觀鎖 假設不會發生併發衝突,只在提交操作時檢查是否違反資料完整性。資料進行提交更新的時候,才會正式對資料的衝突與否進行檢測,如果發現衝突了,則...

資料庫 (2)樂觀鎖和悲觀鎖

併發控制手段 樂觀鎖 樂觀併發控制 悲觀鎖 悲觀併發控制 悲觀鎖 當我們要對乙個資料庫中的一條資料進行修改的時候,為了避免同時被其他人修改,最好的辦法就是對該資料進行加鎖以防止併發。這種借助資料庫鎖機制在修改資料之前先鎖定,再修改的方式稱之為悲觀併發控制。悲觀併發控制實際上是 先取鎖再訪問的保守策略...

資料庫鎖 樂觀鎖 悲觀鎖理解

參考 mysql innodb中,樂觀鎖 悲觀鎖 共享鎖 排它鎖 行鎖 表鎖 死鎖概念的理解 樂觀鎖最簡單的實現就是在表中加乙個版本號欄位如version,每次新增設定為1,更新的時候檢查版本號是否一致,如果不一致就更新失敗。版本一致才能更新,然後將版本號 1。首先資料庫需要關閉自動提交功能,或者說...