日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > 数据库 >内容正文

数据库

数据库 并发更新之乐观锁和悲观锁

發(fā)布時(shí)間:2023/12/20 数据库 52 豆豆
生活随笔 收集整理的這篇文章主要介紹了 数据库 并发更新之乐观锁和悲观锁 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

文章目錄

  • 1. 問題引出
  • 2. 數(shù)據(jù)庫(kù)悲觀鎖解決并發(fā)更新
  • 3. 數(shù)據(jù)庫(kù)樂觀鎖解決并發(fā)更新
  • 4. 樂觀鎖 CAS 的 ABA 問題
  • 5. 拓展思考
    • 5.1. 悲觀鎖和排他鎖、樂觀鎖和 CAS 分別有什么區(qū)別
    • 5.2. 悲觀鎖和樂觀鎖適用場(chǎng)景
    • 5.3. 樂觀鎖是否必須加版本號(hào)或時(shí)間戳字段

1. 問題引出

假設(shè)現(xiàn)在有一張 item 商品表,quantity 字段表示該商品的數(shù)量。

這時(shí)候有一個(gè)用戶下了訂單,購(gòu)買一件商品。那么我們可以用以下 SQL 來(lái)實(shí)現(xiàn)這個(gè)邏輯

UPDATE item SET quantity = quantity - 1 WHERE id = 1;

這個(gè)實(shí)現(xiàn)在一般情況下是沒有問題的,但是現(xiàn)在的后端應(yīng)用都是在多線程或者多進(jìn)程環(huán)境下運(yùn)行,在高并發(fā)情況下就有可能發(fā)生問題

假設(shè)現(xiàn)在有 A 和 B 兩個(gè)用戶同時(shí)下單,后端服務(wù)會(huì)分配 2 個(gè)不同的線程去處理請(qǐng)求,這里分別用線程 A 和 B 來(lái)表示。

線程A(用戶A下單)線程B(用戶B下單)
查詢商品 id = 1,此時(shí) quantity = 100
查詢商品 id = 1,此時(shí) quantity = 100
用戶A下單,更新 quantity = 99
用戶B下單,更新 quantity = 99

在線程 A 還沒更新數(shù)量之前,B 就去把商品數(shù)量查出來(lái)了,并發(fā)更新導(dǎo)致數(shù)據(jù)不一致,業(yè)務(wù)上就體現(xiàn)為超賣。

那么這個(gè)問題該如何解決呢?答案就是加鎖。鎖可以在不同的層面加。如果是單實(shí)例應(yīng)用,直接加本地鎖,例如 Java 應(yīng)用可以使用 synchronized。如果是分布式應(yīng)用,可以通過(guò) Redis、ZooKeeper、Etcd 加分布式鎖

這種情況是數(shù)據(jù)庫(kù)并發(fā)更新導(dǎo)致的,能不能直接在數(shù)據(jù)庫(kù)層面解決呢?答案是可以的,可以利用數(shù)據(jù)庫(kù)鎖機(jī)制來(lái)解決并發(fā)更新問題。方案有悲觀鎖和樂觀鎖,本文對(duì)這2種解決方案展開說(shuō)明

2. 數(shù)據(jù)庫(kù)悲觀鎖解決并發(fā)更新

MySQL 的 InnoDB 引擎提供了以下兩種行鎖機(jī)制。在查詢記錄時(shí),使用以下 SQL,可以給對(duì)應(yīng)行加上共享鎖和排他鎖。

-- 共享鎖(S) SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE -- 排他鎖(X) SELECT * FROM table_name WHERE ... FOR UPDATE

其中 SELECT ... FOR UPDATE 是悲觀鎖的具體實(shí)現(xiàn)。并發(fā)更新可以通過(guò)該機(jī)制保證數(shù)據(jù)一致性

需要注意的是,鎖在 autocommit=0 狀態(tài)下使用才有意義,因?yàn)殒i會(huì)在 commit 之后自動(dòng)釋放。默認(rèn)情況下 MySQL 單行語(yǔ)句就是一個(gè)事務(wù),加鎖語(yǔ)句執(zhí)行完,鎖立即就被釋放了,也就沒意義了

下面給出 SELECT ... FOR UPDATE 解決并發(fā)更新的示例

-- A和B開啟事務(wù) BEGIN; -- A查詢,加上排他鎖 SELECT * FROM item WHERE id = 1 FOR UPDATE;-- B查詢加鎖,由于鎖被A占用,所以阻塞SELECT * FROM item WHERE id = 1 FOR UPDATE; -- A更新 UPDATE item SET quantity = quantity - 1 WHERE id = 1; -- A提交 COMMIT;-- B成功查詢出記錄,繼續(xù)執(zhí)行更新UPDATE item SET quantity = quantity - 1 WHERE id = 1;-- B提交COMMIT;

3. 數(shù)據(jù)庫(kù)樂觀鎖解決并發(fā)更新

樂觀鎖本質(zhì)上不加鎖,是一種 CAS 無(wú)鎖機(jī)制。所謂 CAS,就是在更新的時(shí)候,檢查該實(shí)際值是不是和期望值一樣,一樣就更新成功,不一樣就更新失敗

下面給出 CAS 解決并發(fā)更新的示例

-- A 查出來(lái) quantity = 100 SELECT * FROM item WHERE id = 1;-- B 查出來(lái) quantity = 100SELECT * FROM item WHERE id = 1; -- A 更新 quantity,同時(shí)加上 where 條件檢查 quantity 是不是期望值。發(fā)現(xiàn)是,更新成功 UPDATE item SET quantity = quantity - 1 WHERE id = 1 AND quantity = 100;-- B 更新 quantity,發(fā)現(xiàn) quantity 不是期望值,更新失敗UPDATE item SET quantity = quantity - 1 WHERE id = 1 AND quantity = 100;

CAS 存在更新失敗的情況。如何判斷更新是否失敗呢?這也很簡(jiǎn)單,UPDATE 語(yǔ)句返回值代表更新的行數(shù),直接判斷返回值是不是 0 即可,0 就是失敗。

現(xiàn)在我們可以判斷更新失敗了,那如何解決呢?這個(gè)得具體業(yè)務(wù)具體解決了。如果業(yè)務(wù)容許這種錯(cuò)誤發(fā)現(xiàn),可以給用戶一個(gè)錯(cuò)誤提示,比如:

// 查詢記錄 doQuery();// CAS 更新 if (doCasUpdate() == 0) {doError("提示系統(tǒng)繁忙,請(qǐng)重試"); }

如果業(yè)務(wù)不容許失敗,這時(shí)候可以加一個(gè)死循環(huán)進(jìn)行重試

while (true) {// 查詢記錄doQuery();// CAS 更新if (doCasUpdate() > 0) {break;} }

4. 樂觀鎖 CAS 的 ABA 問題

我們繼續(xù)以商品這個(gè)場(chǎng)景舉例,假設(shè)現(xiàn)在有3個(gè)操作同時(shí)進(jìn)行,分別是 A、B 用戶同時(shí)下單,C 用戶添加商品數(shù)據(jù)

線程A(用戶A下單)線程B(用戶B下單)線程C(運(yùn)營(yíng)C添加庫(kù)存)
查詢 quantity = 100查詢 quantity = 100
更新 quantity = 99
更新 quantity = 99+1= 100
更新 quantity = 99

用戶 B 下單減庫(kù)存本來(lái)應(yīng)該失敗的,但是在 C 用戶的干預(yù)下,更新商品數(shù)量成功了,因?yàn)?quantity 在中間階段又被更新回預(yù)期值 100

這就是 ABA 問題。一個(gè)變量一開始是A,被修改為B,又被修改為A,這在程序看來(lái)數(shù)據(jù)是沒有變化的。但實(shí)際上此A非彼A。

這個(gè)情況對(duì)業(yè)務(wù)有沒有影響呢?在這個(gè)商品數(shù)量場(chǎng)景下確實(shí)是沒有影響的。但是有的業(yè)務(wù)可能是會(huì)有影響的。這時(shí)候需要單獨(dú)引入一個(gè)版本號(hào)或時(shí)間戳字段來(lái)解決

SELECT * FROM item WHERE id = 1; UPDATE item SET quantity = quantity - 1, version = version + 1 WHERE id = 1 AND version = 預(yù)期版本號(hào)

5. 拓展思考

5.1. 悲觀鎖和排他鎖、樂觀鎖和 CAS 分別有什么區(qū)別

悲觀鎖和樂觀鎖都是抽象概念,而且都是針對(duì)并發(fā)更新場(chǎng)景提出的,物理上不存在對(duì)應(yīng)的鎖。

悲觀鎖,去查數(shù)據(jù)的時(shí)候都悲觀地認(rèn)為別人會(huì)修改,所以每次查數(shù)據(jù)時(shí)直接上鎖。排他鎖是悲觀鎖的一種實(shí)現(xiàn)方案

樂觀鎖,相對(duì)悲觀鎖而言,查數(shù)據(jù)時(shí)認(rèn)為一般不會(huì)被修改,所以只在更新數(shù)據(jù)時(shí)檢測(cè)沖突。CAS 是樂觀鎖的一種具體實(shí)現(xiàn)

5.2. 悲觀鎖和樂觀鎖適用場(chǎng)景

寫多讀少用悲觀鎖,讀多寫少用樂觀鎖

舉個(gè)例子,假設(shè)有10萬(wàn)并發(fā),其中有幾個(gè)是更新操作,其它都是讀操作,這時(shí)候就特別適合使用樂觀鎖。對(duì)于更新操作,由于請(qǐng)求數(shù)較少,CAS 沖突概率就小,大部分都是成功的。對(duì)于讀操作,由于沒有加鎖,就沒有性能響應(yīng)

假設(shè)有10萬(wàn)并發(fā),有幾個(gè)是讀操作,其它都是寫操作。如果使用樂觀鎖,CAS 沖突概率極大,大部分都是更新失敗。如果還有循環(huán)不停地進(jìn)行 CAS 操作,一個(gè)是應(yīng)用的 CPU 開銷過(guò)大,一個(gè)是給數(shù)據(jù)庫(kù)帶來(lái)過(guò)多的并發(fā),嚴(yán)重影響性能。這時(shí)候就使用悲觀鎖,直接上鎖。

5.3. 樂觀鎖是否必須加版本號(hào)或時(shí)間戳字段

如果 CAS 業(yè)務(wù)上存在 ABA 問題,那么就得加版本號(hào)或時(shí)間戳字段。

如果不存在 ABA 問題的話,直接通過(guò)業(yè)務(wù)字段本身來(lái)檢測(cè)沖突即可,沒有必要再引入額外字段

總結(jié)

以上是生活随笔為你收集整理的数据库 并发更新之乐观锁和悲观锁的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。