Select for update使用詳解1

前言

近期開(kāi)發(fā)與錢(qián)相關(guān)的項(xiàng)目,在高并發(fā)場(chǎng)景下對(duì)數(shù)據(jù)的準(zhǔn)確行有很高的要求瓮钥,用到了for update筋量,故總結(jié)一波以便日后留戀。

for update的使用場(chǎng)景

如果遇到存在高并發(fā)并且對(duì)于數(shù)據(jù)的準(zhǔn)確性很有要求的場(chǎng)景碉熄,是需要了解和使用for update的桨武。

比如涉及到金錢(qián)、庫(kù)存等锈津。一般這些操作都是很長(zhǎng)一串并且是開(kāi)啟事務(wù)的呀酸。如果庫(kù)存剛開(kāi)始讀的時(shí)候是1,而立馬另一個(gè)進(jìn)程進(jìn)行了update將庫(kù)存更新為0了琼梆,而事務(wù)還沒(méi)有結(jié)束性誉,會(huì)將錯(cuò)的數(shù)據(jù)一直執(zhí)行下去,就會(huì)有問(wèn)題茎杂。所以需要for upate 進(jìn)行數(shù)據(jù)加鎖防止高并發(fā)時(shí)候數(shù)據(jù)出錯(cuò)错览。

記住一個(gè)原則:一鎖二判三更新

排他鎖的申請(qǐng)前提

沒(méi)有線程對(duì)該結(jié)果集中的任何行數(shù)據(jù)使用排他鎖或共享鎖,否則申請(qǐng)會(huì)阻塞煌往。

for update僅適用于InnoDB倾哺,且必須在事務(wù)塊(BEGIN/COMMIT)中才能生效。在進(jìn)行事務(wù)操作時(shí),通過(guò)“for update”語(yǔ)句羞海,MySQL會(huì)對(duì)查詢(xún)結(jié)果集中每行數(shù)據(jù)都添加排他鎖忌愚,其他線程對(duì)該記錄的更新與刪除操作都會(huì)阻塞。排他鎖包含行鎖却邓、表鎖硕糊。

場(chǎng)景分析

假設(shè)有一張商品表 goods,它包含 id申尤,商品名稱(chēng)癌幕,庫(kù)存量三個(gè)字段,表結(jié)構(gòu)如下:

CREATE TABLE goods (

id int(11) NOT NULL AUTO_INCREMENT,

name varchar(100) DEFAULT NULL,

stock int(11) DEFAULT NULL,

PRIMARY KEY (id),

UNIQUE KEY idx_name (name) USING HASH

) ENGINE=InnoDB

插入如下數(shù)據(jù):

INSERT INTO goods VALUES ('1', 'prod11', '1000');

INSERT INTO goods VALUES ('2', 'prod12', '1000');

INSERT INTO goods VALUES ('3', 'prod13', '1000');

INSERT INTO goods VALUES ('4', 'prod14', '1000');

INSERT INTO goods VALUES ('5', 'prod15', '1000');

INSERT INTO goods VALUES ('6', 'prod16', '1000');

INSERT INTO goods VALUES ('7', 'prod17', '1000');

INSERT INTO goods VALUES ('8', 'prod18', '1000');

INSERT INTO goods VALUES ('9', 'prod19', '1000');

一昧穿、數(shù)據(jù)一致性

假設(shè)有A、B兩個(gè)用戶(hù)同時(shí)各購(gòu)買(mǎi)一件 id=1 的商品橙喘,用戶(hù)A獲取到的庫(kù)存量為 1000时鸵,用戶(hù)B獲取到的庫(kù)存量也為 1000,用戶(hù)A完成購(gòu)買(mǎi)后修改該商品的庫(kù)存量為 999厅瞎,用戶(hù)B完成購(gòu)買(mǎi)后修改該商品的庫(kù)存量為 999饰潜,此時(shí)庫(kù)存量數(shù)據(jù)產(chǎn)生了不一致。

有兩種解決方案:

悲觀鎖方案:每次獲取商品時(shí)和簸,對(duì)該商品加排他鎖彭雾。也就是在用戶(hù)A獲取獲取 id=1 的商品信息時(shí)對(duì)該行記錄加鎖,期間其他用戶(hù)阻塞等待訪問(wèn)該記錄锁保。悲觀鎖適合寫(xiě)入頻繁的場(chǎng)景薯酝。

begin;

select * from goods where id = 1 for update;

update goods set stock = stock - 1 where id = 1;

commit;

樂(lè)觀鎖方案:每次獲取商品時(shí),不對(duì)該商品加鎖爽柒。在更新數(shù)據(jù)的時(shí)候需要比較程序中的庫(kù)存量與數(shù)據(jù)庫(kù)中的庫(kù)存量是否相等吴菠,如果相等則進(jìn)行更新,反之程序重新獲取庫(kù)存量浩村,再次進(jìn)行比較做葵,直到兩個(gè)庫(kù)存量的數(shù)值相等才進(jìn)行數(shù)據(jù)更新。樂(lè)觀鎖適合讀取頻繁的場(chǎng)景心墅。

不加鎖獲取 id=1 的商品對(duì)象

select * from goods where id = 1

begin;

更新 stock 值酿矢,這里需要注意 where 條件 “stock = cur_stock”,只有程序中獲取到的庫(kù)存量與數(shù)據(jù)庫(kù)中的庫(kù)存量相等才執(zhí)行更新

update goods set stock = stock - 1 where id = 1 and stock = cur_stock;

commit;

如果我們需要設(shè)計(jì)一個(gè)商城系統(tǒng)怎燥,該選擇以上的哪種方案呢瘫筐?

查詢(xún)商品的頻率比下單支付的頻次高,基于以上我可能會(huì)優(yōu)先考慮第二種方案(當(dāng)然還有其他的方案刺覆,這里只考慮以上兩種方案)严肪。

二、行鎖與表鎖

InnoDB默認(rèn)是行級(jí)別的鎖,當(dāng)有明確指定的主鍵時(shí)候驳糯,是行級(jí)鎖篇梭。否則是表級(jí)別。

for update的注意點(diǎn)

for update 僅適用于InnoDB酝枢,并且必須開(kāi)啟事務(wù)恬偷,在begin與commit之間才生效。

要測(cè)試for update的鎖表情況帘睦,可以利用MySQL的Command Mode袍患,開(kāi)啟二個(gè)視窗來(lái)做測(cè)試。

1竣付、只根據(jù)主鍵進(jìn)行查詢(xún)诡延,并且查詢(xún)到數(shù)據(jù),主鍵字段產(chǎn)生行鎖古胆。

begin;

select * from goods where id = 1 for update;

commit;

2肆良、只根據(jù)主鍵進(jìn)行查詢(xún),沒(méi)有查詢(xún)到數(shù)據(jù)逸绎,不產(chǎn)生鎖惹恃。

begin;

select * from goods where id = 1 for update;

commit;

3、根據(jù)主鍵棺牧、非主鍵含索引(name)進(jìn)行查詢(xún)巫糙,并且查詢(xún)到數(shù)據(jù),主鍵字段產(chǎn)生行鎖颊乘,name字段產(chǎn)生行鎖参淹。

begin;

select * from goods where id = 1 and name='prod11' for update;

commit;

4、根據(jù)主鍵疲牵、非主鍵含索引(name)進(jìn)行查詢(xún)承二,沒(méi)有查詢(xún)到數(shù)據(jù),不產(chǎn)生鎖纲爸。

begin;

select * from goods where id = 1 and name='prod12' for update;

commit;

5亥鸠、根據(jù)主鍵、非主鍵不含索引(name)進(jìn)行查詢(xún)识啦,并且查詢(xún)到數(shù)據(jù)负蚊,如果其他線程按主鍵字段進(jìn)行再次查詢(xún),則主鍵字段產(chǎn)生行鎖颓哮,如果其他線程按非主鍵不含索引字段進(jìn)行查詢(xún)家妆,則非主鍵不含索引字段產(chǎn)生表鎖,如果其他線程按非主鍵含索引字段進(jìn)行查詢(xún)冕茅,則非主鍵含索引字段產(chǎn)生行鎖伤极,如果索引值是枚舉類(lèi)型蛹找,mysql也會(huì)進(jìn)行表鎖,這段話(huà)有點(diǎn)拗口哨坪,大家仔細(xì)理解一下庸疾。

begin;

select * from goods where id = 1 and name='prod11' for update;

commit;

6、根據(jù)主鍵当编、非主鍵不含索引(name)進(jìn)行查詢(xún)届慈,沒(méi)有查詢(xún)到數(shù)據(jù),不產(chǎn)生鎖忿偷。

begin;

select * from goods where id = 1 and name='prod12' for update;

commit;

7金顿、根據(jù)非主鍵含索引(name)進(jìn)行查詢(xún),并且查詢(xún)到數(shù)據(jù)鲤桥,name字段產(chǎn)生行鎖揍拆。

begin;

select * from goods where name='prod11' for update;

commit;

8、根據(jù)非主鍵含索引(name)進(jìn)行查詢(xún)茶凳,沒(méi)有查詢(xún)到數(shù)據(jù)礁凡,不產(chǎn)生鎖。

begin;

select * from goods where name='prod11' for update;

commit;

9慧妄、根據(jù)非主鍵不含索引(stock)進(jìn)行查詢(xún),并且查詢(xún)到數(shù)據(jù)剪芍,stock字段產(chǎn)生表鎖塞淹。

begin;

select * from goods where stock='1000' for update;

commit;

10、根據(jù)非主鍵不含索引(stock)進(jìn)行查詢(xún)罪裹,沒(méi)有查詢(xún)到數(shù)據(jù)饱普,stock字段產(chǎn)生表鎖。

begin;

select * from goods where stock='2000' for update;

commit;

11状共、只根據(jù)主鍵進(jìn)行查詢(xún)套耕,查詢(xún)條件為不等于,并且查詢(xún)到數(shù)據(jù)峡继,主鍵字段產(chǎn)生表鎖冯袍。

begin;

select * from goods where id <> 1 for update;

commit;

12、只根據(jù)主鍵進(jìn)行查詢(xún)碾牌,查詢(xún)條件為不等于康愤,沒(méi)有查詢(xún)到數(shù)據(jù),主鍵字段產(chǎn)生表鎖舶吗。

begin;

select * from goods where id <> 1 for update;

commit;

13征冷、只根據(jù)主鍵進(jìn)行查詢(xún),查詢(xún)條件為 like誓琼,并且查詢(xún)到數(shù)據(jù)检激,主鍵字段產(chǎn)生表鎖肴捉。

begin;

select * from goods where id like '1' for update;

commit;

14、只根據(jù)主鍵進(jìn)行查詢(xún)叔收,查詢(xún)條件為 like齿穗,沒(méi)有查詢(xún)到數(shù)據(jù),主鍵字段產(chǎn)生表鎖今穿。

begin;

select * from goods where id like '1' for update;

commit;

測(cè)試環(huán)境

數(shù)據(jù)庫(kù)版本:5.1.48-community

數(shù)據(jù)庫(kù)引擎:InnoDB Supports transactions, row-level locking, and foreign keys

數(shù)據(jù)庫(kù)隔離策略:REPEATABLE-READ(系統(tǒng)缤灵、會(huì)話(huà))

總結(jié)

1、InnoDB行鎖是通過(guò)給索引上的索引項(xiàng)加鎖來(lái)實(shí)現(xiàn)的蓝晒,只有通過(guò)索引條件檢索數(shù)據(jù)腮出,InnoDB才使用行級(jí)鎖,否則芝薇,InnoDB將使用表鎖胚嘲。

2、由于MySQL的行鎖是針對(duì)索引加的鎖洛二,不是針對(duì)記錄加的鎖馋劈,所以雖然是訪問(wèn)不同行的記錄,但是如果是使用相同的索引鍵晾嘶,是會(huì)出現(xiàn)鎖沖突的妓雾。應(yīng)用設(shè)計(jì)的時(shí)候要注意這一點(diǎn)。

3垒迂、當(dāng)表有多個(gè)索引的時(shí)候械姻,不同的事務(wù)可以使用不同的索引鎖定不同的行,另外机断,不論是使用主鍵索引楷拳、唯一索引或普通索引,InnoDB都會(huì)使用行鎖來(lái)對(duì)數(shù)據(jù)加鎖吏奸。

4欢揖、即便在條件中使用了索引字段,但是否使用索引來(lái)檢索數(shù)據(jù)是由MySQL通過(guò)判斷不同執(zhí)行計(jì)劃的代價(jià)來(lái)決定的奋蔚,如果MySQL認(rèn)為全表掃描效率更高她混,比如對(duì)一些很小的表,它就不會(huì)使用索引旺拉,這種情況下InnoDB將使用表鎖产上,而不是行鎖。因此蛾狗,在分析鎖沖突時(shí)晋涣,別忘了檢查SQL的執(zhí)行計(jì)劃,以確認(rèn)是否真正使用了索引沉桌。

5谢鹊、檢索值的數(shù)據(jù)類(lèi)型與索引字段不同算吩,雖然MySQL能夠進(jìn)行數(shù)據(jù)類(lèi)型轉(zhuǎn)換,但卻不會(huì)使用索引佃扼,從而導(dǎo)致InnoDB使用表鎖偎巢。通過(guò)用explain檢查兩條SQL的執(zhí)行計(jì)劃,我們可以清楚地看到了這一點(diǎn)兼耀。

https://www.cnblogs.com/GreenLeaves/p/6576646.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末压昼,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子瘤运,更是在濱河造成了極大的恐慌窍霞,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拯坟,死亡現(xiàn)場(chǎng)離奇詭異但金,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)郁季,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)冷溃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人梦裂,你說(shuō)我怎么就攤上這事似枕。” “怎么了年柠?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵寨辩,是天一觀的道長(zhǎng)胰蝠。 經(jīng)常有香客問(wèn)我男娄,道長(zhǎng)檩淋,這世上最難降的妖魔是什么牵咙? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任派近,我火速辦了婚禮,結(jié)果婚禮上洁桌,老公的妹妹穿的比我還像新娘渴丸。我一直安慰自己,他們只是感情好另凌,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布谱轨。 她就那樣靜靜地躺著,像睡著了一般吠谢。 火紅的嫁衣襯著肌膚如雪土童。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,198評(píng)論 1 299
  • 那天工坊,我揣著相機(jī)與錄音献汗,去河邊找鬼敢订。 笑死,一個(gè)胖子當(dāng)著我的面吹牛罢吃,可吹牛的內(nèi)容都是我干的楚午。 我是一名探鬼主播,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼尿招,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼矾柜!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起就谜,我...
    開(kāi)封第一講書(shū)人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤怪蔑,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后吁伺,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體饮睬,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年篮奄,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了捆愁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡窟却,死狀恐怖昼丑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情夸赫,我是刑警寧澤菩帝,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站茬腿,受9級(jí)特大地震影響呼奢,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜切平,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一握础、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧悴品,春花似錦禀综、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至届氢,卻和暖如春欠窒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背退子。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工贱迟, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留姐扮,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓衣吠,卻偏偏與公主長(zhǎng)得像茶敏,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子缚俏,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • InnoDB 鎖 數(shù)據(jù)庫(kù)使用鎖是為了支持更好的并發(fā)惊搏,提供數(shù)據(jù)的完整性和一致性。InnoDB是一個(gè)支持行鎖的存儲(chǔ)引擎...
    大富帥閱讀 1,470評(píng)論 0 4
  • 使用mysql悲觀鎖解決并發(fā)問(wèn)題 最近學(xué)習(xí)了一下數(shù)據(jù)庫(kù)的悲觀鎖和樂(lè)觀鎖忧换,根據(jù)自己的理解和網(wǎng)上參考資料總結(jié)如下: 悲...
    可可西里的星星閱讀 95評(píng)論 0 0
  • 5.數(shù)據(jù)庫(kù) 要求: 能熟練使用恬惯、部署、調(diào)優(yōu)亚茬、問(wèn)題排查酪耳、懂原理 1.關(guān)系型數(shù)據(jù)庫(kù): MySQL/Oracle/Pos...
    BigJeffWang閱讀 323評(píng)論 0 1
  • 復(fù)雜SQL的子查詢(xún)中的多表關(guān)聯(lián),執(zhí)行計(jì)劃中表連接順序不一樣刹缝,導(dǎo)致sql執(zhí)行效率降低碗暗。 案例1: 問(wèn)題背景:測(cè)試環(huán)境...
    0d42206c1f8d閱讀 947評(píng)論 0 0
  • for update 的使用體現(xiàn)在事務(wù)中 前言 近期開(kāi)發(fā)與錢(qián)相關(guān)的項(xiàng)目,在高并發(fā)場(chǎng)景下對(duì)數(shù)據(jù)的準(zhǔn)確行有很高的要求梢夯,...
    小陳阿飛閱讀 4,533評(píng)論 0 3