這篇文章主要介紹了MySQL鎖類(lèi)型和加鎖原理是什么,具有一定借鑒價(jià)值,需要的朋友可以參考下。希望大家閱讀完這篇文章后大有收獲。下面讓小編帶著大家一起了解一下。

創(chuàng)新互聯(lián)從2013年成立,先為容城等服務(wù)建站,容城等地企業(yè),進(jìn)行企業(yè)商務(wù)咨詢(xún)服務(wù)。為容城企業(yè)網(wǎng)站制作PC+手機(jī)+微官網(wǎng)三網(wǎng)同步一站式服務(wù)解決您的所有建站問(wèn)題。
首先對(duì)mysql鎖進(jìn)行劃分:
表級(jí)鎖是 MySQL 鎖中粒度最大的一種鎖,表示當(dāng)前的操作對(duì)整張表加鎖,資源開(kāi)銷(xiāo)比行鎖少,不會(huì)出現(xiàn)死鎖的情況,但是發(fā)生鎖沖突的概率很大。被大部分的mysql引擎支持,MyISAM和InnoDB都支持表級(jí)鎖,但是InnoDB默認(rèn)的是行級(jí)鎖。
表鎖由 MySQL Server 實(shí)現(xiàn),一般在執(zhí)行 DDL 語(yǔ)句時(shí)會(huì)對(duì)整個(gè)表進(jìn)行加鎖,比如說(shuō) ALTER TABLE 等操作。在執(zhí)行 SQL 語(yǔ)句時(shí),也可以明確指定對(duì)某個(gè)表進(jìn)行加鎖。
表鎖使用的是一次性鎖技術(shù),也就是說(shuō),在會(huì)話(huà)開(kāi)始的地方使用 lock 命令將后續(xù)需要用到的表都加上鎖,在表釋放前,只能訪問(wèn)這些加鎖的表,不能訪問(wèn)其他表,直到最后通過(guò) unlock tables 釋放所有表鎖。
除了使用 unlock tables 顯示釋放鎖之外,會(huì)話(huà)持有其他表鎖時(shí)執(zhí)行l(wèi)ock table 語(yǔ)句會(huì)釋放會(huì)話(huà)之前持有的鎖;會(huì)話(huà)持有其他表鎖時(shí)執(zhí)行 start transaction 或者 begin 開(kāi)啟事務(wù)時(shí),也會(huì)釋放之前持有的鎖。
共享鎖用法:
LOCK TABLE table_name [ AS alias_name ] READ復(fù)制代碼
排它鎖用法:
LOCK TABLE table_name [AS alias_name][ LOW_PRIORITY ] WRITE復(fù)制代碼
解鎖用法:
unlock tables;復(fù)制代碼
行級(jí)鎖是Mysql中鎖定粒度最細(xì)的一種鎖,表示只針對(duì)當(dāng)前操作的行進(jìn)行加鎖。行級(jí)鎖能大大減少數(shù)據(jù)庫(kù)操作的沖突。其加鎖粒度最小,但加鎖的開(kāi)銷(xiāo)也最大。有可能會(huì)出現(xiàn)死鎖的情況。行級(jí)鎖按照使用方式分為共享鎖和排他鎖。
不同存儲(chǔ)引擎的行鎖實(shí)現(xiàn)不同,后續(xù)沒(méi)有特別說(shuō)明,則行鎖特指 InnoDB 實(shí)現(xiàn)的行鎖。
在了解 InnoDB 的加鎖原理前,需要對(duì)其存儲(chǔ)結(jié)構(gòu)有一定的了解。InnoDB 是聚簇索引,也就是 B+樹(shù)的葉節(jié)點(diǎn)既存儲(chǔ)了主鍵索引也存儲(chǔ)了數(shù)據(jù)行。而 InnoDB 的二級(jí)索引的葉節(jié)點(diǎn)存儲(chǔ)的則是主鍵值,所以通過(guò)二級(jí)索引查詢(xún)數(shù)據(jù)時(shí),還需要拿對(duì)應(yīng)的主鍵去聚簇索引中再次進(jìn)行查詢(xún)。關(guān)于MySQL索引的詳細(xì)知識(shí)可以查看《MySQL索引底層數(shù)據(jù)結(jié)構(gòu)與算法》。
下面以?xún)蓷l SQL 的執(zhí)行為例,講解一下 InnoDB 對(duì)于單行數(shù)據(jù)的加鎖原理。
update user set age = 10 where id = 49; update user set age = 10 where name = 'Tom';復(fù)制代碼
第一條 SQL 使用主鍵索引來(lái)查詢(xún),則只需要在 id = 49 這個(gè)主鍵索引上加上寫(xiě)鎖;
第二條 SQL 則使用二級(jí)索引來(lái)查詢(xún),則首先在 name = Tom 這個(gè)索引上加寫(xiě)鎖,然后由于使用 InnoDB 二級(jí)索引還需再次根據(jù)主鍵索引查詢(xún),所以還需要在 id = 49 這個(gè)主鍵索引上加寫(xiě)鎖,如上圖所示。
也就是說(shuō)使用主鍵索引需要加一把鎖,使用二級(jí)索引需要在二級(jí)索引和主鍵索引上各加一把鎖。
根據(jù)索引對(duì)單行數(shù)據(jù)進(jìn)行更新的加鎖原理了解了,那如果更新操作涉及多個(gè)行呢,比如下面 SQL 的執(zhí)行場(chǎng)景。
update user set age = 10 where id > 49;復(fù)制代碼
這種場(chǎng)景下的鎖的釋放較為復(fù)雜,有多種的優(yōu)化方式,我對(duì)這塊暫時(shí)還沒(méi)有了解,還請(qǐng)知道的小伙伴在下方留言解釋。
頁(yè)級(jí)鎖是MySQL中鎖定粒度介于行級(jí)鎖和表級(jí)鎖中間的一種鎖。表級(jí)鎖速度快,但沖突多,行級(jí)沖突少,但速度慢。所以取了折衷的頁(yè)級(jí),一次鎖定相鄰的一組記錄。BDB支持頁(yè)級(jí)鎖。
共享鎖又稱(chēng)讀鎖,是讀取操作創(chuàng)建的鎖。其他用戶(hù)可以并發(fā)讀取數(shù)據(jù),但任何事務(wù)都不能對(duì)數(shù)據(jù)進(jìn)行修改(獲取數(shù)據(jù)上的排他鎖),直到已釋放所有共享鎖。
如果事務(wù)T對(duì)數(shù)據(jù)A加上共享鎖后,則其他事務(wù)只能對(duì)A再加共享鎖,不能加排他鎖。獲準(zhǔn)共享鎖的事務(wù)只能讀數(shù)據(jù),不能修改數(shù)據(jù)。
SELECT ... LOCK IN SHARE MODE;
在查詢(xún)語(yǔ)句后面增加LOCK IN SHARE MODE,Mysql會(huì)對(duì)查詢(xún)結(jié)果中的每行都加共享鎖,當(dāng)沒(méi)有其他線程對(duì)查詢(xún)結(jié)果集中的任何一行使用排他鎖時(shí),可以成功申請(qǐng)共享鎖,否則會(huì)被阻塞。其他線程也可以讀取使用了共享鎖的表,而且這些線程讀取的是同一個(gè)版本的數(shù)據(jù)。
排他鎖又稱(chēng)寫(xiě)鎖,如果事務(wù)T對(duì)數(shù)據(jù)A加上排他鎖后,則其他事務(wù)不能再對(duì)A加任任何類(lèi)型的封鎖。獲準(zhǔn)排他鎖的事務(wù)既能讀數(shù)據(jù),又能修改數(shù)據(jù)。
SELECT ... FOR UPDATE;
在查詢(xún)語(yǔ)句后面增加FOR UPDATE,Mysql會(huì)對(duì)查詢(xún)結(jié)果中的每行都加排他鎖,當(dāng)沒(méi)有其他線程對(duì)查詢(xún)結(jié)果集中的任何一行使用排他鎖時(shí),可以成功申請(qǐng)排他鎖,否則會(huì)被阻塞。
在數(shù)據(jù)庫(kù)的鎖機(jī)制中介紹過(guò),數(shù)據(jù)庫(kù)管理系統(tǒng)(DBMS)中的并發(fā)控制的任務(wù)是確保在多個(gè)事務(wù)同時(shí)存取數(shù)據(jù)庫(kù)中同一數(shù)據(jù)時(shí)不破壞事務(wù)的隔離性和統(tǒng)一性以及數(shù)據(jù)庫(kù)的統(tǒng)一性。
樂(lè)觀并發(fā)控制(樂(lè)觀鎖)和悲觀并發(fā)控制(悲觀鎖)是并發(fā)控制主要采用的技術(shù)手段。
無(wú)論是悲觀鎖還是樂(lè)觀鎖,都是人們定義出來(lái)的概念,可以認(rèn)為是一種思想。其實(shí)不僅僅是關(guān)系型數(shù)據(jù)庫(kù)系統(tǒng)中有樂(lè)觀鎖和悲觀鎖的概念,像memcache、hibernate、tair等都有類(lèi)似的概念。
針對(duì)于不同的業(yè)務(wù)場(chǎng)景,應(yīng)該選用不同的并發(fā)控制方式。所以,不要把樂(lè)觀并發(fā)控制和悲觀并發(fā)控制狹義的理解為DBMS中的概念,更不要把他們和數(shù)據(jù)中提供的鎖機(jī)制(行鎖、表鎖、排他鎖、共享鎖)混為一談。其實(shí),在DBMS中,悲觀鎖正是利用數(shù)據(jù)庫(kù)本身提供的鎖機(jī)制來(lái)實(shí)現(xiàn)的。
在關(guān)系數(shù)據(jù)庫(kù)管理系統(tǒng)里,悲觀并發(fā)控制(又名“悲觀鎖”,Pessimistic Concurrency Control,縮寫(xiě)“PCC”)是一種并發(fā)控制的方法。它可以阻止一個(gè)事務(wù)以影響其他用戶(hù)的方式來(lái)修改數(shù)據(jù)。如果一個(gè)事務(wù)執(zhí)行的操作對(duì)某行數(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)境中。
悲觀鎖,正如其名,它指的是對(duì)數(shù)據(jù)被外界(包括本系統(tǒng)當(dāng)前的其他事務(wù),以及來(lái)自外部系統(tǒng)的事務(wù)處理)修改持保守態(tài)度(悲觀),因此,在整個(gè)數(shù)據(jù)處理過(guò)程中,將數(shù)據(jù)處于鎖定狀態(tài)。 悲觀鎖的實(shí)現(xiàn),往往依靠數(shù)據(jù)庫(kù)提供的鎖機(jī)制 (也只有數(shù)據(jù)庫(kù)層提供的鎖機(jī)制才能真正保證數(shù)據(jù)訪問(wèn)的排他性,否則,即使在本系統(tǒng)中實(shí)現(xiàn)了加鎖機(jī)制,也無(wú)法保證外部系統(tǒng)不會(huì)修改數(shù)據(jù))
悲觀鎖實(shí)際上是采取了“先取鎖在訪問(wèn)”的策略,為數(shù)據(jù)的處理安全提供了保證,但是在效率方面,由于額外的加鎖機(jī)制產(chǎn)生了額外的開(kāi)銷(xiāo),并且增加了死鎖的機(jī)會(huì)。并且降低了并發(fā)性;當(dāng)一個(gè)事物所以一行數(shù)據(jù)的時(shí)候,其他事物必須等待該事務(wù)提交之后,才能操作這行數(shù)據(jù)。
在關(guān)系數(shù)據(jù)庫(kù)管理系統(tǒng)里,樂(lè)觀并發(fā)控制(又名“樂(lè)觀鎖”,Optimistic Concurrency Control,縮寫(xiě)“OCC”)是一種并發(fā)控制的方法。它假設(shè)多用戶(hù)并發(fā)的事務(wù)在處理時(shí)不會(huì)彼此互相影響,各事務(wù)能夠在不產(chǎn)生鎖的情況下處理各自影響的那部分?jǐn)?shù)據(jù)。在提交數(shù)據(jù)更新之前,每個(gè)事務(wù)會(huì)先檢查在該事務(wù)讀取數(shù)據(jù)后,有沒(méi)有其他事務(wù)又修改了該數(shù)據(jù)。如果其他事務(wù)有更新的話(huà),正在提交的事務(wù)會(huì)進(jìn)行回滾。
樂(lè)觀鎖( Optimistic Locking ) 相對(duì)悲觀鎖而言,樂(lè)觀鎖假設(shè)認(rèn)為數(shù)據(jù)一般情況下不會(huì)造成沖突,所以在數(shù)據(jù)進(jìn)行提交更新的時(shí)候,才會(huì)正式對(duì)數(shù)據(jù)的沖突與否進(jìn)行檢測(cè),如果發(fā)現(xiàn)沖突了,則讓返回用戶(hù)錯(cuò)誤的信息,讓用戶(hù)決定如何去做。
相對(duì)于悲觀鎖,在對(duì)數(shù)據(jù)庫(kù)進(jìn)行處理的時(shí)候,樂(lè)觀鎖并不會(huì)使用數(shù)據(jù)庫(kù)提供的鎖機(jī)制。一般的實(shí)現(xiàn)樂(lè)觀鎖的方式就是記錄數(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ù)庫(kù)表對(duì)應(yīng)記錄的當(dāng)前版本信息與第一次取出來(lái)的版本標(biāo)識(shí)進(jìn)行比對(duì),如果數(shù)據(jù)庫(kù)表當(dāng)前版本號(hào)與第一次取出來(lái)的版本標(biāo)識(shí)值相等,則予以更新,否則認(rèn)為是過(guò)期數(shù)據(jù)。
樂(lè)觀并發(fā)控制相信事務(wù)之間的數(shù)據(jù)競(jìng)爭(zhēng)(data race)的概率是比較小的,因此盡可能直接做下去,直到提交的時(shí)候才去鎖定,所以不會(huì)產(chǎn)生任何鎖和死鎖。但如果直接簡(jiǎn)單這么做,還是有可能會(huì)遇到不可預(yù)期的結(jié)果,例如兩個(gè)事務(wù)都讀取了數(shù)據(jù)庫(kù)的某一行,經(jīng)過(guò)修改以后寫(xiě)回?cái)?shù)據(jù)庫(kù),這時(shí)就遇到了問(wèn)題。
由于表鎖和行鎖雖然鎖定范圍不同,但是會(huì)相互沖突。所以當(dāng)你要加表鎖時(shí),勢(shì)必要先遍歷該表的所有記錄,判斷是否加有排他鎖。這種遍歷檢查的方式顯然是一種低效的方式,MySQL 引入了意向鎖,來(lái)檢測(cè)表鎖和行鎖的沖突。
意向鎖也是表級(jí)鎖,也可分為讀意向鎖(IS 鎖)和寫(xiě)意向鎖(IX 鎖)。當(dāng)事務(wù)要在記錄上加上讀鎖或?qū)戞i時(shí),要首先在表上加上意向鎖。這樣判斷表中是否有記錄加鎖就很簡(jiǎn)單了,只要看下表上是否有意向鎖就行了。
意向鎖之間是不會(huì)產(chǎn)生沖突的,也不和 AUTO_INC 表鎖沖突,它只會(huì)阻塞表級(jí)讀鎖或表級(jí)寫(xiě)鎖,另外,意向鎖也不會(huì)和行鎖沖突,行鎖只會(huì)和行鎖沖突。
意向鎖是InnoDB自動(dòng)加的,不需要用戶(hù)干預(yù)。
對(duì)于insert、update、delete,InnoDB會(huì)自動(dòng)給涉及的數(shù)據(jù)加排他鎖(X);
對(duì)于一般的Select語(yǔ)句,InnoDB不會(huì)加任何鎖,事務(wù)可以通過(guò)以下語(yǔ)句給顯示加共享鎖或排他鎖。
意向共享鎖(IS):表示事務(wù)準(zhǔn)備給數(shù)據(jù)行加入共享鎖,也就是說(shuō)一個(gè)數(shù)據(jù)行加共享鎖前必須先取得該表的IS鎖
意向排他鎖(IX):類(lèi)似上面,表示事務(wù)準(zhǔn)備給數(shù)據(jù)行加入排他鎖,說(shuō)明事務(wù)在一個(gè)數(shù)據(jù)行加排他鎖前必須先取得該表的IX鎖。
記錄鎖是最簡(jiǎn)單的行鎖,并沒(méi)有什么好說(shuō)的。上邊描述 InnoDB 加鎖原理中的鎖就是記錄鎖,只鎖住 id = 49 或者 name = 'Tom' 這一條記錄。
當(dāng) SQL 語(yǔ)句無(wú)法使用索引時(shí),會(huì)進(jìn)行全表掃描,這個(gè)時(shí)候 MySQL 會(huì)給整張表的所有數(shù)據(jù)行加記錄鎖,再由 MySQL Server 層進(jìn)行過(guò)濾。但是,在 MySQL Server 層進(jìn)行過(guò)濾的時(shí)候,如果發(fā)現(xiàn)不滿(mǎn)足 WHERE 條件,會(huì)釋放對(duì)應(yīng)記錄的鎖。這樣做,保證了最后只會(huì)持有滿(mǎn)足條件記錄上的鎖,但是每條記錄的加鎖操作還是不能省略的。
所以更新操作必須要根據(jù)索引進(jìn)行操作,沒(méi)有索引時(shí),不僅會(huì)消耗大量的鎖資源,增加數(shù)據(jù)庫(kù)的開(kāi)銷(xiāo),還會(huì)極大的降低了數(shù)據(jù)庫(kù)的并發(fā)性能。
當(dāng)我們使用范圍條件而不是相等條件檢索數(shù)據(jù),并請(qǐng)求共享或排他鎖時(shí),InnoDB會(huì)給符合條件的已有數(shù)據(jù)記錄的索引項(xiàng)加鎖;對(duì)于鍵值在條件范圍內(nèi)但并不存在的記錄,InnoDB 也會(huì)對(duì)這個(gè)“間隙”加鎖,這種鎖機(jī)制就是所謂的間隙鎖。
間隙鎖是鎖索引記錄中的間隔,或者第一條索引記錄之前的范圍,又或者最后一條索引記錄之后的范圍。
間隙鎖在 InnoDB 的唯一作用就是防止其它事務(wù)的插入操作,以此來(lái)達(dá)到防止幻讀的發(fā)生,所以間隙鎖不分什么共享鎖與排他鎖。
要禁止間隙鎖,可以把隔離級(jí)別降為讀已提交,或者開(kāi)啟參數(shù) innodb_locks_unsafe_for_binlog
show variables like 'innodb_locks_unsafe_for_binlog';復(fù)制代碼
innodb_locks_unsafe_for_binlog:默認(rèn)
值為OFF,即啟用間隙鎖。因?yàn)榇藚?shù)是只讀模式,如果想要禁用間隙鎖,需要修改 my.cnf(windows是my.ini) 重新啟動(dòng)才行。
# 在 my.cnf 里面的[mysqld]添加 [mysqld] innodb_locks_unsafe_for_binlog = 1復(fù)制代碼
測(cè)試環(huán)境:
MySQL5.7,InnoDB,默認(rèn)的隔離級(jí)別(RR)
示例表:
CREATE TABLE `my_gap` ( `id` int(1) NOT NULL AUTO_INCREMENT, `name` varchar(8) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;INSERT INTO `my_gap` VALUES ('1', '張三');INSERT INTO `my_gap` VALUES ('5', '李四');INSERT INTO `my_gap` VALUES ('7', '王五');INSERT INTO `my_gap` VALUES ('11', '趙六');復(fù)制代碼在進(jìn)行測(cè)試之前,我們先看看 my_gap 表中存在的隱藏間隙:
/* 開(kāi)啟事務(wù)1 */BEGIN;/* 查詢(xún) id = 5 的數(shù)據(jù)并加記錄鎖 */SELECT * FROM `my_gap` WHERE `id` = 5 FOR UPDATE;/* 延遲30秒執(zhí)行,防止鎖釋放 */SELECT SLEEP(30); # 注意:以下的語(yǔ)句不是放在一個(gè)事務(wù)中執(zhí)行,而是分開(kāi)多次執(zhí)行,每次事務(wù)中只有一條添加語(yǔ)句/* 事務(wù)2插入一條 name = '杰倫' 的數(shù)據(jù) */INSERT INTO `my_gap` (`id`, `name`) VALUES (4, '杰倫'); # 正常執(zhí)行/* 事務(wù)3插入一條 name = '學(xué)友' 的數(shù)據(jù) */INSERT INTO `my_gap` (`id`, `name`) VALUES (8, '學(xué)友'); # 正常執(zhí)行/* 提交事務(wù)1,釋放事務(wù)1的鎖 */COMMIT;復(fù)制代碼
上述案例,由于主鍵是唯一索引,而且只使用一個(gè)索引查詢(xún),并且只鎖定了一條記錄,所以只會(huì)對(duì) id = 5 的數(shù)據(jù)加上記錄鎖(行鎖),而不會(huì)產(chǎn)生間隙鎖。
恢復(fù)初始化的4條記錄,繼續(xù)在 id 唯一索引列上做以下測(cè)試:
/* 開(kāi)啟事務(wù)1 */BEGIN;/* 查詢(xún) id 在 7 - 11 范圍的數(shù)據(jù)并加記錄鎖 */SELECT * FROM `my_gap` WHERE `id` BETWEEN 5 AND 7 FOR UPDATE;/* 延遲30秒執(zhí)行,防止鎖釋放 */SELECT SLEEP(30); # 注意:以下的語(yǔ)句不是放在一個(gè)事務(wù)中執(zhí)行,而是分開(kāi)多次執(zhí)行,每次事務(wù)中只有一條添加語(yǔ)句/* 事務(wù)2插入一條 id = 3,name = '思聰3' 的數(shù)據(jù) */INSERT INTO `my_gap` (`id`, `name`) VALUES (3, '思聰3'); # 正常執(zhí)行/* 事務(wù)3插入一條 id = 4,name = '思聰4' 的數(shù)據(jù) */INSERT INTO `my_gap` (`id`, `name`) VALUES (4, '思聰4'); # 正常執(zhí)行/* 事務(wù)4插入一條 id = 6,name = '思聰6' 的數(shù)據(jù) */INSERT INTO `my_gap` (`id`, `name`) VALUES (6, '思聰6'); # 阻塞/* 事務(wù)5插入一條 id = 8, name = '思聰8' 的數(shù)據(jù) */INSERT INTO `my_gap` (`id`, `name`) VALUES (8, '思聰8'); # 阻塞/* 事務(wù)6插入一條 id = 9, name = '思聰9' 的數(shù)據(jù) */INSERT INTO `my_gap` (`id`, `name`) VALUES (9, '思聰9'); # 阻塞/* 事務(wù)7插入一條 id = 11, name = '思聰11' 的數(shù)據(jù) */INSERT INTO `my_gap` (`id`, `name`) VALUES (11, '思聰11'); # 阻塞/* 事務(wù)8插入一條 id = 12, name = '思聰12' 的數(shù)據(jù) */INSERT INTO `my_gap` (`id`, `name`) VALUES (12, '思聰12'); # 正常執(zhí)行/* 提交事務(wù)1,釋放事務(wù)1的鎖 */COMMIT;復(fù)制代碼
從上面可以看到,(5,7]、(7,11] 這兩個(gè)區(qū)間,都不可插入數(shù)據(jù),其它區(qū)間,都可以正常插入數(shù)據(jù)。所以可以得出結(jié)論:當(dāng)我們給(5,7] 這個(gè)區(qū)間加鎖的時(shí)候,會(huì)鎖住(5,7]、(7,11] 這兩個(gè)區(qū)間。
恢復(fù)初始化的4條記錄,我們?cè)賮?lái)測(cè)試如果鎖住不存在的數(shù)據(jù)時(shí),會(huì)如何?
/* 開(kāi)啟事務(wù)1 */BEGIN;/* 查詢(xún) id = 3 這一條不存在的數(shù)據(jù)并加記錄鎖 */SELECT * FROM `my_gap` WHERE `id` = 3 FOR UPDATE;/* 延遲30秒執(zhí)行,防止鎖釋放 */SELECT SLEEP(30); # 注意:以下的語(yǔ)句不是放在一個(gè)事務(wù)中執(zhí)行,而是分開(kāi)多次執(zhí)行,每次事務(wù)中只有一條添加語(yǔ)句/* 事務(wù)2插入一條 id = 3,name = '小張' 的數(shù)據(jù) */INSERT INTO `my_gap` (`id`, `name`) VALUES (2, '小張'); # 阻塞/* 事務(wù)3插入一條 id = 4,name = '小白' 的數(shù)據(jù) */INSERT INTO `my_gap` (`id`, `name`) VALUES (4, '小白'); # 阻塞/* 事務(wù)4插入一條 id = 6,name = '小東' 的數(shù)據(jù) */INSERT INTO `my_gap` (`id`, `name`) VALUES (6, '小東'); # 正常執(zhí)行/* 事務(wù)5插入一條 id = 8, name = '大羅' 的數(shù)據(jù) */INSERT INTO `my_gap` (`id`, `name`) VALUES (8, '大羅'); # 正常執(zhí)行/* 提交事務(wù)1,釋放事務(wù)1的鎖 */COMMIT;復(fù)制代碼
從上面可以看出,指定查詢(xún)某一條記錄時(shí),如果這條記錄不存在,會(huì)產(chǎn)生間隙鎖。
示例表:id 是主鍵,在 number 上,建立了一個(gè)普通索引。
# 注意:number 不是唯一值CREATE TABLE `my_gap1` ( `id` int(1) NOT NULL AUTO_INCREMENT, `number` int(1) NOT NULL COMMENT '數(shù)字', PRIMARY KEY (`id`), KEY `number` (`number`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;INSERT INTO `my_gap1` VALUES (1, 1);INSERT INTO `my_gap1` VALUES (5, 3);INSERT INTO `my_gap1` VALUES (7, 8);INSERT INTO `my_gap1` VALUES (11, 12);復(fù)制代碼
在進(jìn)行測(cè)試之前,我們先來(lái)看看 my_gap1 表中 number 索引存在的隱藏間隙:
我們執(zhí)行以下的事務(wù)(事務(wù)1最后提交),分別執(zhí)行下面的語(yǔ)句:
/* 開(kāi)啟事務(wù)1 */BEGIN;/* 查詢(xún) number = 3 的數(shù)據(jù)并加記錄鎖 */SELECT * FROM `my_gap1` WHERE `number` = 3 FOR UPDATE;/* 延遲30秒執(zhí)行,防止鎖釋放 */SELECT SLEEP(30); # 注意:以下的語(yǔ)句不是放在一個(gè)事務(wù)中執(zhí)行,而是分開(kāi)多次執(zhí)行,每次事務(wù)中只有一條添加語(yǔ)句/* 事務(wù)2插入一條 number = 0 的數(shù)據(jù) */INSERT INTO `my_gap1` (`number`) VALUES (0); # 正常執(zhí)行/* 事務(wù)3插入一條 number = 1 的數(shù)據(jù) */INSERT INTO `my_gap1` (`number`) VALUES (1); # 被阻塞/* 事務(wù)4插入一條 number = 2 的數(shù)據(jù) */INSERT INTO `my_gap1` (`number`) VALUES (2); # 被阻塞/* 事務(wù)5插入一條 number = 4 的數(shù)據(jù) */INSERT INTO `my_gap1` (`number`) VALUES (4); # 被阻塞/* 事務(wù)6插入一條 number = 8 的數(shù)據(jù) */INSERT INTO `my_gap1` (`number`) VALUES (8); # 正常執(zhí)行/* 事務(wù)7插入一條 number = 9 的數(shù)據(jù) */INSERT INTO `my_gap1` (`number`) VALUES (9); # 正常執(zhí)行/* 事務(wù)8插入一條 number = 10 的數(shù)據(jù) */INSERT INTO `my_gap1` (`number`) VALUES (10); # 正常執(zhí)行/* 提交事務(wù)1 */COMMIT;復(fù)制代碼
我們會(huì)發(fā)現(xiàn)有些語(yǔ)句可以正常執(zhí)行,有些語(yǔ)句被阻塞來(lái)。查看表中的數(shù)據(jù):
這里可以看到,number(1,8) 的間隙中,插入語(yǔ)句都被阻塞來(lái),而不在這個(gè)范圍內(nèi)的語(yǔ)句,正常執(zhí)行,這就是因?yàn)橛虚g隙鎖的原因。
我們?cè)龠M(jìn)行以下測(cè)試,這里將數(shù)據(jù)還原成初始化那樣
/* 開(kāi)啟事務(wù)1 */BEGIN;/* 查詢(xún) number = 3 的數(shù)據(jù)并加記錄鎖 */SELECT * FROM `my_gap1` WHERE `number` = 3 FOR UPDATE;/* 延遲30秒執(zhí)行,防止鎖釋放 */SELECT SLEEP(30);/* 事務(wù)1插入一條 id = 2, number = 1 的數(shù)據(jù) */INSERT INTO `my_gap1` (`id`, `number`) VALUES (2, 1); # 阻塞/* 事務(wù)2插入一條 id = 3, number = 2 的數(shù)據(jù) */INSERT INTO `my_gap1` (`id`, `number`) VALUES (3, 2); # 阻塞/* 事務(wù)3插入一條 id = 6, number = 8 的數(shù)據(jù) */INSERT INTO `my_gap1` (`id`, `number`) VALUES (6, 8); # 阻塞/* 事務(wù)4插入一條 id = 8, number = 8 的數(shù)據(jù) */INSERT INTO `my_gap1` (`id`, `number`) VALUES (8, 8); # 正常執(zhí)行/* 事務(wù)5插入一條 id = 9, number = 9 的數(shù)據(jù) */INSERT INTO `my_gap1` (`id`, `number`) VALUES (9, 9); # 正常執(zhí)行/* 事務(wù)6插入一條 id = 10, number = 12 的數(shù)據(jù) */INSERT INTO `my_gap1` (`id`, `number`) VALUES (10, 12); # 正常執(zhí)行/* 事務(wù)7修改 id = 11, number = 12 的數(shù)據(jù) */UPDATE `my_gap1` SET `number` = 5 WHERE `id` = 11 AND `number` = 12; # 阻塞/* 提交事務(wù)1 */COMMIT;復(fù)制代碼
查看表中的數(shù)據(jù);
這里有一個(gè)奇怪的現(xiàn)象:
這是為什么?我們來(lái)看看下面的圖:
從圖中庫(kù)看出,當(dāng) number 相同時(shí),會(huì)根據(jù)主鍵 id 來(lái)排序
臨鍵鎖,是記錄鎖(行鎖)與間隙鎖的組合,它的鎖范圍,即包含索引記錄,又包含索引區(qū)間。它指的是加在某條記錄以及這條記錄前面間隙上的鎖。假設(shè)一個(gè)索引包含 15、18、20 ,30,49,50 這幾個(gè)值,可能的 Next-key 鎖如下:
(-∞, 15],(15, 18],(18, 20],(20, 30],(30, 49],(49, 50],(50, +∞)復(fù)制代碼
通常我們都用這種左開(kāi)右閉區(qū)間來(lái)表示 Next-key 鎖,其中,圓括號(hào)表示不包含該記錄,方括號(hào)表示包含該記錄。前面四個(gè)都是 Next-key 鎖,最后一個(gè)為間隙鎖。和間隙鎖一樣,在 RC 隔離級(jí)別下沒(méi)有 Next-key 鎖,只有 RR 隔離級(jí)別才有。還是之前的例子,如果 id 不是主鍵,而是二級(jí)索引,且不是唯一索引,那么這個(gè) SQL 在 RR 隔離級(jí)別下就會(huì)加如下的 Next-key 鎖 (30, 49](49, 50)
此時(shí)如果插入一條 id = 31 的記錄將會(huì)阻塞住。之所以要把 id = 49 前后的間隙都鎖住,仍然是為了解決幻讀問(wèn)題,因?yàn)?id 是非唯一索引,所以 id = 49 可能會(huì)有多條記錄,為了防止再插入一條 id = 49 的記錄。
注意:臨鍵鎖的主要目的,也是為了避免幻讀(Phantom Read)。如果把事務(wù)隔離級(jí)別降級(jí)為 RC,臨鍵鎖則也會(huì)失效。
插入意向鎖是一種特殊的間隙鎖(簡(jiǎn)稱(chēng)II GAP)表示插入的意向,只有在 INSERT 的時(shí)候才會(huì)有這個(gè)鎖。注意,這個(gè)鎖雖然也叫意向鎖,但是和上面介紹的表級(jí)意向鎖是兩個(gè)完全不同的概念,不要搞混了。
插入意向鎖和插入意向鎖之間互不沖突,所以可以在同一個(gè)間隙中有多個(gè)事務(wù)同時(shí)插入不同索引的記錄。譬如在例子中,id = 30 和 id = 49 之間如果有兩個(gè)事務(wù)要同時(shí)分別插入 id = 32 和 id = 33 是沒(méi)問(wèn)題的,雖然兩個(gè)事務(wù)都會(huì)在 id = 30 和 id = 50 之間加上插入意向鎖,但是不會(huì)沖突。
插入意向鎖只會(huì)和間隙鎖或 Next-key 鎖沖突,正如上面所說(shuō),間隙鎖唯一的作用就是防止其他事務(wù)插入記錄造成幻讀,正是由于在執(zhí)行 INSERT 語(yǔ)句時(shí)需要加插入意向鎖,而插入意向鎖和間隙鎖沖突,從而阻止了插入操作的執(zhí)行。
插入意向鎖的作用:
AUTO_INC 鎖又叫自增鎖(一般簡(jiǎn)寫(xiě)成 AI 鎖),是一種表鎖,當(dāng)表中有自增列(AUTO_INCREMENT)時(shí)出現(xiàn)。當(dāng)插入表中有自增列時(shí),數(shù)據(jù)庫(kù)需要自動(dòng)生成自增值,它會(huì)先為該表加 AUTO_INC 表鎖,阻塞其他事務(wù)的插入操作,這樣保證生成的自增值肯定是唯一的。AUTO_INC 鎖具有如下特點(diǎn):
使用AUTO_INCREMENT 函數(shù)實(shí)現(xiàn)自增操作,自增幅度通過(guò) auto_increment_offset和auto_increment_increment這2個(gè)參數(shù)進(jìn)行控制:
通過(guò)使用last_insert_id()函數(shù)可以獲得最后一個(gè)插入的數(shù)字
select last_insert_id();復(fù)制代碼
首先insert大致上可以分成三類(lèi):
如果存在自增字段,MySQL 會(huì)維護(hù)一個(gè)自增鎖,和自增鎖相關(guān)的一個(gè)參數(shù)為(5.1.22 版本后加入) innodb_autoinc_lock_mode ,可以設(shè)定 3 值:
MyISam引擎均為 traditonal,每次均會(huì)進(jìn)行表鎖。但是InnoDB引擎會(huì)視參數(shù)不同產(chǎn)生不同的鎖,默認(rèn)為 1:consecutive。
show variables like 'innodb_autoinc_lock_mode';復(fù)制代碼
innodb_autoinc_lock_mode 為 0 時(shí),也就是 traditional 級(jí)別。該自增鎖時(shí)表鎖級(jí)別,且必須等待當(dāng)前 SQL 執(zhí)行完畢后或者回滾才會(huì)釋放,在高并發(fā)的情況下可想而知自增鎖競(jìng)爭(zhēng)時(shí)比較大的。
innodb_autoinc_lock_mode 為 1 時(shí),也就是 consecutive 級(jí)別。這是如果是單一的 insert SQL,可以立即獲得該鎖,并立即釋放,而不必等待當(dāng)前SQL執(zhí)行完成(除非在其它事務(wù)中已經(jīng)有 session 獲取了自增鎖)。另外當(dāng)SQL是一些批量 insert SQL 時(shí),比如 insert into ... select ... , load data , replace ... select ... 時(shí),這時(shí)還是表級(jí)鎖,可以理解為退化為必須等待當(dāng)前 SQL 執(zhí)行完才釋放。可以認(rèn)為,該值為 1 時(shí)相對(duì)比較輕量級(jí)的鎖,也不會(huì)對(duì)復(fù)制產(chǎn)生影響,唯一的缺陷是產(chǎn)生自增值不一定是完全連續(xù)的。
innodb_autoinc_lock_mode 為 2 時(shí),也就是 interleaved 級(jí)別。所有 insert 種類(lèi)的 SQL 都可以立馬獲得鎖并釋放,這時(shí)的效率最高。但是會(huì)引入一個(gè)新的問(wèn)題:當(dāng) binlog_format 為 statement 時(shí),這是復(fù)制沒(méi)法保證安全,因?yàn)榕康?insert,比如 insert ... select ... 語(yǔ)句在這個(gè)情況下,也可以立馬獲取到一大批的自增 id 值,不必鎖整個(gè)表, slave 在回放這個(gè) SQL 時(shí)必然會(huì)產(chǎn)生錯(cuò)亂。
如果你的二進(jìn)制文件格式是mixed | row 那么這三個(gè)值中的任何一個(gè)對(duì)于你來(lái)說(shuō)都是復(fù)制安全的。
由于現(xiàn)在mysql已經(jīng)推薦把二進(jìn)制的格式設(shè)置成row,所以在binlog_format不是statement的情況下最好是innodb_autoinc_lock_mode=2 這樣可能知道更好的性能。
鎖的模式有:讀意向鎖,寫(xiě)意向鎖,讀鎖,寫(xiě)鎖和自增鎖(auto_inc)。
| IS | IX | S | X | AI | |
|---|---|---|---|---|---|
| IS | 兼容 | 兼容 | 兼容 | 兼容 | |
| IX | 兼容 | 兼容 | 兼容 | ||
| S | 兼容 | 兼容 | |||
| X | |||||
| AI | 兼容 | 兼容 |
總結(jié)起來(lái)有下面幾點(diǎn):
根據(jù)鎖的粒度可以把鎖細(xì)分為表鎖和行鎖,行鎖根據(jù)場(chǎng)景的不同又可以進(jìn)一步細(xì)分,依次為 Next-Key Lock,Gap Lock 間隙鎖,Record Lock 記錄鎖和插入意向 GAP 鎖。
不同的鎖鎖定的位置是不同的,比如說(shuō)記錄鎖只鎖住對(duì)應(yīng)的記錄,而間隙鎖鎖住記錄和記錄之間的間隔,Next-Key Lock 則所屬記錄和記錄之前的間隙。不同類(lèi)型鎖的鎖定范圍大致如下圖所示。
| RECORD | GAP | NEXT-KEY | II GAP | |
|---|---|---|---|---|
| RECORD | 兼容 | 兼容 | ||
| GAP | 兼容 | 兼容 | 兼容 | 兼容 |
| NEXT-KEY | 兼容 | 兼容 | ||
| II GAP | 兼容 | 兼容 |
其中,第一行表示已有的鎖,第一列表示要加的鎖。插入意向鎖較為特殊,所以我們先對(duì)插入意向鎖做個(gè)總結(jié),如下:
其他類(lèi)型的鎖的規(guī)則較為簡(jiǎn)單:
記錄鎖和記錄鎖沖突,Next-key 鎖和 Next-key 鎖沖突,記錄鎖和 Next-key 鎖沖突;
感謝你能夠認(rèn)真閱讀完這篇文章,希望小編分享MySQL鎖類(lèi)型和加鎖原理是什么內(nèi)容對(duì)大家有幫助,同時(shí)也希望大家多多支持創(chuàng)新互聯(lián),關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,遇到問(wèn)題就找創(chuàng)新互聯(lián),詳細(xì)的解決方法等著你來(lái)學(xué)習(xí)!
文章標(biāo)題:MySQL鎖類(lèi)型和加鎖原理是什么
網(wǎng)頁(yè)網(wǎng)址:http://chinadenli.net/article36/goipsg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供品牌網(wǎng)站設(shè)計(jì)、域名注冊(cè)、用戶(hù)體驗(yàn)、網(wǎng)站內(nèi)鏈、自適應(yīng)網(wǎng)站、移動(dòng)網(wǎng)站建設(shè)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話(huà):028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)