本篇文章帶大家了解一下Mysql中的鎖,搞懂共享鎖、排他鎖、悲觀鎖、樂(lè)觀鎖,并聊聊使用場(chǎng)景,希望對(duì)大家有所幫助!
一、常見(jiàn)鎖類(lèi)型
- 表級(jí)鎖,鎖定整張表
- 頁(yè)級(jí)鎖,鎖定一頁(yè)
- 行級(jí)鎖,鎖定一行
- 共享鎖,也叫S鎖,在MyISAM中也叫讀鎖
- 排他鎖,也叫X鎖,在MyISAM中也叫寫(xiě)鎖
- 悲觀鎖,抽象性質(zhì),其實(shí)不真實(shí)存在
- 樂(lè)觀鎖,抽象性質(zhì),其實(shí)不真實(shí)存在

二、Mysql引擎介紹
- 其實(shí)mysql中的引擎有很多種類(lèi),其中InnoDB和MyISAM引擎最常用
- 在mysql5.5版本前默認(rèn)使用MyISAM引擎,之后使用InnoDB引擎
- 查看數(shù)據(jù)庫(kù)引擎命令如下
show variables like '%storage_engine%';
三、常用引擎間的區(qū)別
-
MyISAM 操作數(shù)據(jù)都是使用的表鎖,你更新一條記錄就要鎖整個(gè)表,導(dǎo)致性能較低,并發(fā)不高。當(dāng)然同時(shí)它也不會(huì)存在死鎖問(wèn)題。
-
而 InnoDB 與 MyISAM 的最大不同有兩點(diǎn):一是 InnoDB 支持事務(wù);二是 InnoDB 采用了行級(jí)鎖。
-
在 Mysql 中,行級(jí)鎖并不是直接鎖記錄,而是鎖索引。索引分為主鍵索引和非主鍵索引兩種,如果一條sql 語(yǔ)句操作了主鍵索引,Mysql 就會(huì)鎖定這條主鍵索引;如果一條語(yǔ)句操作了非主鍵索引,MySQL會(huì)先鎖定該非主鍵索引,再鎖定相關(guān)的主鍵索引。
-
InnoDB 行鎖是通過(guò)給索引項(xiàng)加鎖實(shí)現(xiàn)的,如果沒(méi)有索引,InnoDB 會(huì)通過(guò)隱藏的聚簇索引來(lái)對(duì)記錄加鎖。也就是說(shuō):如果不通過(guò)索引條件檢索數(shù)據(jù),那么InnoDB將對(duì)表中所有數(shù)據(jù)加鎖,實(shí)際效果跟表鎖一樣。因?yàn)闆](méi)有了索引,找到某一條記錄就得掃描全表,要掃描全表,就得鎖定表。
四、共享鎖與排他鎖
-
數(shù)據(jù)庫(kù)的增刪改操作默認(rèn)都會(huì)加排他鎖,而查詢不會(huì)加任何鎖。
-
共享鎖:對(duì)某一資源加共享鎖,自身可以讀該資源,其他人也可以讀該資源(也可以再繼續(xù)加共享鎖,即 共享鎖可多個(gè)共存),但無(wú)法修改。要想修改就必須等所有共享鎖都釋放完之后。
-
排他鎖:對(duì)某一資源加排他鎖,自身可以進(jìn)行增刪改查,其他人無(wú)法進(jìn)行任何操作。
//共享鎖 select * from 表名 lock in share mode //排他鎖 select * from 表名 for update
五、排他鎖的實(shí)際應(yīng)用
- 這里我們以兩個(gè)操作數(shù)據(jù)庫(kù)的請(qǐng)求為例,假設(shè)這兩個(gè)請(qǐng)求分別為T(mén)1和T2
- 假設(shè)T1為查詢請(qǐng)求,而T2為更新數(shù)據(jù)請(qǐng)求,在T1查詢很長(zhǎng)時(shí)間的時(shí)候,還沒(méi)有返回結(jié)果,但是這時(shí)候T2過(guò)來(lái)請(qǐng)求更新了
- 這個(gè)流程應(yīng)該是: T1運(yùn)行加共享鎖、T2運(yùn)行、發(fā)現(xiàn)T1未完成等待其完成、T1完成、T2開(kāi)始執(zhí)行
- T2之所以要等待,是因?yàn)門(mén)2執(zhí)行更新的時(shí)候需要給表加排他鎖,但是數(shù)據(jù)庫(kù)規(guī)定,不能在同一資源上同時(shí)共存這兩種鎖,所以T2必須等T1執(zhí)行完,釋放鎖后,才可以正常操作
T1: select * from 表名 lock in share mode //假設(shè)還未返回結(jié)果 T2: update 表名 set name='autofelix'
六、共享鎖的實(shí)際應(yīng)用
- 如果T1和T2都是執(zhí)行的查詢,也就是都加共享鎖
- 這時(shí)候就不用等待,可以立馬執(zhí)行
- 因?yàn)橥毁Y源上可以同時(shí)存在多個(gè)共享鎖,也被稱(chēng)為,共享鎖與共享鎖兼容
- 意味著共享鎖不阻止其他人同時(shí)讀取資源,但是阻止其他人修改資源
T1: select * from table lock in share mode T2: select * from table lock in share mode
七、死鎖的發(fā)生
- 假設(shè)T1和T2都同時(shí)執(zhí)行2個(gè)資源操作,分別是查詢和更新數(shù)據(jù)
- 假設(shè)T1和T2同時(shí)達(dá)到select,T1對(duì)表加共享鎖,而T2也加上了共享鎖
- 當(dāng)T1的select執(zhí)行完畢,準(zhǔn)備執(zhí)行update時(shí)
- 根據(jù)鎖機(jī)制,T1的共享鎖必須升級(jí)到排他鎖才可以執(zhí)行接下來(lái)的update操作
- 在升級(jí)排他鎖之前,必須等T2的共享鎖釋放,同理,T2也在等T1的共享鎖釋放
- 于是都在等待對(duì)方的鎖釋放,導(dǎo)致程序卡死,這種情況就是死鎖
T1: 開(kāi)啟事務(wù),執(zhí)行查詢更新兩個(gè)操作 select * from table lock in share mode update table set column1='hello' T2: 開(kāi)啟事務(wù),執(zhí)行查詢更新兩個(gè)操作 select * from table lock in share mode update table set column1='world'
八、另一種發(fā)生死鎖的情景
- 當(dāng)T1和T2都是只執(zhí)行更新語(yǔ)句的時(shí)候
- 如下程序所示,這種語(yǔ)句非常的常見(jiàn),很多人覺(jué)得他會(huì)產(chǎn)生死鎖,其實(shí)要看情況
- 如果id是主鍵,由于主鍵機(jī)制,并不需要全表掃描,直接可以更新當(dāng)前數(shù)據(jù),所以不會(huì)產(chǎn)生死鎖
- 如果id是普通字段,那么當(dāng)T1加上排他鎖之后,T2為了找到id=20條數(shù)據(jù),必須進(jìn)行全表掃描,當(dāng)他掃到第10條的時(shí)候,發(fā)現(xiàn)這里有排他鎖,導(dǎo)致全表掃描進(jìn)行不下去,就會(huì)導(dǎo)致等待
T1: begin update table set content='hello' where id=10 T2: begin update table set content='world' where id=20
九、死鎖的解決方式
- 就是讓T1和T2順序執(zhí)行,比如T1在執(zhí)行完select后,立馬給自身加上排他鎖,這樣T2不得不等待T1執(zhí)行完才能繼續(xù)
- 但是如果有很多請(qǐng)求過(guò)來(lái)的話,都必須等待,這對(duì)用戶特別的不友好
- 所以,某些數(shù)據(jù)庫(kù)引入了另一種方式,叫做更新鎖,這里mysql除外,不存在更新鎖
- 更新鎖其實(shí)就是排他鎖的另一種實(shí)現(xiàn),只是他允許其他人讀的同時(shí)加共享鎖,但是不允許其他操作,除非釋放了更新鎖
- 流程大概如此: T1執(zhí)行完select加上更新鎖,T2執(zhí)行查詢完,準(zhǔn)備加更新鎖,發(fā)現(xiàn)已經(jīng)有了,就等待,其他請(qǐng)求過(guò)來(lái),如果查詢是不受影響的,但是更新才等待
- 這相比上面的查詢也要等待增加了效率
T1: begin select * from table for update update table set content='hello' T2: begin select * from table for update update table set content='world'
T1: begin select * from table [加更新鎖操作] update table set content='hello' T2: begin select * from table [加更新鎖操作] update table set content='world'
十、意向鎖和計(jì)劃鎖
- 計(jì)劃鎖與程序猿無(wú)關(guān),不需要了解
- 意向鎖,Innodb特有,分為意向共享鎖和意向排他鎖
- 意向共享鎖: 表示事務(wù)獲取共享鎖時(shí),必須先得獲取該表的意向共享鎖
- 意向排他鎖: 表示事務(wù)獲取排他鎖時(shí),必須先得獲取該表的意向排他鎖
- 我們知道要對(duì)整個(gè)表加鎖,必須保證表內(nèi)不存在任何鎖
- 如果一行行的去檢查是否加鎖,效率必然極低,這時(shí)候可以檢測(cè)意向鎖是否被占用即可
十一、樂(lè)觀鎖和悲觀鎖
- 樂(lè)觀鎖和悲觀鎖都是針對(duì)select而言的
- 比如在商品搶購(gòu)中,用戶購(gòu)買(mǎi)后庫(kù)存需要減1,而很多用戶同時(shí)購(gòu)買(mǎi)時(shí),讀出來(lái)的庫(kù)存數(shù)量一樣,然后多個(gè)用戶同時(shí)用該庫(kù)存去減1
- 這種做法必然會(huì)出現(xiàn)很大的漏洞,如果向在淘寶,京東出現(xiàn)這種情況,你就可以打包回家種地了
- 這種情況如何解決呢,其實(shí)可以使用悲觀鎖進(jìn)行解決,說(shuō)白了也就是排他鎖
- 用戶進(jìn)來(lái)查庫(kù)存的時(shí)候,就加上排他鎖,等他所有操作完成后,再釋放排他鎖,讓其他人進(jìn)來(lái)
- 不讓用戶等待,就可以使用樂(lè)觀鎖方式解決,樂(lè)觀鎖一般靠表的設(shè)計(jì)和時(shí)間戳來(lái)實(shí)現(xiàn)
- 一般是在表中添加version或者timestamp時(shí)間戳字段
- 這樣就會(huì)保證如果更新失敗,就表示有其他程序更新了數(shù)據(jù)庫(kù),就可以通過(guò)重試解決
update table set num=num-1 where id=10 and version=12
【