• <fieldset id="8imwq"><menu id="8imwq"></menu></fieldset>
  • <bdo id="8imwq"><input id="8imwq"></input></bdo>
    最新文章專題視頻專題問(wèn)答1問(wèn)答10問(wèn)答100問(wèn)答1000問(wèn)答2000關(guān)鍵字專題1關(guān)鍵字專題50關(guān)鍵字專題500關(guān)鍵字專題1500TAG最新視頻文章推薦1 推薦3 推薦5 推薦7 推薦9 推薦11 推薦13 推薦15 推薦17 推薦19 推薦21 推薦23 推薦25 推薦27 推薦29 推薦31 推薦33 推薦35 推薦37視頻文章20視頻文章30視頻文章40視頻文章50視頻文章60 視頻文章70視頻文章80視頻文章90視頻文章100視頻文章120視頻文章140 視頻2關(guān)鍵字專題關(guān)鍵字專題tag2tag3文章專題文章專題2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章專題3
    問(wèn)答文章1 問(wèn)答文章501 問(wèn)答文章1001 問(wèn)答文章1501 問(wèn)答文章2001 問(wèn)答文章2501 問(wèn)答文章3001 問(wèn)答文章3501 問(wèn)答文章4001 問(wèn)答文章4501 問(wèn)答文章5001 問(wèn)答文章5501 問(wèn)答文章6001 問(wèn)答文章6501 問(wèn)答文章7001 問(wèn)答文章7501 問(wèn)答文章8001 問(wèn)答文章8501 問(wèn)答文章9001 問(wèn)答文章9501
    當(dāng)前位置: 首頁(yè) - 科技 - 知識(shí)百科 - 正文

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

    來(lái)源:懂視網(wǎng) 責(zé)編:小采 時(shí)間:2020-11-09 20:54:37
    文檔

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

    MySQL中Innodb的事務(wù)隔離級(jí)別和鎖的關(guān)系的講解教程:前言: 我們都知道事務(wù)的幾種性質(zhì),數(shù)據(jù)庫(kù)為了維護(hù)這些性質(zhì),尤其是一致性和隔離性,一般使用加鎖這種方式。同時(shí)數(shù)據(jù)庫(kù)又是個(gè)高并發(fā)的應(yīng)用,同一時(shí)間會(huì)有大量的并發(fā)訪問(wèn),如果加鎖過(guò)度,會(huì)極大的降低并發(fā)處理能力。所以對(duì)于加鎖的處理,可以說(shuō)就是數(shù)據(jù)庫(kù)對(duì)于事
    推薦度:
    導(dǎo)讀MySQL中Innodb的事務(wù)隔離級(jí)別和鎖的關(guān)系的講解教程:前言: 我們都知道事務(wù)的幾種性質(zhì),數(shù)據(jù)庫(kù)為了維護(hù)這些性質(zhì),尤其是一致性和隔離性,一般使用加鎖這種方式。同時(shí)數(shù)據(jù)庫(kù)又是個(gè)高并發(fā)的應(yīng)用,同一時(shí)間會(huì)有大量的并發(fā)訪問(wèn),如果加鎖過(guò)度,會(huì)極大的降低并發(fā)處理能力。所以對(duì)于加鎖的處理,可以說(shuō)就是數(shù)據(jù)庫(kù)對(duì)于事

    前言:

    我們都知道事務(wù)的幾種性質(zhì),數(shù)據(jù)庫(kù)為了維護(hù)這些性質(zhì),尤其是一致性和隔離性,一般使用加鎖這種方式。同時(shí)數(shù)據(jù)庫(kù)又是個(gè)高并發(fā)的應(yīng)用,同一時(shí)間會(huì)有大量的并發(fā)訪問(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ā)訪問(wèn),為了預(yù)防死鎖,一般應(yīng)用中推薦使用一次封鎖法,就是在方法的開始階段,已經(jīng)預(yù)先知道會(huì)用到哪些數(shù)據(jù),然后全部鎖住,在方法運(yùn)行之后,再全部解鎖。這種方式可以有效的避免循環(huán)死鎖,但在數(shù)據(jù)庫(kù)中卻不適用,因?yàn)樵谑聞?wù)開始階段,數(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)行寫操作之前要申請(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ì)話中未提交事務(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)的查詢都是事務(wù)開始時(shí)刻一致的,InnoDB默認(rèn)級(jí)別。在SQL標(biāo)準(zhǔn)中,該隔離級(jí)別消除了不可重復(fù)讀,但是還存在幻象讀
    串行讀(Serializable):完全串行化的讀,每次讀都需要獲得表級(jí)共享鎖,讀寫相互都會(huì)阻塞
    Read Uncommitted這種級(jí)別,數(shù)據(jù)庫(kù)一般都不會(huì)用,而且任何操作都不會(huì)加鎖,這里就不討論了。

    MySQL中鎖的種類
    MySQL中鎖的種類很多,有常見(jiàn)的表鎖和行鎖,也有新加入的Metadata Lock等等,表鎖是對(duì)一整張表加鎖,雖然可分為讀鎖和寫鎖,但畢竟是鎖住整張表,會(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ù)的寫入、修改和刪除是需要加鎖的。效果如下

    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的模式

    SET session transaction isolation level read committed;
    SET SESSION binlog_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ā)過(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)不滿足后,會(huì)調(diào)用unlock_row方法,把不滿足條件的記錄釋放鎖 (違背了二段鎖協(xié)議的約束)。這樣做,保證了最后只會(huì)持有滿足條件記錄上的鎖,但是每條記錄的加鎖操作還是不能省略的。可見(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í)別。我們姑且分“讀”和“寫”兩個(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

    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;
    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ù),和第一次查詢的結(jié)果不一樣,是不可重讀的。

    commit; 
    
    

    事務(wù)B修改id=1的數(shù)據(jù)提交之后,事務(wù)A同樣的查詢,后一次和前一次的結(jié)果不一樣,這就是不可重讀(重新讀取產(chǎn)生的結(jié)果不一樣)。這就很可能帶來(lái)一些問(wèn)題,那么我們來(lái)看看在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;
    
    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ù)。

    commit; 
    

    我們注意到,當(dāng)teacher_id=1時(shí),事務(wù)A先做了一次讀取,事務(wù)B中間修改了id=1的數(shù)據(jù),并commit之后,事務(wù)A第二次讀到的數(shù)據(jù)和第一次完全相同。所以說(shuō)它是可重讀的。那么MySQL是怎么做到的呢?這里姑且賣個(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í)別 ,讀用讀鎖,寫用寫鎖,讀鎖和寫鎖互斥,這么做可以有效的避免幻讀、不可重復(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ù)訪問(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ù)性能的大量開銷,特別是對(duì)長(zhǎng)事務(wù)而言,這樣的開銷往往無(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),每開啟一個(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ù)方面的教課書中學(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在查詢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)下圖

    20151125164322895.png (1530×1093)

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

    “讀”與“讀”的區(qū)別
    可能有讀者會(huì)疑惑,事務(wù)的隔離級(jí)別其實(shí)都是對(duì)于讀數(shù)據(jù)的定義,但到了這里,就被拆成了讀和寫兩個(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

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

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

    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ù)據(jù)關(guān)系,為了簡(jiǎn)化,我們用鏈表結(jié)構(gòu)來(lái)表達(dá)(實(shí)際上是個(gè)樹形結(jié)構(gòu),但原理相同)

    20151125164349667.png (664×373)

    如圖所示,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就無(wú)法在這個(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;
    
    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)解除不滿足條件的鎖,因?yàn)闆](méi)有索引,則這些字段也就沒(méi)有排序,也就沒(méi)有區(qū)間。除非該事務(wù)提交,否則其它事務(wù)無(wú)法插入任何數(shù)據(jù)。

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

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

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

    您可能感興趣的文章:

  • MySQL鎖(表鎖,行鎖,共享鎖,排它鎖,間隙鎖)使用詳解
  • mysql鎖表和解鎖語(yǔ)句分享
  • MySQL行級(jí)鎖、表級(jí)鎖、頁(yè)級(jí)鎖詳細(xì)介紹
  • Mysql數(shù)據(jù)庫(kù)鎖定機(jī)制詳細(xì)介紹
  • MYSQL鎖表問(wèn)題的解決方法
  • mysql 數(shù)據(jù)庫(kù)死鎖原因及解決辦法
  • mysql數(shù)據(jù)庫(kù)鎖的產(chǎn)生原因及解決辦法
  • MySQL死鎖問(wèn)題分析及解決方法實(shí)例詳解
  • mysql 鎖表鎖行語(yǔ)句分享(MySQL事務(wù)處理)
  • 查找MySQL線程中死鎖的ID的方法
  • mysql鎖定單個(gè)表的方法
  • mysql共享鎖與排他鎖用法實(shí)例分析
  • 聲明:本網(wǎng)頁(yè)內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問(wèn)題請(qǐng)及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com

    文檔

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

    MySQL中Innodb的事務(wù)隔離級(jí)別和鎖的關(guān)系的講解教程:前言: 我們都知道事務(wù)的幾種性質(zhì),數(shù)據(jù)庫(kù)為了維護(hù)這些性質(zhì),尤其是一致性和隔離性,一般使用加鎖這種方式。同時(shí)數(shù)據(jù)庫(kù)又是個(gè)高并發(fā)的應(yīng)用,同一時(shí)間會(huì)有大量的并發(fā)訪問(wèn),如果加鎖過(guò)度,會(huì)極大的降低并發(fā)處理能力。所以對(duì)于加鎖的處理,可以說(shuō)就是數(shù)據(jù)庫(kù)對(duì)于事
    推薦度:
    標(biāo)簽: mysql 事務(wù) 鎖的
    • 熱門焦點(diǎn)

    最新推薦

    猜你喜歡

    熱門推薦

    專題
    Top
    主站蜘蛛池模板: 中文精品久久久久人妻| 精品第一国产综合精品蜜芽| 99re6这里有精品热视频| 国产精品原创巨作?v网站| 99在线精品视频观看免费| 亚洲国产精品不卡毛片a在线| 2021国产成人精品久久| 久久国产精品成人片免费| 四虎亚洲国产成人久久精品| 国产精品粉嫩美女在线观看| 国产精品三级在线观看无码| 久久精品亚洲精品国产色婷| 精品99久久aaa一级毛片| 久久99热狠狠色精品一区| 精品人伦一区二区三区潘金莲| 四虎亚洲国产成人久久精品| 国产欧美精品专区一区二区| 日本欧美韩国日本精品| 精品免费久久久久国产一区| 你懂的国产精品| 99re6在线视频精品免费| 久久精品亚洲一区二区三区浴池 | 日韩美女18网站久久精品| 国产精品日日摸夜夜添夜夜添1国产精品va欧美精 | 思思久久99热免费精品6| 2021最新国产精品一区| 精品无码无人网站免费视频| 一色屋精品视频在线观看| 亚洲精品午夜无码专区| 亚洲精品高清在线| 亚洲?V乱码久久精品蜜桃| 亚洲国产成人a精品不卡在线| 欧美日韩综合精品| 亚洲?V乱码久久精品蜜桃| 四虎永久在线精品免费一区二区| 久久人人超碰精品CAOPOREN| 国内精品99亚洲免费高清| 巨大黑人极品VIDEOS精品| 无码国产亚洲日韩国精品视频一区二区三区| 欧美成人精品一区二区综合| 欧美精品色婷婷五月综合|