激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

服務(wù)器之家:專(zhuān)注于服務(wù)器技術(shù)及軟件下載分享
分類(lèi)導(dǎo)航

Mysql|Sql Server|Oracle|Redis|MongoDB|PostgreSQL|Sqlite|DB2|mariadb|Access|數(shù)據(jù)庫(kù)技術(shù)|

服務(wù)器之家 - 數(shù)據(jù)庫(kù) - Mysql - MySQL中Innodb的事務(wù)隔離級(jí)別和鎖的關(guān)系的講解教程

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

2020-05-24 15:35MYSQL教程網(wǎng) Mysql

這篇文章主要介紹了MySQL中Innodb的事務(wù)隔離級(jí)別和鎖的關(guān)系講解教程,來(lái)自于美團(tuán)技術(shù)團(tuán)隊(duì)的經(jīng)驗(yàn)實(shí)際經(jīng)驗(yàn)分享,需要的朋友可以參考下

前言:

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

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

加鎖階段:在該階段可以進(jìn)行加鎖操作。在對(duì)任何數(shù)據(jù)進(jìn)行讀操作之前要申請(qǐng)并獲得S鎖(共享鎖,其它事務(wù)可以繼續(xù)加共享鎖,但不能加排它鎖),在進(jìn)行寫(xiě)操作之前要申請(qǐng)并獲得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)的鎖
這種方式雖然無(wú)法避免死鎖,但是兩段鎖協(xié)議可以保證事務(wù)的并發(fā)調(diào)度是串行化(串行化很重要,尤其是在數(shù)據(jù)恢復(fù)和備份的時(shí)候)的。

事務(wù)中的加鎖方式
事務(wù)的四種隔離級(jí)別

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

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

  • 未提交讀(Read uncommitted) 可能 可能 可能
  • 已提交讀(Read committed) 不可能 可能 可能
  • 可重復(fù)讀(Repeatable read) 不可能 不可能 可能
  • 可串行化(Serializable ) 不可能 不可能 不可能

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

提交讀(Read Committed):只能讀取到已經(jīng)提交的數(shù)據(jù)。Oracle等多數(shù)數(shù)據(jù)庫(kù)默認(rèn)都是該級(jí)別 (不重復(fù)讀)
可重復(fù)讀(Repeated Read):可重復(fù)讀。在同一個(gè)事務(wù)內(nèi)的查詢(xún)都是事務(wù)開(kāi)始時(shí)刻一致的,InnoDB默認(rèn)級(jí)別。在SQL標(biāo)準(zhǔn)中,該隔離級(jí)別消除了不可重復(fù)讀,但是還存在幻象讀
串行讀(Serializable):完全串行化的讀,每次讀都需要獲得表級(jí)共享鎖,讀寫(xiě)相互都會(huì)阻塞
Read Uncommitted這種級(jí)別,數(shù)據(jù)庫(kù)一般都不會(huì)用,而且任何操作都不會(huì)加鎖,這里就不討論了。

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

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

Read Committed(讀取提交內(nèi)容)
在RC級(jí)別中,數(shù)據(jù)的讀取都是不加鎖的,但是數(shù)據(jù)的寫(xiě)入、修改和刪除是需要加鎖的。效果如下

?
1
2
3
4
5
6
7
8
9
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
1 row in set (0.02 sec)
?
1
MySQL> select * from class_teacher;
?
1
2
3
4
5
6
7
+----+--------------+------------+
| id | class_name  | teacher_id |
+----+--------------+------------+
| 1 | 初三一班   |     1 |
| 3 | 初二一班   |     2 |
| 4 | 初二二班   |     2 |
+----+--------------+------------+

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

?
1
2
SET session transaction isolation level read committed;
SET SESSION binlog_format = 'ROW';

(或者是MIXED)
事務(wù)A 事務(wù)B
begin; begin;

?
1
update class_teacher set class_name='初三二班' where teacher_id=1; update class_teacher set class_name='初三三班' where teacher_id=1;
?
1
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
?
1
commit;

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

這時(shí)我們要注意到,teacher_id是有索引的,如果是沒(méi)有索引的class_name呢?update class_teacher set teacher_id=3 where class_name = '初三一班';
那么MySQL會(huì)給整張表的所有數(shù)據(jù)行的加行鎖。這里聽(tīng)起來(lái)有點(diǎn)不可思議,但是當(dāng)sql運(yùn)行的過(guò)程中,MySQL并不知道哪些數(shù)據(jù)行是 class_name = '初三一班'的(沒(méi)有索引嘛),如果一個(gè)條件無(wú)法通過(guò)索引快速過(guò)濾,存儲(chǔ)引擎層面就會(huì)將所有記錄加鎖后返回,再由MySQL Server層進(jìn)行過(guò)濾。

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

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

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


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

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

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

?
1
2
begin;
begin;
?
1
2
3
4
5
6
7
8
9
10
11
12
13
select id,class_name,teacher_id from class_teacher where teacher_id=1;
 
id class_name teacher_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;
 
id class_name teacher_id
1 初三三班 1
2 初三一班 1

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

?
1
commit;

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


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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
begin;
begin;
 
begin;
 
select id,class_name,teacher_id from class_teacher where teacher_id=1;
 
id class_name teacher_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;
 
id class_name teacher_id
1 初三二班 1
2 初三一班 1

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

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

?
1
commit;

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

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

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

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

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

悲觀鎖和樂(lè)觀鎖
悲觀鎖

正如其名,它指的是對(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ù)訪(fǎng)問(wèn)的排他性,否則,即使在本系統(tǒng)中實(shí)現(xiàn)了加鎖機(jī)制,也無(wú)法保證外部系統(tǒng)不會(huì)修改數(shù)據(jù))。

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

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

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

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

MVCC在MySQL的InnoDB中的實(shí)現(xiàn)
在InnoDB中,會(huì)在每行數(shù)據(jù)后添加兩個(gè)額外的隱藏的值來(lái)實(shí)現(xiàn)MVCC,這兩個(gè)值一個(gè)記錄這行數(shù)據(jù)何時(shí)被創(chuàng)建,另外一個(gè)記錄這行數(shù)據(jù)何時(shí)過(guò)期(或者被刪除)。 在實(shí)際操作中,存儲(chǔ)的并不是時(shí)間,而是事務(wù)的版本號(hào),每開(kāi)啟一個(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)到原來(lái)刪除的行
  • 通過(guò)MVCC,雖然每行記錄都需要額外的存儲(chǔ)空間,更多的行檢查工作以及一些額外的維護(hù)工作,但可以減少鎖的使用,大多數(shù)讀操作都不用加鎖,讀數(shù)據(jù)操作很簡(jiǎn)單,性能很好,并且也能保證只會(huì)讀取到符合標(biāo)準(zhǔn)的行,也只鎖住必要行。

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

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

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

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

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

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

快照讀:就是select

?
1
select * from table ....;

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

?
1
2
3
4
5
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)前讀”,就需要另外的模塊來(lái)解決了。

寫(xiě)("當(dāng)前讀")
事務(wù)的隔離級(jí)別中雖然只定義了讀數(shù)據(jù)的要求,實(shí)際上這也可以說(shuō)是寫(xiě)數(shù)據(jù)的要求。上文的“讀”,實(shí)際是講的快照讀;而這里說(shuō)的“寫(xiě)”就是當(dāng)前讀了。
為了解決當(dāng)前讀中的幻讀問(wèn)題,MySQL事務(wù)使用了Next-Key鎖。

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

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

RC級(jí)別:

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
begin;
begin;
 
select id,class_name,teacher_id from class_teacher where teacher_id=30;
 
id class_name teacher_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;
 
id class_name teacher_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;
 
id class_name teacher_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;
 
id class_name teacher_id
2 初三四班 30
commit;

事務(wù)Acommit后,事務(wù)B的insert執(zhí)行。
通過(guò)對(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ù),而且沒(méi)有被之前的update語(yǔ)句所修改,這就是“當(dāng)前讀”的幻讀。

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

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

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

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

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

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

?
1
2
3
4
(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就無(wú)法在這個(gè)兩個(gè)區(qū)間insert進(jìn)新數(shù)據(jù)。

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

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
begin; begin; begin;
select id,class_name,teacher_id from class_teacher;
 
id class_name teacher_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之后,這條語(yǔ)句才插入成功 commit;
commit;

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

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

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

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

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

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 福利免费在线观看 | 男女一边摸一边做羞羞视频免费 | 天天夜夜操操 | 久久久久女人精品毛片九一 | 女人一区二区三区 | 国产精品999在线观看 | 国产精品久久久久久久久久iiiii | 黄网站在线免费看 | 欧美高清在线精品一区二区不卡 | 久久精品国产99久久久古代 | 成码无人av片在线观看网站 | 免费视频a| 国产精品片www48888 | 色奇米 | 一夜新娘第三季免费观看 | 91在线播放国产 | 国产91一区 | 久久久久久久久久91 | 久草在线综合 | 久久久久久久久久久久久久国产 | 啪啪毛片 | 亚洲电影在线观看高清免费 | 久久久久久久亚洲精品 | 国产免费观看a大片的网站 欧美成人一级 | 国产精品无码久久久久 | 亚洲影视在线 | 91 在线| 欧美在线观看视频一区二区 | 91快色| 一级α片免费看刺激高潮视频 | 一级黄色电影网站 | 免费三级大片 | 日本最新免费二区三区 | 最新中文字幕在线视频 | 欧美一级淫片a免费播放口 91九色蝌蚪国产 | 一夜新娘第三季免费观看 | 成人福利在线播放 | 草久影视 | 性欧美极品xxxx欧美一区二区 | 超污视频在线看 | 91久久国产综合精品女同国语 |