|
Mysql共享鎖、排他鎖、悲觀鎖、樂(lè)觀鎖及其使用場(chǎng)景 一、相關(guān)名詞 |--表級(jí)鎖(鎖定整個(gè)表) |--頁(yè)級(jí)鎖(鎖定一頁(yè)) |--行級(jí)鎖(鎖定一行) |--共享鎖(S鎖,MyISAM 叫做讀鎖) |--排他鎖(X鎖,MyISAM 叫做寫(xiě)鎖) |--悲觀鎖(抽象性,不真實(shí)存在這個(gè)鎖) |--樂(lè)觀鎖(抽象性,不真實(shí)存在這個(gè)鎖)
二、InnoDB與MyISAM Mysql 在5.5之前默認(rèn)使用 MyISAM 存儲(chǔ)引擎,之后使用 InnoDB 。查看當(dāng)前存儲(chǔ)引擎: show variables like '%storage_engine%'; 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)有了索引,找到某一條記錄就得掃描全表,要掃描全表,就得鎖定表。
三、共享鎖與排他鎖 1.首先說(shuō)明:數(shù)據(jù)庫(kù)的增刪改操作默認(rèn)都會(huì)加排他鎖,而查詢不會(huì)加任何鎖。 |--共享鎖:對(duì)某一資源加共享鎖,自身可以讀該資源,其他人也可以讀該資源(也可以再繼續(xù)加共享鎖,即 共享鎖可多個(gè)共存),但無(wú)法修改。要想修改就必須等所有共享鎖都釋放完之后。語(yǔ)法為: select * from table lock in share mode |--排他鎖:對(duì)某一資源加排他鎖,自身可以進(jìn)行增刪改查,其他人無(wú)法進(jìn)行任何操作。語(yǔ)法為: select * from table for update
2.下面援引例子說(shuō)明(援自:http://blog.csdn.net/samjustin1/article/details/52210125): 這里用T1代表一個(gè)數(shù)據(jù)庫(kù)執(zhí)行請(qǐng)求,T2代表另一個(gè)請(qǐng)求,也可以理解為T1為一個(gè)線程,T2 為另一個(gè)線程。
例1:------------------------------------------------------------------------------------------------------------------------------------- T1:select * from table lock in share mode(假設(shè)查詢會(huì)花很長(zhǎng)時(shí)間,下面的例子也都這么假設(shè)) T2:update table set column1='hello'
過(guò)程: T1運(yùn)行(并加共享鎖) T2運(yùn)行 If T1還沒(méi)執(zhí)行完 T2等...... else 鎖被釋放 T2執(zhí)行 end if
T2 之所以要等,是因?yàn)?T2 在執(zhí)行 update 前,試圖對(duì) table 表加一個(gè)排他鎖,而數(shù)據(jù)庫(kù)規(guī)定同一資源上不能同時(shí)共存共享鎖和排他鎖。所以 T2 必須等 T1 執(zhí)行完,釋放了共享鎖,才能加上排他鎖,然后才能開(kāi)始執(zhí)行 update 語(yǔ)句。
例2:------------------------------------------------------------------------------------------------------------------------------------- T1:select * from table lock in share mode T2:select * from table lock in share mode
這里T2不用等待T1執(zhí)行完,而是可以馬上執(zhí)行。
分析: T1運(yùn)行,則 table 被加鎖,比如叫l(wèi)ockA,T2運(yùn)行,再對(duì) table 加一個(gè)共享鎖,比如叫l(wèi)ockB,兩個(gè)鎖是可以同時(shí)存在于同一資源上的(比如同一個(gè)表上)。這被稱為共享鎖與共享鎖兼容。這意味著共享鎖不阻止其它人同時(shí)讀資源,但阻止其它人修改資源。
例3:------------------------------------------------------------------------------------------------------------------------------------- T1:select * from table lock in share mode T2:select * from table lock in share mode T3:update table set column1='hello'
T2 不用等 T1 運(yùn)行完就能運(yùn)行,T3 卻要等 T1 和 T2 都運(yùn)行完才能運(yùn)行。因?yàn)?T3 必須等 T1 和 T2 的共享鎖全部釋放才能進(jìn)行加排他鎖然后執(zhí)行 update 操作。
例4:(死鎖的發(fā)生)----------------------------------------------------------------------------------------------------------------- T1:begin tran select * from table lock in share mode update table set column1='hello' T2:begin tran select * from table lock in share mode update table set column1='world'
假設(shè) T1 和 T2 同時(shí)達(dá)到 select,T1 對(duì) table 加共享鎖,T2 也對(duì) table 加共享鎖,當(dāng) T1 的 select 執(zhí)行完,準(zhǔn)備執(zhí)行 update 時(shí),根據(jù)鎖機(jī)制,T1 的共享鎖需要升級(jí)到排他鎖才能執(zhí)行接下來(lái)的 update。在升級(jí)排他鎖前,必須等 table 上的其它共享鎖(T2)釋放,同理,T2 也在等 T1 的共享鎖釋放。于是死鎖產(chǎn)生了。
例5:------------------------------------------------------------------------------------------------------------------------------------- T1:begin tran update table set column1='hello' where id=10 T2:begin tran update table set column1='world' where id=20
這種語(yǔ)句雖然最為常見(jiàn),很多人覺(jué)得它有機(jī)會(huì)產(chǎn)生死鎖,但實(shí)際上要看情況 |--如果id是主鍵(默認(rèn)有主鍵索引),那么T1會(huì)一下子找到該條記錄(id=10的記錄),然后對(duì)該條記錄加排他鎖,T2,同樣,一下子通過(guò)索引定位到記錄,然后對(duì)id=20的記錄加排他鎖,這樣T1和T2各更新各的,互不影響。T2也不需要等。 |--如果id是普通的一列,沒(méi)有索引。那么當(dāng)T1對(duì)id=10這一行加排他鎖后,T2為了找到id=20,需要對(duì)全表掃描。但因?yàn)門1已經(jīng)為一條記錄加了排他鎖,導(dǎo)致T2的全表掃描進(jìn)行不下去(其實(shí)是因?yàn)門1加了排他鎖,數(shù)據(jù)庫(kù)默認(rèn)會(huì)為該表加意向鎖,T2要掃描全表,就得等該意向鎖釋放,也就是T1執(zhí)行完成),就導(dǎo)致T2等待。
死鎖怎么解決呢?一種辦法是,如下: 例6:------------------------------------------------------------------------------------------------------------------------------------- T1:begin tran select * from table for update update table set column1='hello' T2:begin tran select * from table for update update table set column1='world'
這樣,當(dāng) T1 的 select 執(zhí)行時(shí),直接對(duì)表加上了排他鎖,T2 在執(zhí)行 select 時(shí),就需要等 T1 事物完全執(zhí)行完才能執(zhí)行。排除了死鎖發(fā)生。但當(dāng)?shù)谌齻€(gè) user 過(guò)來(lái)想執(zhí)行一個(gè)查詢語(yǔ)句時(shí),也因?yàn)榕潘i的存在而不得不等待,第四個(gè)、第五個(gè) user 也會(huì)因此而等待。在大并發(fā)情況下,讓大家等待顯得性能就太友好了。 所以,有些數(shù)據(jù)庫(kù)這里引入了更新鎖(如Mssql,注意:Mysql不存在更新鎖)。
例7:------------------------------------------------------------------------------------------------------------------------------------- T1:begin tran select * from table [加更新鎖操作] update table set column1='hello' T2:begin tran select * from table [加更新鎖操作] update table set column1='world'
更新鎖其實(shí)就可以看成排他鎖的一種變形,只是它也允許其他人讀(并且還允許加共享鎖)。但不允許其他操作,除非我釋放了更新鎖。T1 執(zhí)行 select,加更新鎖。T2 運(yùn)行,準(zhǔn)備加更新鎖,但發(fā)現(xiàn)已經(jīng)有一個(gè)更新鎖在那兒了,只好等。當(dāng)后來(lái)有 user3、user4...需要查詢 table 表中的數(shù)據(jù)時(shí),并不會(huì)因?yàn)?T1 的 select 在執(zhí)行就被阻塞,照樣能查詢,相比起例6,這提高了效率。
后面還有意向鎖和計(jì)劃鎖:
我們知道,如果要對(duì)整個(gè)表加鎖,需保證該表內(nèi)目前不存在任何鎖。 因此,如果需要對(duì)整個(gè)表加鎖,那么就可以根據(jù):檢查意向鎖是否被占用,來(lái)知道表內(nèi)目前是否存在共享鎖或排他鎖了。而不需要再一行行地去檢查每一行是否被加鎖。
四、樂(lè)觀鎖與悲觀鎖 首先說(shuō)明,樂(lè)觀鎖和悲觀鎖都是針對(duì)讀(select)來(lái)說(shuō)的。 案例: 某商品,用戶購(gòu)買后庫(kù)存數(shù)應(yīng)-1,而某兩個(gè)或多個(gè)用戶同時(shí)購(gòu)買,此時(shí)三個(gè)執(zhí)行程序均同時(shí)讀得庫(kù)存為“n”,之后進(jìn)行了一些操作,最后將均執(zhí)行update table set 庫(kù)存數(shù)=n-1,那么,很顯然這是錯(cuò)誤的。
解決:
總結(jié):對(duì)于以上,可以看得出來(lái)樂(lè)觀鎖和悲觀鎖的區(qū)別:
也就是一句話:讀用樂(lè)觀鎖,寫(xiě)用悲觀鎖。 |
|
|
來(lái)自: 觀審美2 > 《.·技術(shù)》