聲明:本欄目所使用的素材都是凱哥學(xué)堂VIP學(xué)員所寫,學(xué)員有權(quán)匿名,對(duì)文章有最終解釋權(quán);凱哥學(xué)堂旨在促進(jìn)VIP學(xué)員互相學(xué)習(xí)的基礎(chǔ)上公開筆記。
之前我們介紹了行級(jí)鎖,顧名思義行級(jí)鎖就只是鎖住一行或多行數(shù)據(jù),因?yàn)獒槍?duì)的是行去鎖的,因?yàn)橐粋€(gè)表格內(nèi)會(huì)有很多行數(shù)據(jù),要在這些數(shù)據(jù)中去鎖定其中幾行數(shù)據(jù),是比較耗費(fèi)資源。而表級(jí)鎖則是可以鎖住整個(gè)表,所以相對(duì)于行級(jí)來說沒那么耗費(fèi)資源,表級(jí)鎖有兩個(gè)模式:只讀模式和只寫模式,這和文件權(quán)限里的只讀只寫有點(diǎn)類似。 在一般情況下表格鎖并不經(jīng)常使用,在這里只是介紹一下如何使用表級(jí)鎖,和解鎖表級(jí)鎖,而且表級(jí)鎖的資料都可以在網(wǎng)絡(luò)上查找到,所以了解一下即可,在mysql官方也有表格鎖語法的文檔:
https://dev.mysql.com/doc/refman/5.6/en/lock-tables.html
LOCK TABLES 表名 READ
示例:
我們?cè)俅蜷_一個(gè)客戶端來看看能否使用SELECT語句查詢這個(gè)帶有表級(jí)鎖的表格的數(shù)據(jù):
因?yàn)槲覀兪褂玫氖侵蛔x模式的表級(jí)鎖,自然每個(gè)用戶都可以讀取、查詢這個(gè)表格的數(shù)據(jù),那么我們可以嘗試一下update這會(huì)對(duì)表格數(shù)據(jù)修改的語句能否執(zhí)行:
在行級(jí)鎖里即便某些行數(shù)據(jù)被上鎖了也還是能夠使用insert語句插入數(shù)據(jù)的,那么我們?cè)囈幌略诒砀矜i里是否能行得通:
從結(jié)果可以得知在表級(jí)鎖的只讀模式下,是不允許任何用戶對(duì)上鎖的表格進(jìn)行任何的修改的。
自然的delete語句也無法使用:
那么如何解鎖呢?看看解鎖時(shí)會(huì)發(fā)生什么,解鎖表級(jí)鎖的語法很簡單:
UNLOCK TABLES
示例:
使用只寫模式的表級(jí)鎖,語法:
LOCK TABLES 表名 WRITE
示例:
在表級(jí)鎖的只寫模式里,只有上鎖用戶可以對(duì)表格進(jìn)行寫入數(shù)據(jù),其他用戶是不可以寫入數(shù)據(jù)的,其他用戶就連使用SELECT語句查詢數(shù)據(jù)都不可以:
上鎖用戶可以使用insert語句插入數(shù)據(jù),其他用戶則不允許這個(gè)操作:
update語句也是一樣的:
還有delete語句:
如果用戶給一張表格上了表級(jí)鎖,那么這個(gè)用戶在給這個(gè)表格解鎖之前就只能操作這個(gè)表格,數(shù)據(jù)庫里的其他表格均不可以進(jìn)行任何的操作,如果操作就會(huì)報(bào)錯(cuò):
只寫模式的解鎖語法是一樣的,都是UNLOCK TABLES:
總結(jié)一下表級(jí)鎖,表級(jí)鎖就是針對(duì)表格進(jìn)行鎖定,相對(duì)于行級(jí)鎖沒那么耗資源,表級(jí)鎖有兩個(gè)模式,只讀模式和只寫模式,只讀模式下上鎖用戶和其他用戶都只能查詢數(shù)據(jù)不能寫入數(shù)據(jù),只寫模式下上鎖用戶可以進(jìn)行查詢數(shù)據(jù)和寫入數(shù)據(jù),其他用戶既不能查詢數(shù)據(jù),也不能寫入數(shù)據(jù),執(zhí)行任何SQL語句都會(huì)進(jìn)入等待狀態(tài),一直等到表格解鎖為止,當(dāng)表格解鎖的時(shí)候在等待中的事務(wù)會(huì)馬上被執(zhí)行。某個(gè)用戶對(duì)某個(gè)表格使用了表級(jí)鎖的話,就只能操作那個(gè)表格,數(shù)據(jù)庫里的其他表格均不可進(jìn)行任何操作。
悲觀鎖(Pessimistic Lock)是一種概念、解決某些問題的模式,并不是一種特定的機(jī)制,悲觀鎖,正如其名,它指的是對(duì)數(shù)據(jù)被外界(包括本系統(tǒng)當(dāng)前的其他事務(wù),以及來自外部系統(tǒng)的事務(wù)處理)修改持保守態(tài)度(悲觀),因此,在整個(gè)數(shù)據(jù)處理過程中,將數(shù)據(jù)處于鎖定狀態(tài)。悲觀鎖的實(shí)現(xiàn),往往依靠數(shù)據(jù)庫提供的鎖機(jī)制(也只有數(shù)據(jù)庫層提供的鎖機(jī)制才能真正保證數(shù)據(jù)訪問的排他性,否則,即使在本系統(tǒng)中實(shí)現(xiàn)了加鎖機(jī)制,也無法保證外部系統(tǒng)不會(huì)修改數(shù)據(jù))。
所以簡單來說悲觀鎖就是很悲觀,每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人會(huì)修改,所以每次在拿數(shù)據(jù)的時(shí)候都會(huì)上鎖,這樣別人想拿這個(gè)數(shù)據(jù)就會(huì)block直到它拿到鎖。傳統(tǒng)的關(guān)系型數(shù)據(jù)庫里邊就用到了很多這種鎖機(jī)制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。
例如之前我們做的火車票務(wù)系統(tǒng)的小案例,就是使用的悲觀鎖的方式,在我們的代碼里都是借助于數(shù)據(jù)庫自帶的鎖機(jī)制完成的,當(dāng)用戶A在購票時(shí)用戶B就不能夠購票,或者購票失敗,這就是在整個(gè)數(shù)據(jù)處理過程中,將數(shù)據(jù)處于鎖定狀態(tài),具有很明顯的排他性(悲觀)。 代碼:
悲觀并發(fā)控制實(shí)際上是“先取鎖再訪問”的保守策略,為數(shù)據(jù)處理的安全提供了保證。但是在效率方面,處理加鎖的機(jī)制會(huì)讓數(shù)據(jù)庫產(chǎn)生額外的開銷,還有增加產(chǎn)生死鎖的機(jī)會(huì);另外,在只讀型事務(wù)處理中由于不會(huì)產(chǎn)生沖突,也沒必要使用鎖,這樣做只能增加系統(tǒng)負(fù)載;還有會(huì)降低了并行性,一個(gè)事務(wù)如果鎖定了某行數(shù)據(jù),其他事務(wù)就必須等待該事務(wù)處理完才可以處理那數(shù)據(jù)。
樂觀鎖( Optimistic Locking ) 相對(duì)悲觀鎖而言,樂觀鎖假設(shè)認(rèn)為數(shù)據(jù)一般情況下不會(huì)造成沖突,所以在數(shù)據(jù)進(jìn)行提交更新的時(shí)候,才會(huì)正式對(duì)數(shù)據(jù)的沖突與否進(jìn)行檢測(cè),如果發(fā)現(xiàn)沖突了,則讓返回用戶錯(cuò)誤的信息,讓用戶決定如何去做。 相對(duì)于悲觀鎖,在對(duì)數(shù)據(jù)庫進(jìn)行處理的時(shí)候,樂觀鎖并不會(huì)使用數(shù)據(jù)庫提供的鎖機(jī)制。一般的實(shí)現(xiàn)樂觀鎖的方式就是記錄數(shù)據(jù)版本。
數(shù)據(jù)版本,為數(shù)據(jù)增加的一個(gè)版本標(biāo)識(shí)。當(dāng)讀取數(shù)據(jù)時(shí),將版本標(biāo)識(shí)的值一同讀出,數(shù)據(jù)每更新一次,同時(shí)對(duì)版本標(biāo)識(shí)進(jìn)行更新。當(dāng)我們提交更新的時(shí)候,判斷數(shù)據(jù)庫表對(duì)應(yīng)記錄的當(dāng)前版本信息與第一次取出來的版本標(biāo)識(shí)進(jìn)行比對(duì),如果數(shù)據(jù)庫表當(dāng)前版本號(hào)與第一次取出來的版本標(biāo)識(shí)值相等,則予以更新,否則認(rèn)為是過期數(shù)據(jù)。
所以實(shí)際上樂觀鎖和悲觀鎖一樣也是一種概念、解決某些業(yè)務(wù)需求的模式,并不是一種特定的機(jī)制,樂觀鎖的主要實(shí)現(xiàn)方式是我們開發(fā)人員自己通過在數(shù)據(jù)庫中增加一條存儲(chǔ)數(shù)據(jù)版本的列,然后通過代碼來判斷這些數(shù)據(jù)的版本,而不是借助數(shù)據(jù)庫自帶的鎖來完成的一種鎖定數(shù)據(jù)的模式。
例如我們假設(shè)一種情況:用戶A和用戶B同時(shí)在ATM機(jī)里往一個(gè)賬戶余額2000元的賬戶取款,兩個(gè)用戶都查詢到賬戶還有2000元,所以用戶A要取款1500,用戶B要取款1000,然后這兩個(gè)取款事務(wù)會(huì)同時(shí)提交,如果這個(gè)銀行的系統(tǒng)邏輯寫得不夠好的話,就會(huì)出現(xiàn)2000-1500-1000=-500的結(jié)果,賬戶余額就會(huì)出現(xiàn)負(fù)數(shù)。
示意圖:
在這種取款的情況下,如果使用悲觀鎖來鎖住數(shù)據(jù)的話,由于其排他性,那么另外一個(gè)用戶就無法查詢賬戶余額,只能處于等待狀態(tài),因?yàn)樵诒^鎖里在事務(wù)結(jié)束之前數(shù)據(jù)都是處于鎖定狀態(tài),而且在銀行在這種數(shù)據(jù)量大的地方,使用共享鎖這種行級(jí)鎖也耗費(fèi)資源。所以就需要用到樂觀鎖了,樂觀鎖只有在操作提交的時(shí)候才會(huì)去鎖定數(shù)據(jù)。在樂觀鎖中我們可以給數(shù)據(jù)設(shè)定一個(gè)版本號(hào),一旦這個(gè)數(shù)據(jù)發(fā)生修改,版本號(hào)就會(huì)發(fā)生變化,每一個(gè)操作都會(huì)先判斷版本號(hào)是否是最新的版本號(hào),不是的話就不允許操作,在樂觀鎖的實(shí)現(xiàn)過程中我們并不會(huì)使用到數(shù)據(jù)庫自帶的鎖,所以用戶們都可以任意的查詢或提交操作。
示意圖:
下面我們做一個(gè)簡單的取款系統(tǒng)來演示如何實(shí)現(xiàn)樂觀鎖:
先準(zhǔn)備一個(gè)表格里面填充一行數(shù)據(jù):
代碼示例:
運(yùn)行結(jié)果:
樂觀并發(fā)控制相信事務(wù)之間的數(shù)據(jù)競爭(data race)的概率是比較小的,因此盡可能直接做下去,直到提交的時(shí)候才去鎖定,所以不會(huì)產(chǎn)生任何鎖和死鎖。但如果直接簡單這么做,還是有可能會(huì)遇到不可預(yù)期的結(jié)果,例如兩個(gè)事務(wù)都讀取了數(shù)據(jù)庫的某一行,經(jīng)過修改以后寫回?cái)?shù)據(jù)庫,這時(shí)就遇到了問題。
臟讀就是指當(dāng)一個(gè)事務(wù)正在訪問數(shù)據(jù),并且對(duì)數(shù)據(jù)進(jìn)行了修改,而這種修改還沒有提交到數(shù)據(jù)庫中,這時(shí),另外一個(gè)事務(wù)也訪問這個(gè)數(shù)據(jù),然后使用了這個(gè)數(shù)據(jù)。因?yàn)檫@個(gè)【學(xué)Java,到凱哥學(xué)堂kaige123.com】數(shù)據(jù)是還沒有提交的數(shù)據(jù),那么另外一個(gè)事務(wù)讀到的這個(gè)數(shù)據(jù)是臟數(shù)據(jù)(Dirty Data),依據(jù)臟數(shù)據(jù)所做的操作可能是不正確的。
在一個(gè)事務(wù)內(nèi),多次讀同一個(gè)數(shù)據(jù)。在這個(gè)事務(wù)還沒有結(jié)束時(shí),另一個(gè)事務(wù)也訪問該同一數(shù)據(jù)。那么,在第一個(gè)事務(wù)的兩次讀數(shù)據(jù)之間。由于第二個(gè)事務(wù)的修改,那么第一個(gè)事務(wù)讀到的數(shù)據(jù)可能不一樣,這樣就發(fā)生了在一個(gè)事務(wù)內(nèi)兩次讀到的數(shù)據(jù)是不一樣的,因此稱為不可重復(fù)讀,即原始讀取不可重復(fù)。
幻讀是指當(dāng)事務(wù)不是獨(dú)立執(zhí)行時(shí)發(fā)生的一種現(xiàn)象,例如第一個(gè)事務(wù)對(duì)一個(gè)表中的數(shù)據(jù)進(jìn)行了修改,比如這種修改涉及到表中的“全部數(shù)據(jù)行”。同時(shí),第二個(gè)事務(wù)也修改這個(gè)表中的數(shù)據(jù),這種修改是向表中插入“一行新數(shù)據(jù)”。那么,以后就會(huì)發(fā)生操作第一個(gè)事務(wù)的用戶發(fā)現(xiàn)表中還存在沒有修改的數(shù)據(jù)行,就好象發(fā)生了幻覺一樣.一般解決幻讀的方法是增加范圍鎖RangeS,鎖定檢索范圍為只讀,這樣就避免了幻讀。