什么是悲觀鎖
在關(guān)系數(shù)據(jù)庫管理系統(tǒng)里啥箭,悲觀并發(fā)控制(又名“悲觀鎖”酒繁,Pessimistic Concurrency Control皮钠,縮寫“PCC”)是一種并發(fā)控制的方法识樱。它可以阻止一個(gè)事務(wù)以影響其他用戶的方式來修改數(shù)據(jù)。如果一個(gè)事務(wù)執(zhí)行的操作讀某行數(shù)據(jù)應(yīng)用了鎖猴贰,那只有當(dāng)這個(gè)事務(wù)把鎖釋放对雪,其他事務(wù)才能夠執(zhí)行與該鎖沖突的操作。
悲觀并發(fā)控制主要用于數(shù)據(jù)爭(zhēng)用激烈的環(huán)境米绕,以及發(fā)生并發(fā)沖突時(shí)使用鎖保護(hù)數(shù)據(jù)的成本要低于回滾事務(wù)的成本的環(huán)境中瑟捣。
簡(jiǎn)而言之,悲觀鎖主要用于保護(hù)數(shù)據(jù)的完整性栅干。當(dāng)多個(gè)事務(wù)并發(fā)執(zhí)行時(shí)迈套,某個(gè)事務(wù)對(duì)數(shù)據(jù)應(yīng)用了鎖,則其他事務(wù)只能等該事務(wù)執(zhí)行完了碱鳞,才能進(jìn)行對(duì)該數(shù)據(jù)進(jìn)行修改操作桑李。
使用場(chǎng)景
在商品購(gòu)買場(chǎng)景中,當(dāng)有多個(gè)用戶對(duì)某個(gè)庫存有限的商品同時(shí)進(jìn)行下單操作窿给。若采用先查詢庫存贵白,后減庫存的方式進(jìn)行庫存數(shù)量的變更,將會(huì)導(dǎo)致超賣的產(chǎn)生崩泡。
若使用悲觀鎖禁荒,當(dāng)B用戶獲取到某個(gè)商品的庫存數(shù)據(jù)時(shí),用戶A則會(huì)阻塞角撞,直到B用戶完成減庫存的整個(gè)事務(wù)時(shí)圈浇,A用戶才可以獲取到商品的庫存數(shù)據(jù)。則可以避免商品被超賣靴寂。
如何使用悲觀鎖
用法:SELECT … FOR UPDATE;
例如,
select * from tbl_user where id=1 for update;
獲取鎖的前提:結(jié)果集中的數(shù)據(jù)沒有使用排他鎖或共享鎖時(shí)召耘,才能獲取鎖百炬,否則將會(huì)阻塞。
需要注意的是污它, FOR UPDATE
生效需要同時(shí)滿足兩個(gè)條件時(shí)才生效:
- 數(shù)據(jù)庫的引擎為 innoDB
- 操作位于事務(wù)塊中(BEGIN/COMMIT)
體驗(yàn)悲觀鎖
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ù)庫的自動(dòng)提交屬性
set autocommit=0;
// 開啟事務(wù)
BEGIN;
SELECT * FROM tbl_user where id=1 for update;
窗口2
此時(shí)剖踊,我們?cè)诖翱?執(zhí)行下面這條命令庶弃,嘗試獲取悲觀鎖:
SELECT * FROM tbl_user where id=1 for update;
執(zhí)行完后,窗口2并沒有像窗口1一樣德澈,立刻返回結(jié)果歇攻,而是發(fā)生了阻塞。
若超時(shí)間未獲取鎖梆造,將會(huì)得到一個(gè)鎖超時(shí)錯(cuò)誤提示缴守。如下圖所示:
行鎖與表鎖
當(dāng)執(zhí)行 select ... for update
時(shí),將會(huì)把數(shù)據(jù)鎖住镇辉,因此屡穗,我們需要注意一下鎖的級(jí)別。MySQL InnoDB 默認(rèn)為行級(jí)鎖忽肛。當(dāng)查詢語句指定了主鍵時(shí)村砂,MySQL會(huì)執(zhí)行「行級(jí)鎖」,否則MySQL會(huì)執(zhí)行「表鎖」屹逛。
常見情況如下:
- 若明確指明主鍵础废,且結(jié)果集有數(shù)據(jù),行鎖罕模;
- 若明確指明主鍵评腺,結(jié)果集無數(shù)據(jù),則無鎖手销;
- 若無主鍵歇僧,且非主鍵字段無索引,則表鎖锋拖;
- 若使用主鍵但主鍵不明確诈悍,則使用表鎖;
select * from tbl_user where id<>1 for update;
若需要了解更多情況兽埃,可以閱讀 此篇文章了解更多侥钳。
小結(jié): innoDB的行鎖是通過給索引上的索引項(xiàng)加鎖實(shí)現(xiàn)的,因此柄错,只有通過索引檢索數(shù)據(jù)舷夺,才會(huì)采用行鎖,否則使用的是表鎖售貌。
總結(jié)
悲觀鎖采用的是「先獲取鎖再訪問」的策略给猾,來保障數(shù)據(jù)的安全。但是加鎖策略颂跨,依賴數(shù)據(jù)庫實(shí)現(xiàn)敢伸,會(huì)增加數(shù)據(jù)庫的負(fù)擔(dān),且會(huì)增加死鎖的發(fā)生幾率恒削。此外池颈,對(duì)于不會(huì)發(fā)生變化的只讀數(shù)據(jù)尾序,加鎖只會(huì)增加額外不必要的負(fù)擔(dān)。在實(shí)際的實(shí)踐中躯砰,對(duì)于并發(fā)很高的場(chǎng)景并不會(huì)使用悲觀鎖每币,因?yàn)楫?dāng)一個(gè)事務(wù)鎖住了數(shù)據(jù),那么其他事務(wù)都會(huì)發(fā)生阻塞琢歇,會(huì)導(dǎo)致大量的事務(wù)發(fā)生積壓拖垮整個(gè)系統(tǒng)兰怠。