Innodb中的事務(wù)隔離級(jí)別和鎖的關(guān)系

Innodb中的事務(wù)隔離級(jí)別和鎖的關(guān)系

轉(zhuǎn)發(fā)(https://tech.meituan.com/innodb_lock.html)

ameng?·2014-08-20 15:50

前言:

我們都知道事務(wù)的幾種性質(zhì),數(shù)據(jù)庫為了維護(hù)這些性質(zhì),尤其是一致性和隔離性,一般使用加鎖這種方式。同時(shí)數(shù)據(jù)庫又是個(gè)高并發(fā)的應(yīng)用,同一時(shí)間會(huì)有大量的并發(fā)訪問,如果加鎖過度,會(huì)極大的降低并發(fā)處理能力。所以對(duì)于加鎖的處理,可以說就是數(shù)據(jù)庫對(duì)于事務(wù)處理的精髓所在。這里通過分析MySQL中InnoDB引擎的加鎖機(jī)制,來拋磚引玉,讓讀者更好的理解,在事務(wù)處理中數(shù)據(jù)庫到底做了什么。

#一次封鎖or兩段鎖?

因?yàn)橛写罅康牟l(fā)訪問,為了預(yù)防死鎖,一般應(yīng)用中推薦使用一次封鎖法,就是在方法的開始階段,已經(jīng)預(yù)先知道會(huì)用到哪些數(shù)據(jù),然后全部鎖住,在方法運(yùn)行之后,再全部解鎖。這種方式可以有效的避免循環(huán)死鎖,但在數(shù)據(jù)庫中卻不適用,因?yàn)樵谑聞?wù)開始階段,數(shù)據(jù)庫并不知道會(huì)用到哪些數(shù)據(jù)。

數(shù)據(jù)庫遵循的是兩段鎖協(xié)議,將事務(wù)分成兩個(gè)階段,加鎖階段和解鎖階段(所以叫兩段鎖)

加鎖階段:在該階段可以進(jìn)行加鎖操作。在對(duì)任何數(shù)據(jù)進(jìn)行讀操作之前要申請并獲得S鎖(共享鎖,其它事務(wù)可以繼續(xù)加共享鎖,但不能加排它鎖),在進(jìn)行寫操作之前要申請并獲得X鎖(排它鎖,其它事務(wù)不能再獲得任何鎖)。加鎖不成功,則事務(wù)進(jìn)入等待狀態(tài),直到加鎖成功才繼續(xù)執(zhí)行。

解鎖階段:當(dāng)事務(wù)釋放了一個(gè)封鎖以后,事務(wù)進(jìn)入解鎖階段,在該階段只能進(jìn)行解鎖操作不能再進(jìn)行加鎖操作。

事務(wù)加鎖/解鎖處理

begin;

insert into test .....加insert對(duì)應(yīng)的鎖

update test set...加update對(duì)應(yīng)的鎖

delete from test ....加delete對(duì)應(yīng)的鎖

commit;事務(wù)提交時(shí),同時(shí)釋放insert、update、delete對(duì)應(yīng)的鎖

這種方式雖然無法避免死鎖,但是兩段鎖協(xié)議可以保證事務(wù)的并發(fā)調(diào)度是串行化(串行化很重要,尤其是在數(shù)據(jù)恢復(fù)和備份的時(shí)候)的。

#事務(wù)中的加鎖方式

##事務(wù)的四種隔離級(jí)別

在數(shù)據(jù)庫操作中,為了有效保證并發(fā)讀取數(shù)據(jù)的正確性,提出的事務(wù)隔離級(jí)別。我們的數(shù)據(jù)庫鎖,也是為了構(gòu)建這些隔離級(jí)別存在的。

隔離級(jí)別臟讀(Dirty Read)不可重復(fù)讀(NonRepeatable Read)幻讀(Phantom Read)

未提交讀(Read uncommitted)可能可能可能

已提交讀(Read committed)不可能可能可能

可重復(fù)讀(Repeatable read)不可能不可能可能

可串行化(Serializable )不可能不可能不可能

未提交讀(Read Uncommitted):允許臟讀,也就是可能讀取到其他會(huì)話中未提交事務(wù)修改的數(shù)據(jù)

提交讀(Read Committed):只能讀取到已經(jīng)提交的數(shù)據(jù)。Oracle等多數(shù)數(shù)據(jù)庫默認(rèn)都是該級(jí)別 (不重復(fù)讀)

可重復(fù)讀(Repeated Read):可重復(fù)讀。在同一個(gè)事務(wù)內(nèi)的查詢都是事務(wù)開始時(shí)刻一致的,InnoDB默認(rèn)級(jí)別。在SQL標(biāo)準(zhǔn)中,該隔離級(jí)別消除了不可重復(fù)讀,但是還存在幻象讀

串行讀(Serializable):完全串行化的讀,每次讀都需要獲得表級(jí)共享鎖,讀寫相互都會(huì)阻塞

Read Uncommitted這種級(jí)別,數(shù)據(jù)庫一般都不會(huì)用,而且任何操作都不會(huì)加鎖,這里就不討論了。

##MySQL中鎖的種類

MySQL中鎖的種類很多,有常見的表鎖和行鎖,也有新加入的Metadata Lock等等,表鎖是對(duì)一整張表加鎖,雖然可分為讀鎖和寫鎖,但畢竟是鎖住整張表,會(huì)導(dǎo)致并發(fā)能力下降,一般是做ddl處理時(shí)使用。

行鎖則是鎖住數(shù)據(jù)行,這種加鎖方法比較復(fù)雜,但是由于只鎖住有限的數(shù)據(jù),對(duì)于其它數(shù)據(jù)不加限制,所以并發(fā)能力強(qiáng),MySQL一般都是用行鎖來處理并發(fā)事務(wù)。這里主要討論的也就是行鎖。

###Read Committed(讀取提交內(nèi)容)

在RC級(jí)別中,數(shù)據(jù)的讀取都是不加鎖的,但是數(shù)據(jù)的寫入、修改和刪除是需要加鎖的。效果如下

MySQL> show create table class_teacher \G\

Table: class_teacher

Create Table: CREATE TABLE `class_teacher` (

? `id` int(11) NOT NULL AUTO_INCREMENT,

? `class_name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,

? `teacher_id` int(11) NOT NULL,

? PRIMARY KEY (`id`),

? KEY `idx_teacher_id` (`teacher_id`)

) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci

1 row in set (0.02 sec)

MySQL> select * from class_teacher;

+----+--------------+------------+

| id | class_name? | teacher_id |

+----+--------------+------------+

|? 1 | 初三一班? ? |? ? ? ? ? 1 |

|? 3 | 初二一班? ? |? ? ? ? ? 2 |

|? 4 | 初二二班? ? |? ? ? ? ? 2 |

+----+--------------+------------+

由于MySQL的InnoDB默認(rèn)是使用的RR級(jí)別,所以我們先要將該session開啟成RC級(jí)別,并且設(shè)置binlog的模式

SETsessiontransactionisolationlevelreadcommitted;SETSESSIONbinlog_format ='ROW';(或者是MIXED)

事務(wù)A事務(wù)B

begin;begin;

update class_teacher set class_name='初三二班' where teacher_id=1;update class_teacher set class_name='初三三班' where teacher_id=1;

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

commit;

為了防止并發(fā)過程中的修改沖突,事務(wù)A中MySQL給teacher_id=1的數(shù)據(jù)行加鎖,并一直不commit(釋放鎖),那么事務(wù)B也就一直拿不到該行鎖,wait直到超時(shí)。

這時(shí)我們要注意到,teacher_id是有索引的,如果是沒有索引的class_name呢?update class_teacher set teacher_id=3 where class_name = '初三一班';

那么MySQL會(huì)給整張表的所有數(shù)據(jù)行的加行鎖。這里聽起來有點(diǎn)不可思議,但是當(dāng)sql運(yùn)行的過程中,MySQL并不知道哪些數(shù)據(jù)行是 class_name = '初三一班'的(沒有索引嘛),如果一個(gè)條件無法通過索引快速過濾,存儲(chǔ)引擎層面就會(huì)將所有記錄加鎖后返回,再由MySQL Server層進(jìn)行過濾。

但在實(shí)際使用過程當(dāng)中,MySQL做了一些改進(jìn),在MySQL Server過濾條件,發(fā)現(xiàn)不滿足后,會(huì)調(diào)用unlock_row方法,把不滿足條件的記錄釋放鎖 (違背了二段鎖協(xié)議的約束)。這樣做,保證了最后只會(huì)持有滿足條件記錄上的鎖,但是每條記錄的加鎖操作還是不能省略的。可見即使是MySQL,為了效率也是會(huì)違反規(guī)范的。(參見《高性能MySQL》中文第三版p181)

這種情況同樣適用于MySQL的默認(rèn)隔離級(jí)別RR。所以對(duì)一個(gè)數(shù)據(jù)量很大的表做批量修改的時(shí)候,如果無法使用相應(yīng)的索引,MySQL Server過濾數(shù)據(jù)的的時(shí)候特別慢,就會(huì)出現(xiàn)雖然沒有修改某些行的數(shù)據(jù),但是它們還是被鎖住了的現(xiàn)象。

###Repeatable Read(可重讀)

這是MySQL中InnoDB默認(rèn)的隔離級(jí)別。我們姑且分“讀”和“寫”兩個(gè)模塊來講解。

####讀

讀就是可重讀,可重讀這個(gè)概念是一事務(wù)的多個(gè)實(shí)例在并發(fā)讀取數(shù)據(jù)時(shí),會(huì)看到同樣的數(shù)據(jù)行,有點(diǎn)抽象,我們來看一下效果。

RC(不可重讀)模式下的展現(xiàn)

事務(wù)A事務(wù)B

begin;begin;

select id,class_name,teacher_id from class_teacher where teacher_id=1;

idclass_nameteacher_id

1初三二班1

2初三一班1

update class_teacher set class_name='初三三班' where id=1;

commit;

select id,class_name,teacher_id from class_teacher where teacher_id=1;

idclass_nameteacher_id

1初三三班1

2初三一班1

讀到了事務(wù)B修改的數(shù)據(jù),和第一次查詢的結(jié)果不一樣,是不可重讀的。

commit;

事務(wù)B修改id=1的數(shù)據(jù)提交之后,事務(wù)A同樣的查詢,后一次和前一次的結(jié)果不一樣,這就是不可重讀(重新讀取產(chǎn)生的結(jié)果不一樣)。這就很可能帶來一些問題,那么我們來看看在RR級(jí)別中MySQL的表現(xiàn):

事務(wù)A事務(wù)B事務(wù)C

begin;begin;begin;

select id,class_name,teacher_id from class_teacher where?teacher_id=1;

idclass_nameteacher_id

1初三二班1

2初三一班1

update class_teacher set class_name='初三三班' where id=1;

commit;

insert into class_teacher values (null,'初三三班',1);

commit;

select id,class_name,teacher_id from class_teacher where?teacher_id=1;

idclass_nameteacher_id

1初三二班1

2初三一班1

沒有讀到事務(wù)B修改的數(shù)據(jù),和第一次sql讀取的一樣,是可重復(fù)讀的。

沒有讀到事務(wù)C新添加的數(shù)據(jù)。

commit;

我們注意到,當(dāng)teacher_id=1時(shí),事務(wù)A先做了一次讀取,事務(wù)B中間修改了id=1的數(shù)據(jù),并commit之后,事務(wù)A第二次讀到的數(shù)據(jù)和第一次完全相同。所以說它是可重讀的。那么MySQL是怎么做到的呢?這里姑且賣個(gè)關(guān)子,我們往下看。

####不可重復(fù)讀和幻讀的區(qū)別####

很多人容易搞混不可重復(fù)讀和幻讀,確實(shí)這兩者有些相似。但不可重復(fù)讀重點(diǎn)在于update和delete,而幻讀的重點(diǎn)在于insert。

如果使用鎖機(jī)制來實(shí)現(xiàn)這兩種隔離級(jí)別,在可重復(fù)讀中,該sql第一次讀取到數(shù)據(jù)后,就將這些數(shù)據(jù)加鎖,其它事務(wù)無法修改這些數(shù)據(jù),就可以實(shí)現(xiàn)可重復(fù)讀了。但這種方法卻無法鎖住insert的數(shù)據(jù),所以當(dāng)事務(wù)A先前讀取了數(shù)據(jù),或者修改了全部數(shù)據(jù),事務(wù)B還是可以insert數(shù)據(jù)提交,這時(shí)事務(wù)A就會(huì)發(fā)現(xiàn)莫名其妙多了一條之前沒有的數(shù)據(jù),這就是幻讀,不能通過行鎖來避免。需要Serializable隔離級(jí)別 ,讀用讀鎖,寫用寫鎖,讀鎖和寫鎖互斥,這么做可以有效的避免幻讀、不可重復(fù)讀、臟讀等問題,但會(huì)極大的降低數(shù)據(jù)庫的并發(fā)能力。

所以說不可重復(fù)讀和幻讀最大的區(qū)別,就在于如何通過鎖機(jī)制來解決他們產(chǎn)生的問題。

上文說的,是使用悲觀鎖機(jī)制來處理這兩種問題,但是MySQL、ORACLE、PostgreSQL等成熟的數(shù)據(jù)庫,出于性能考慮,都是使用了以樂觀鎖為理論基礎(chǔ)的MVCC(多版本并發(fā)控制)來避免這兩種問題。

####悲觀鎖和樂觀鎖####

悲觀鎖

正如其名,它指的是對(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ù))。

在悲觀鎖的情況下,為了保證事務(wù)的隔離性,就需要一致性鎖定讀。讀取數(shù)據(jù)時(shí)給加鎖,其它事務(wù)無法修改這些數(shù)據(jù)。修改刪除數(shù)據(jù)時(shí)也要加鎖,其它事務(wù)無法讀取這些數(shù)據(jù)。

樂觀鎖

相對(duì)悲觀鎖而言,樂觀鎖機(jī)制采取了更加寬松的加鎖機(jī)制。悲觀鎖大多數(shù)情況下依靠數(shù)據(jù)庫的鎖機(jī)制實(shí)現(xiàn),以保證操作最大程度的獨(dú)占性。但隨之而來的就是數(shù)據(jù)庫性能的大量開銷,特別是對(duì)長事務(wù)而言,這樣的開銷往往無法承受。

而樂觀鎖機(jī)制在一定程度上解決了這個(gè)問題。樂觀鎖,大多是基于數(shù)據(jù)版本( Version )記錄機(jī)制實(shí)現(xiàn)。何謂數(shù)據(jù)版本?即為數(shù)據(jù)增加一個(gè)版本標(biāo)識(shí),在基于數(shù)據(jù)庫表的版本解決方案中,一般是通過為數(shù)據(jù)庫表增加一個(gè) “version” 字段來實(shí)現(xiàn)。讀取出數(shù)據(jù)時(shí),將此版本號(hào)一同讀出,之后更新時(shí),對(duì)此版本號(hào)加一。此時(shí),將提交數(shù)據(jù)的版本數(shù)據(jù)與數(shù)據(jù)庫表對(duì)應(yīng)記錄的當(dāng)前版本信息進(jìn)行比對(duì),如果提交的數(shù)據(jù)版本號(hào)大于數(shù)據(jù)庫表當(dāng)前版本號(hào),則予以更新,否則認(rèn)為是過期數(shù)據(jù)。

要說明的是,MVCC的實(shí)現(xiàn)沒有固定的規(guī)范,每個(gè)數(shù)據(jù)庫都會(huì)有不同的實(shí)現(xiàn)方式,這里討論的是InnoDB的MVCC。

####MVCC在MySQL的InnoDB中的實(shí)現(xiàn)

在InnoDB中,會(huì)在每行數(shù)據(jù)后添加兩個(gè)額外的隱藏的值來實(shí)現(xiàn)MVCC,這兩個(gè)值一個(gè)記錄這行數(shù)據(jù)何時(shí)被創(chuàng)建,另外一個(gè)記錄這行數(shù)據(jù)何時(shí)過期(或者被刪除)。 在實(shí)際操作中,存儲(chǔ)的并不是時(shí)間,而是事務(wù)的版本號(hào),每開啟一個(gè)新事務(wù),事務(wù)的版本號(hào)就會(huì)遞增。 在可重讀Repeatable reads事務(wù)隔離級(jí)別下:

SELECT時(shí),讀取創(chuàng)建版本號(hào)<=當(dāng)前事務(wù)版本號(hào),刪除版本號(hào)為空或>當(dāng)前事務(wù)版本號(hào)。

INSERT時(shí),保存當(dāng)前事務(wù)版本號(hào)為行的創(chuàng)建版本號(hào)

DELETE時(shí),保存當(dāng)前事務(wù)版本號(hào)為行的刪除版本號(hào)

UPDATE時(shí),插入一條新紀(jì)錄,保存當(dāng)前事務(wù)版本號(hào)為行創(chuàng)建版本號(hào),同時(shí)保存當(dāng)前事務(wù)版本號(hào)到原來刪除的行

通過MVCC,雖然每行記錄都需要額外的存儲(chǔ)空間,更多的行檢查工作以及一些額外的維護(hù)工作,但可以減少鎖的使用,大多數(shù)讀操作都不用加鎖,讀數(shù)據(jù)操作很簡單,性能很好,并且也能保證只會(huì)讀取到符合標(biāo)準(zhǔn)的行,也只鎖住必要行。

我們不管從數(shù)據(jù)庫方面的教課書中學(xué)到,還是從網(wǎng)絡(luò)上看到,大都是上文中事務(wù)的四種隔離級(jí)別這一模塊列出的意思,RR級(jí)別是可重復(fù)讀的,但無法解決幻讀,而只有在Serializable級(jí)別才能解決幻讀。于是我就加了一個(gè)事務(wù)C來展示效果。在事務(wù)C中添加了一條teacher_id=1的數(shù)據(jù)commit,RR級(jí)別中應(yīng)該會(huì)有幻讀現(xiàn)象,事務(wù)A在查詢teacher_id=1的數(shù)據(jù)時(shí)會(huì)讀到事務(wù)C新加的數(shù)據(jù)。但是測試后發(fā)現(xiàn),在MySQL中是不存在這種情況的,在事務(wù)C提交后,事務(wù)A還是不會(huì)讀到這條數(shù)據(jù)。可見在MySQL的RR級(jí)別中,是解決了幻讀的讀問題的。參見下圖

讀問題解決了,根據(jù)MVCC的定義,并發(fā)提交數(shù)據(jù)時(shí)會(huì)出現(xiàn)沖突,那么沖突時(shí)如何解決呢?我們再來看看InnoDB中RR級(jí)別對(duì)于寫數(shù)據(jù)的處理。

####“讀”與“讀”的區(qū)別

可能有讀者會(huì)疑惑,事務(wù)的隔離級(jí)別其實(shí)都是對(duì)于讀數(shù)據(jù)的定義,但到了這里,就被拆成了讀和寫兩個(gè)模塊來講解。這主要是因?yàn)镸ySQL中的讀,和事務(wù)隔離級(jí)別中的讀,是不一樣的。

我們且看,在RR級(jí)別中,通過MVCC機(jī)制,雖然讓數(shù)據(jù)變得可重復(fù)讀,但我們讀到的數(shù)據(jù)可能是歷史數(shù)據(jù),是不及時(shí)的數(shù)據(jù),不是數(shù)據(jù)庫當(dāng)前的數(shù)據(jù)!這在一些對(duì)于數(shù)據(jù)的時(shí)效特別敏感的業(yè)務(wù)中,就很可能出問題。

對(duì)于這種讀取歷史數(shù)據(jù)的方式,我們叫它快照讀 (snapshot read),而讀取數(shù)據(jù)庫當(dāng)前版本數(shù)據(jù)的方式,叫當(dāng)前讀 (current read)。很顯然,在MVCC中:

快照讀:就是select

select * from table ....;

當(dāng)前讀:特殊的讀操作,插入/更新/刪除操作,屬于當(dāng)前讀,處理的都是當(dāng)前的數(shù)據(jù),需要加鎖。

select * from table where ? lock in share mode;

select * from table where ? for update;

insert;

update ;

delete;

事務(wù)的隔離級(jí)別實(shí)際上都是定義了當(dāng)前讀的級(jí)別,MySQL為了減少鎖處理(包括等待其它鎖)的時(shí)間,提升并發(fā)能力,引入了快照讀的概念,使得select不用加鎖。而update、insert這些“當(dāng)前讀”,就需要另外的模塊來解決了。

###寫("當(dāng)前讀")

事務(wù)的隔離級(jí)別中雖然只定義了讀數(shù)據(jù)的要求,實(shí)際上這也可以說是寫數(shù)據(jù)的要求。上文的“讀”,實(shí)際是講的快照讀;而這里說的“寫”就是當(dāng)前讀了。

為了解決當(dāng)前讀中的幻讀問題,MySQL事務(wù)使用了Next-Key鎖。

####Next-Key鎖

Next-Key鎖是行鎖和GAP(間隙鎖)的合并,行鎖上文已經(jīng)介紹了,接下來說下GAP間隙鎖。

行鎖可以防止不同事務(wù)版本的數(shù)據(jù)修改提交時(shí)造成數(shù)據(jù)沖突的情況。但如何避免別的事務(wù)插入數(shù)據(jù)就成了問題。我們可以看看RR級(jí)別和RC級(jí)別的對(duì)比

RC級(jí)別:

事務(wù)A事務(wù)B

begin;begin;

select id,class_name,teacher_id from class_teacher where teacher_id=30;

idclass_nameteacher_id

2初三二班30

update class_teacher set class_name='初三四班' where teacher_id=30;

insert into class_teacher values (null,'初三二班',30);

commit;

select id,class_name,teacher_id from class_teacher where teacher_id=30;

idclass_nameteacher_id

2初三四班30

10初三二班30

RR級(jí)別:

事務(wù)A事務(wù)B

begin;begin;

select id,class_name,teacher_id from class_teacher where teacher_id=30;

idclass_nameteacher_id

2初三二班30

update class_teacher set class_name='初三四班' where teacher_id=30;

insert into class_teacher values (null,'初三二班',30);

waiting....

select id,class_name,teacher_id from class_teacher where teacher_id=30;

idclass_nameteacher_id

2初三四班30

commit;事務(wù)Acommit后,事務(wù)B的insert執(zhí)行。

通過對(duì)比我們可以發(fā)現(xiàn),在RC級(jí)別中,事務(wù)A修改了所有teacher_id=30的數(shù)據(jù),但是當(dāng)事務(wù)Binsert進(jìn)新數(shù)據(jù)后,事務(wù)A發(fā)現(xiàn)莫名其妙多了一行teacher_id=30的數(shù)據(jù),而且沒有被之前的update語句所修改,這就是“當(dāng)前讀”的幻讀。

RR級(jí)別中,事務(wù)A在update后加鎖,事務(wù)B無法插入新數(shù)據(jù),這樣事務(wù)A在update前后讀的數(shù)據(jù)保持一致,避免了幻讀。這個(gè)鎖,就是Gap鎖。

MySQL是這么實(shí)現(xiàn)的:

在class_teacher這張表中,teacher_id是個(gè)索引,那么它就會(huì)維護(hù)一套B+樹的數(shù)據(jù)關(guān)系,為了簡化,我們用鏈表結(jié)構(gòu)來表達(dá)(實(shí)際上是個(gè)樹形結(jié)構(gòu),但原理相同)

如圖所示,InnoDB使用的是聚集索引,teacher_id身為二級(jí)索引,就要維護(hù)一個(gè)索引字段和主鍵id的樹狀結(jié)構(gòu)(這里用鏈表形式表現(xiàn)),并保持順序排列。

Innodb將這段數(shù)據(jù)分成幾個(gè)個(gè)區(qū)間

(negative infinity, 5],

(5,30],

(30,positive infinity);

update class_teacher set class_name='初三四班' where teacher_id=30;不僅用行鎖,鎖住了相應(yīng)的數(shù)據(jù)行;同時(shí)也在兩邊的區(qū)間,(5,30]和(30,positive infinity),都加入了gap鎖。這樣事務(wù)B就無法在這個(gè)兩個(gè)區(qū)間insert進(jìn)新數(shù)據(jù)。

受限于這種實(shí)現(xiàn)方式,Innodb很多時(shí)候會(huì)鎖住不需要鎖的區(qū)間。如下所示:

事務(wù)A事務(wù)B事務(wù)C

begin;begin;begin;

select id,class_name,teacher_id from class_teacher;

idclass_nameteacher_id

1初三一班5

2初三二班30

update class_teacher set class_name='初一一班' where teacher_id=20;

insert into class_teacher values (null,'初三五班',10);

waiting .....

insert into class_teacher values (null,'初三五班',40);

commit;事務(wù)A commit之后,這條語句才插入成功commit;

commit;

update的teacher_id=20是在(5,30]區(qū)間,即使沒有修改任何數(shù)據(jù),Innodb也會(huì)在這個(gè)區(qū)間加gap鎖,而其它區(qū)間不會(huì)影響,事務(wù)C正常插入。

如果使用的是沒有索引的字段,比如update class_teacher set teacher_id=7 where class_name='初三八班(即使沒有匹配到任何數(shù)據(jù))',那么會(huì)給全表加入gap鎖。同時(shí),它不能像上文中行鎖一樣經(jīng)過MySQL Server過濾自動(dòng)解除不滿足條件的鎖,因?yàn)闆]有索引,則這些字段也就沒有排序,也就沒有區(qū)間。除非該事務(wù)提交,否則其它事務(wù)無法插入任何數(shù)據(jù)。

行鎖防止別的事務(wù)修改或刪除,GAP鎖防止別的事務(wù)新增,行鎖和GAP鎖結(jié)合形成的的Next-Key鎖共同解決了RR級(jí)別在寫數(shù)據(jù)時(shí)的幻讀問題。

###Serializable

這個(gè)級(jí)別很簡單,讀加共享鎖,寫加排他鎖,讀寫互斥。使用的悲觀鎖的理論,實(shí)現(xiàn)簡單,數(shù)據(jù)更加安全,但是并發(fā)能力非常差。如果你的業(yè)務(wù)并發(fā)的特別少或者沒有并發(fā),同時(shí)又要求數(shù)據(jù)及時(shí)可靠的話,可以使用這種模式。

這里要吐槽一句,不要看到select就說不會(huì)加鎖了,在Serializable這個(gè)級(jí)別,還是會(huì)加鎖的!

參考資料

MySQL參考手冊

《高性能MySQL》第三版

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,461評(píng)論 6 532
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,538評(píng)論 3 417
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,423評(píng)論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,991評(píng)論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,761評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,207評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,419評(píng)論 0 288
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,959評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,782評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,983評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,222評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,653評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,901評(píng)論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,678評(píng)論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,978評(píng)論 2 374

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