什么是悲觀鎖
在關(guān)系數(shù)據(jù)庫管理系統(tǒng)里,悲觀并發(fā)控制(又名“悲觀鎖”,Pessimistic Concurrency Control,縮寫“PCC”)是一種并發(fā)控制的方法。它可以阻止一個事務(wù)以影響其他用戶的方式來修改數(shù)據(jù)。如果一個事務(wù)執(zhí)行的操作讀某行數(shù)據(jù)應(yīng)用了鎖,那只有當(dāng)這個事務(wù)把鎖釋放,其他事務(wù)才能夠執(zhí)行與該鎖沖突的操作。
悲觀并發(fā)控制主要用于數(shù)據(jù)爭用激烈的環(huán)境,以及發(fā)生并發(fā)沖突時使用鎖保護數(shù)據(jù)的成本要低于回滾事務(wù)的成本的環(huán)境中。
簡而言之,悲觀鎖主要用于保護數(shù)據(jù)的完整性。當(dāng)多個事務(wù)并發(fā)執(zhí)行時,某個事務(wù)對數(shù)據(jù)應(yīng)用了鎖,則其他事務(wù)只能等該事務(wù)執(zhí)行完了,才能進行對該數(shù)據(jù)進行修改操作。
使用場景
在商品購買場景中,當(dāng)有多個用戶對某個庫存有限的商品同時進行下單操作。若采用先查詢庫存,后減庫存的方式進行庫存數(shù)量的變更,將會導(dǎo)致超賣的產(chǎn)生。

若使用悲觀鎖,當(dāng)B用戶獲取到某個商品的庫存數(shù)據(jù)時,用戶A則會阻塞,直到B用戶完成減庫存的整個事務(wù)時,A用戶才可以獲取到商品的庫存數(shù)據(jù)。則可以避免商品被超賣。
如何使用悲觀鎖
用法:SELECT … FOR UPDATE;
例如,
select * from tbl_user where id=1 for update;
獲取鎖的前提:結(jié)果集中的數(shù)據(jù)沒有使用排他鎖或共享鎖時,才能獲取鎖,否則將會阻塞。
需要注意的是, FOR UPDATE 生效需要同時滿足兩個條件時才生效:
- 數(shù)據(jù)庫的引擎為 innoDB
- 操作位于事務(wù)塊中(BEGIN/COMMIT)
體驗悲觀鎖
Step 1 初始化表結(jié)構(gòu)和數(shù)據(jù)
CREATE TABLE `tbl_user` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`status` int(11) DEFAULT NULL,
`name` varchar(255) COLLATE utf8_bin DEFAULT NULL,
PRIMARY KEY (`id`)
);
INSERT INTO `tbl_user` (`id`, `status`, `name`)
VALUES
(1,1,X'7469616E'),
(2,1,X'63697479');
Step 2
窗口1
// 關(guān)閉mysql數(shù)據(jù)庫的自動提交屬性
set autocommit=0;
// 開啟事務(wù)
BEGIN;
SELECT * FROM tbl_user where id=1 for update;
窗口2
此時,我們在窗口2執(zhí)行下面這條命令,嘗試獲取悲觀鎖:
SELECT * FROM tbl_user where id=1 for update;
執(zhí)行完后,窗口2并沒有像窗口1一樣,立刻返回結(jié)果,而是發(fā)生了阻塞。
若超時間未獲取鎖,將會得到一個鎖超時錯誤提示。如下圖所示:

行鎖與表鎖
當(dāng)執(zhí)行 select ... for update時,將會把數(shù)據(jù)鎖住,因此,我們需要注意一下鎖的級別。MySQL InnoDB 默認為行級鎖。當(dāng)查詢語句指定了主鍵時,MySQL會執(zhí)行「行級鎖」,否則MySQL會執(zhí)行「表鎖」。
常見情況如下:
- 若明確指明主鍵,且結(jié)果集有數(shù)據(jù),行鎖;
- 若明確指明主鍵,結(jié)果集無數(shù)據(jù),則無鎖;
- 若無主鍵,且非主鍵字段無索引,則表鎖;
- 若使用主鍵但主鍵不明確,則使用表鎖;
select * from tbl_user where id<>1 for update;
若需要了解更多情況,可以閱讀 此篇文章了解更多。
小結(jié): innoDB的行鎖是通過給索引上的索引項加鎖實現(xiàn)的,因此,只有通過索引檢索數(shù)據(jù),才會采用行鎖,否則使用的是表鎖。
總結(jié)
悲觀鎖采用的是「先獲取鎖再訪問」的策略,來保障數(shù)據(jù)的安全。但是加鎖策略,依賴數(shù)據(jù)庫實現(xiàn),會增加數(shù)據(jù)庫的負擔(dān),且會增加死鎖的發(fā)生幾率。此外,對于不會發(fā)生變化的只讀數(shù)據(jù),加鎖只會增加額外不必要的負擔(dān)。在實際的實踐中,對于并發(fā)很高的場景并不會使用悲觀鎖,因為當(dāng)一個事務(wù)鎖住了數(shù)據(jù),那么其他事務(wù)都會發(fā)生阻塞,會導(dǎo)致大量的事務(wù)發(fā)生積壓拖垮整個系統(tǒng)。