<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          InnoDB解決幻讀的方案 --- LBCC&MVCC

          共 8543字,需瀏覽 18分鐘

           ·

          2021-05-05 07:01

          最近要在公司內(nèi)做一次技術(shù)分享,思來(lái)想去不知道該分享些什么,最后在朋友的提示下,準(zhǔn)備分享一下MySQLInnoDB引擎下的事務(wù)幻讀問(wèn)題與解決方案--LBCC&MVCC。經(jīng)過(guò)好幾天的熬夜通宵,終于把這部分的內(nèi)容捋清楚了。至于為什么說(shuō)是InnoDB呢?因?yàn)?code style="">MyISAM引擎是不支持事務(wù)的。

          事務(wù)

          概念

          一個(gè)事情由n個(gè)單元組成,這n個(gè)單元在執(zhí)行過(guò)程中,要么同時(shí)成功,要么同時(shí)失敗,這就把n個(gè)單元放在了一個(gè)事務(wù)之中。舉個(gè)簡(jiǎn)單的例子:在不考慮試題正確與否的前提下,一張?jiān)嚲碛啥鄠€(gè)題目構(gòu)成,當(dāng)你答完題交給老師的時(shí)候是將一整張?jiān)嚲斫唤o老師,而不是將每道題單獨(dú)交給老師,在這里試卷就可以理解成一個(gè)事務(wù)。

          事務(wù)的特性:ACID

          A:原子性(Atomicity),原子性是指事務(wù)是一個(gè)不可分割的工作單位,事務(wù)中的操作,要么都發(fā)生,要么都不發(fā)生。

          :假設(shè)你在購(gòu)物車(chē)?yán)锾砑恿藘杉路荷弦潞脱澴?,?dāng)你把兩件衣服作為一個(gè)訂單提交支付的時(shí)候,要么兩件衣服一起支付成功,要么都失敗,不可能存在上衣付完錢(qián)了,褲子還沒(méi)付完的情況,反之亦然。

          C:一致性(Consistency),在一個(gè)事務(wù)中,事務(wù)前后數(shù)據(jù)的完整性必須保持一致。

          :假設(shè)用戶A和用戶B兩者的錢(qián)加起來(lái)一共是200,那么不管A和B之間如何轉(zhuǎn)賬,轉(zhuǎn)幾次賬,事務(wù)結(jié)束后兩個(gè)用戶的錢(qián)相加起來(lái)應(yīng)該還得是200,這就是事務(wù)的一致性。

          I:隔離性(Isolation),存在于多個(gè)事務(wù)中,事務(wù)的隔離性是指多個(gè)用戶并發(fā)訪問(wèn)數(shù)據(jù)庫(kù)時(shí),一個(gè)用戶的事務(wù)不能被其它用戶的事務(wù)所干擾,多個(gè)并發(fā)事務(wù)之間數(shù)據(jù)要相互隔離。

          :對(duì)于任意兩個(gè)并發(fā)的事務(wù)T1和T2,在事務(wù)T1看來(lái),T2要么在T1開(kāi)始之前就已經(jīng)結(jié)束,要么在T1結(jié)束之后才開(kāi)始,這樣每個(gè)事務(wù)都感覺(jué)不到有其他事務(wù)在并發(fā)地執(zhí)行。

          D:持久性(Durability),持久性是指一個(gè)事務(wù)一旦被提交,它對(duì)數(shù)據(jù)庫(kù)中數(shù)據(jù)的改變就是永久性的,接下來(lái)即使數(shù)據(jù)庫(kù)發(fā)生故障也不應(yīng)該對(duì)其有任何影響。

          :我們?cè)诓僮鲾?shù)據(jù)庫(kù)時(shí),事務(wù)提交或者回滾都會(huì)直接改變數(shù)據(jù)庫(kù)中的值。

          事務(wù)的操作

          在使用事務(wù)之前,首先我們要開(kāi)啟事務(wù),我們可以通過(guò)start或者begin命令開(kāi)啟事務(wù);如果我們想提交事務(wù)可以手動(dòng)執(zhí)行commit命令,如果我們想回滾事務(wù),可以執(zhí)行rollback命令。

          注:在MySQL中事務(wù)的提交是默認(rèn)開(kāi)啟的,可以執(zhí)行show variables like 'autocommit'命令查看,如果是ON則證明自動(dòng)提交已經(jīng)開(kāi)啟,如果為OFF則需要手動(dòng)提交。

          隔離性引發(fā)的并發(fā)問(wèn)題

          1)臟讀:B事務(wù)讀取到了A事務(wù)尚未提交的數(shù)據(jù);

          2)不可重復(fù)讀:B事務(wù)讀到了A事務(wù)已經(jīng)提交的數(shù)據(jù),即B事務(wù)在A事務(wù)提交之前和提交之后讀取到的數(shù)據(jù)內(nèi)容不一致(AB事務(wù)操作的是同一條數(shù)據(jù));

          3)幻讀/虛讀:B事務(wù)讀到了A事務(wù)已經(jīng)提交的數(shù)據(jù),即A事務(wù)執(zhí)行插入操作,B事務(wù)在A事務(wù)前后讀到的數(shù)據(jù)數(shù)量不一致。

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

          為了解決以上隔離性引發(fā)的并發(fā)問(wèn)題,數(shù)據(jù)庫(kù)提供了事物的隔離機(jī)制。

          • read uncommitted(讀未提交): 一個(gè)事務(wù)還沒(méi)提交時(shí),它做的變更就能被別的事務(wù)看到,讀取尚未提交的數(shù)據(jù),哪個(gè)問(wèn)題都不能解決;
          • read committed(讀已提交):一個(gè)事務(wù)提交之后,它做的變更才會(huì)被其他事務(wù)看到,讀取已經(jīng)提交的數(shù)據(jù),可以解決臟讀 ---- oracle默認(rèn)的;
          • repeatable read(可重復(fù)讀):一個(gè)事務(wù)執(zhí)行過(guò)程中看到的數(shù)據(jù),總是跟這個(gè)事務(wù)在啟動(dòng)時(shí)看到的數(shù)據(jù)是一致的,可以解決臟讀和不可重復(fù)讀 ---mysql默認(rèn)的;
          • serializable(串行化):顧名思義是對(duì)于同一行記錄,“寫(xiě)”會(huì)加“寫(xiě)鎖”,“讀”會(huì)加“讀鎖”。當(dāng)出現(xiàn)讀寫(xiě)鎖沖突的時(shí)候,后訪問(wèn)的事務(wù)必須等前一個(gè)事務(wù)執(zhí)行完成,才能繼續(xù)執(zhí)行??梢越鉀Q臟讀、不可重復(fù)讀和虛讀---相當(dāng)于鎖表。

          雖然serializable級(jí)別可以解決所有的數(shù)據(jù)庫(kù)并發(fā)問(wèn)題,但是它會(huì)在讀取的每一行數(shù)據(jù)上都加鎖,這就可能導(dǎo)致大量的超時(shí)和鎖競(jìng)爭(zhēng)問(wèn)題,從而導(dǎo)致效率下降。所以我們?cè)趯?shí)際應(yīng)用中也很少使用serializable,只有在非常需要確保數(shù)據(jù)的一致性而且可以接受沒(méi)有并發(fā)的情況下,才考慮采用該級(jí)別。

          LBCC&MVCC

          InnoDB默認(rèn)的事務(wù)隔離級(jí)別是repeatable read(后文中用簡(jiǎn)稱RR),它為了解決該隔離級(jí)別下的幻讀的并發(fā)問(wèn)題,提出了LBCCMVCC兩種方案。其中LBCC解決的是當(dāng)前讀情況下的幻讀,MVCC解決的是普通讀(快照讀)的幻讀。至于什么是當(dāng)前讀,什么是快照讀,將在下文中給出答案。

          LBCC

          LBCCLock-Based Concurrent Control的簡(jiǎn)稱,意思是基于鎖的并發(fā)控制。在InnoDB中按鎖的模式來(lái)分的話可以分為共享鎖(S)、排它鎖(X)和意向鎖,其中意向鎖又分為意向共享鎖(IS)和意向排它鎖(IX)(此處先不做介紹,后期會(huì)專(zhuān)門(mén)出篇文章講一下InnoDBMyisam引擎的鎖);如果按照鎖的算法來(lái)分的話又分為記錄鎖(Record Locks)、間隙鎖(Gap Locks)和臨鍵鎖(Next-key Locks)。其中臨鍵鎖就可以用來(lái)解決RR下的幻讀問(wèn)題。那么什么是臨鍵鎖呢?繼續(xù)往下看。

          我們將數(shù)據(jù)庫(kù)中存儲(chǔ)的每一行數(shù)據(jù)稱為記錄。則上圖中1、5、9、11分別代表id為當(dāng)前數(shù)的記錄。對(duì)于鍵值在條件范圍內(nèi)但不存在的記錄,叫做間隙(GAP)。則上圖中的(-∞,1)、(1,5)...(11,+∞)為數(shù)據(jù)庫(kù)中存在的間隙。而(-∞,1]、(1,5]...(11,+∞)我們稱之為臨鍵,即左開(kāi)右閉的集合。

          記錄鎖(Record Locks)

          對(duì)表中的行記錄加鎖,叫做記錄鎖,簡(jiǎn)稱行鎖??梢允褂?code style="">sql語(yǔ)句select ... for update來(lái)開(kāi)啟鎖,select語(yǔ)句必須為精準(zhǔn)匹配(=),不能為范圍匹配,且匹配列字段必須為唯一索引或者主鍵列。也可以通過(guò)對(duì)查詢條件為主鍵索引或唯一索引的數(shù)據(jù)行進(jìn)行UPDATE操作來(lái)添加記錄鎖。

          記錄鎖存在于包括主鍵索引在內(nèi)的唯一索引中,鎖定單條索引記錄。

          間隙鎖(GAP Locks)

          對(duì)上面說(shuō)到的間隙加鎖即為間隙鎖。間隙鎖是對(duì)范圍加鎖,但不包括已存在的索引項(xiàng)??梢允褂?code style="">sql語(yǔ)句select ... for update來(lái)開(kāi)啟鎖,select語(yǔ)句為范圍查詢,匹配列字段為索引項(xiàng),且沒(méi)有數(shù)據(jù)返回;或者select語(yǔ)句為等值查詢,匹配字段為唯一索引,也沒(méi)有數(shù)據(jù)返回。

          間隙鎖有一個(gè)比較致命的弱點(diǎn),就是當(dāng)鎖定一個(gè)范圍鍵值之后,即使某些不存在的鍵值也會(huì)被無(wú)辜的鎖定,而造成在鎖定的時(shí)候無(wú)法插入鎖定鍵值范圍內(nèi)的任何數(shù)據(jù)。在某些場(chǎng)景下這可能會(huì)對(duì)性能造成很大的危害。以下是加鎖之后,插入操作的例子:

          select * from user where id > 15 for update;
          //插入失敗,因?yàn)閕d20大于15,不難理解
          insert into user values(20,'20');
          //插入失敗,原因是間隙鎖鎖的是記錄間隙,而不是sql,也就是說(shuō)`select`語(yǔ)句的鎖范圍是(11,+∞),而13在這個(gè)區(qū)間中,所以也失敗。
          insert into user values(13,'13');

          GAP Locks只存在于RR隔離級(jí)別下,它鎖住的是間隙內(nèi)的數(shù)據(jù)。加完鎖之后,間隙中無(wú)法插入其他記錄,并且鎖的是記錄間隙,而非sql語(yǔ)句。間隙鎖之間都不存在沖突關(guān)系。

          打開(kāi)間隙鎖設(shè)置: 以通過(guò)命令show variables like 'innodb_locks_unsafe_for_binlog';來(lái)查看 innodb_locks_unsafe_for_binlog 是否禁用。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

          臨鍵鎖(Next-Key Locks)

          當(dāng)我們對(duì)上面的記錄和間隙共同加鎖時(shí),添加的便是臨鍵鎖(左開(kāi)右閉的集合加鎖)。為了防止幻讀,臨鍵鎖阻止特定條件的新記錄的插入,因?yàn)椴迦霑r(shí)要獲取插入意向鎖,與已持有的臨鍵鎖沖突。可以使用sql語(yǔ)句select ... for update來(lái)開(kāi)啟鎖,select語(yǔ)句為范圍查詢,匹配列字段為索引項(xiàng),且有數(shù)據(jù)返回;或者select語(yǔ)句為等值查詢,匹配列字段為索引項(xiàng),不管有沒(méi)有數(shù)據(jù)返回。

          插入意向鎖并非意向鎖,而是一種特殊的間隙鎖。

          總結(jié)

          • 如果查詢沒(méi)有命中索引,則退化為表鎖;
          • 如果等值查詢唯一索引且命中唯一一條記錄,則退化為行鎖;
          • 如果等值查詢唯一索引且沒(méi)有命中記錄,則退化為臨近結(jié)點(diǎn)的間隙鎖;
          • 如果等值查詢非唯一索引且沒(méi)有命中記錄,退化為臨近結(jié)點(diǎn)的間隙鎖(包括結(jié)點(diǎn)也被鎖定);如果命中記錄,則鎖定所有命中行的臨鍵鎖,并同時(shí)鎖定最大記錄行下一個(gè)區(qū)間的間隙鎖。
          • 如果范圍查詢唯一索引或查詢非唯一索引且命中記錄,則鎖定所有命中行的臨鍵鎖 ,并同時(shí)鎖定最大記錄行下一個(gè)區(qū)間的間隙鎖。
          • 如果范圍查詢索引且沒(méi)有命中記錄,退化為臨近結(jié)點(diǎn)的間隙鎖(包括結(jié)點(diǎn)也被鎖定)。

          當(dāng)前讀

          當(dāng)前讀(Locking Read)也稱鎖定讀,讀取當(dāng)前數(shù)據(jù)的最新版本,而且讀取到這個(gè)數(shù)據(jù)之后會(huì)對(duì)這個(gè)數(shù)據(jù)加鎖,防止別的事務(wù)更改即通過(guò)next-key鎖(行鎖+gap鎖)來(lái)解決當(dāng)前讀的問(wèn)題。在進(jìn)行寫(xiě)操作的時(shí)候就需要進(jìn)行“當(dāng)前讀”,讀取數(shù)據(jù)記錄的最新版本,包含以下SQL類(lèi)型:select ... lock in share mode 、select ... for update、update 、delete 、insert。

          MVCC

          LBCC是基于鎖的并發(fā)控制,因?yàn)殒i的粒度過(guò)大,會(huì)導(dǎo)致性能的下降,因此提出了比LBCC性能更優(yōu)越的方法MVCCMVCCMulti-Version Concurremt Control的簡(jiǎn)稱,意思是基于多版本的并發(fā)控制協(xié)議,通過(guò)版本號(hào),避免同一數(shù)據(jù)在不同事務(wù)間的競(jìng)爭(zhēng),只存在于InnoDB引擎下。它主要是為了提高數(shù)據(jù)庫(kù)的并發(fā)讀寫(xiě)性能,不用加鎖就能讓多個(gè)事務(wù)并發(fā)讀寫(xiě)。MVCC的實(shí)現(xiàn)依賴于:三個(gè)隱藏字段、Undo logRead View,其核心思想就是:只能查找事務(wù)id小于等于當(dāng)前事務(wù)ID的行;只能查找刪除時(shí)間大于等于當(dāng)前事務(wù)ID的行,或未刪除的行。接下來(lái)讓我們從源碼級(jí)別來(lái)分析下MVCC。

          隱藏列

          MySQL中會(huì)為每一行記錄生成隱藏列,接下來(lái)就讓我們了解一下這幾個(gè)隱藏列吧。

          (1)DB_TRX_ID:事務(wù)ID,是根據(jù)事務(wù)產(chǎn)生時(shí)間順序自動(dòng)遞增的,是獨(dú)一無(wú)二的。如果某個(gè)事務(wù)執(zhí)行過(guò)程中對(duì)該記錄執(zhí)行了增、刪、改操作,那么InnoDB存儲(chǔ)引擎就會(huì)記錄下該條事務(wù)的id。

          (2)DB_ROLL_PTR:回滾指針,本質(zhì)上就是一個(gè)指向記錄對(duì)應(yīng)的undo log的一個(gè)指針,大小為 7 個(gè)字節(jié),InnoDB 便是通過(guò)這個(gè)指針找到之前版本的數(shù)據(jù)。該行記錄上所有舊版本,在undo log中都通過(guò)鏈表的形式組織。

          (3)DB_ROW_ID:行標(biāo)識(shí)(隱藏單調(diào)自增 ID),如果表沒(méi)有主鍵,InnoDB 會(huì)自動(dòng)生成一個(gè)隱藏主鍵,大小為 6 字節(jié)。如果數(shù)據(jù)表沒(méi)有設(shè)置主鍵,會(huì)以它產(chǎn)生聚簇索引。

          (4)實(shí)際還有一個(gè)刪除flag隱藏字段,既記錄被更新或刪除并不代表真的刪除,而是刪除flag變了。

          undo log

          每當(dāng)我們要對(duì)一條記錄做改動(dòng)時(shí)(這里的改動(dòng)可以指INSERT、DELETE、UPDATE),都需要把回滾時(shí)所需的東西記錄下來(lái), 比如:

          • Insert undo log :插入一條記錄時(shí),至少要把這條記錄的主鍵值記下來(lái),之后回滾的時(shí)候只需要把這個(gè)主鍵值對(duì)應(yīng)的記錄刪掉就好了。
          • Delete undo log:刪除一條記錄時(shí),至少要把這條記錄中的內(nèi)容都記下來(lái),這樣之后回滾時(shí)再把由這些內(nèi)容組成的記錄插入到表中就好了。
          • Update undo log:修改一條記錄時(shí),至少要把修改這條記錄前的舊值都記錄下來(lái),這樣之后回滾時(shí)再把這條記錄更新為舊值就好了。InnoDB把這些為了回滾而記錄的這些東西稱之為undo log。這里需要注意的一點(diǎn)是,由于查詢操作(SELECT)并不會(huì)修改任何用戶記錄,所以在查詢操作執(zhí)行時(shí),并不需要記錄相應(yīng)的undo log。

          每次對(duì)記錄進(jìn)行改動(dòng)都會(huì)記錄一條undo日志,每條undo日志也都有一個(gè)DB_ROLL_PTR屬性,可以將這些undo日志都連起來(lái),串成一個(gè)鏈表,形成版本鏈。版本鏈的頭節(jié)點(diǎn)就是當(dāng)前記錄最新的值。

          先插入一條記錄,假設(shè)該記錄的事務(wù)id為80,那么此刻該條記錄的示意圖如下所示實(shí)際上insert undo只在事務(wù)回滾時(shí)起作用,當(dāng)事務(wù)提交后,該類(lèi)型的undo日志就沒(méi)用了,它占用的Undo Log Segment也會(huì)被系統(tǒng)回收。接著繼續(xù)執(zhí)行sql操作其版本鏈如下

          很多人以為undo log用于將數(shù)據(jù)庫(kù)物理的恢復(fù)到執(zhí)行語(yǔ)句或者事務(wù)之前的樣子,其實(shí)并非如此,undo log是邏輯日志,只是將數(shù)據(jù)庫(kù)邏輯的恢復(fù)到原來(lái)的樣子。因?yàn)樵诙嗖l(fā)系統(tǒng)中,你把一個(gè)頁(yè)中的數(shù)據(jù)物理的恢復(fù)到原來(lái)的樣子,可能會(huì)影響其他的事務(wù)。

          Read View

          在可重復(fù)讀隔離級(jí)別下,我們可以把每一次普通的select查詢(不加for update語(yǔ)句)當(dāng)作一次快照讀,而快照便是進(jìn)行select的那一刻,生成的當(dāng)前數(shù)據(jù)庫(kù)系統(tǒng)中所有未提交的事務(wù)id數(shù)組(數(shù)組里最小的idmin_id)和已經(jīng)創(chuàng)建的最大事務(wù)idmax_id)的集合,即我們所說(shuō)的一致性視圖readview。在進(jìn)行快照讀的過(guò)程中要根據(jù)一定的規(guī)則將版本鏈中每個(gè)版本的事務(wù)idreadview進(jìn)行匹配查詢我們需要的結(jié)果。

          快照讀是不會(huì)看到別的事務(wù)插入的數(shù)據(jù)的。因此,幻讀在“當(dāng)前讀”下才會(huì)出現(xiàn)??煺兆x的實(shí)現(xiàn)是基于多版本并發(fā)控制,即MVCC,可以認(rèn)為MVCC是行鎖的一個(gè)變種,但它在很多情況下,避免了加鎖操作,降低了開(kāi)銷(xiāo);既然是基于多版本,即快照讀可能讀到的并不一定是數(shù)據(jù)的最新版本,而有可能是之前的歷史版本。MVCC只在 READ COMMITTEDREPEATABLE READ兩個(gè)隔離級(jí)別下工作,其他兩個(gè)隔離級(jí)別不和MVCC不兼容。因?yàn)?code style="">READ UNCOMMITTED總是讀取最新的數(shù)據(jù)行,而不是符合當(dāng)前事務(wù)版本的數(shù)據(jù)行,而SERIALIZABLE 則會(huì)對(duì)所有讀取的行都加鎖。事務(wù)的快照時(shí)間點(diǎn)(即下文中說(shuō)到的Read View的生成時(shí)間)是以第一個(gè)select來(lái)確認(rèn)的。所以即便事務(wù)先開(kāi)始,但是select在后面的事務(wù)的update之類(lèi)的語(yǔ)句后進(jìn)行,那么它是可以獲取前面的事務(wù)的對(duì)應(yīng)的數(shù)據(jù)。

          RC和RR隔離級(jí)別下的快照讀和當(dāng)前讀:RC隔離級(jí)別下,快照讀和當(dāng)前讀結(jié)果一樣,都是讀取已提交的最新;RR隔離級(jí)別下,當(dāng)前讀結(jié)果是其他事務(wù)已經(jīng)提交的最新結(jié)果,快照讀是讀當(dāng)前事務(wù)之前讀到的結(jié)果。RR下創(chuàng)建快照讀的時(shí)機(jī)決定了讀到的版本。

          對(duì)于使用RC和RR隔離級(jí)別的事務(wù)來(lái)說(shuō),都必須保證讀到已經(jīng)提交了的事務(wù)修改過(guò)的記錄,也就是說(shuō)假如另一個(gè)事務(wù)已經(jīng)修改了記錄但是尚未提交,是不能直接讀取最新版本的記錄的。核心問(wèn)題就是:需要判斷一下版本鏈中的哪個(gè)版本是當(dāng)前事務(wù)可見(jiàn)的。為此,InnoDB提出了一個(gè)Read View的概念。

          Read View就是事務(wù)進(jìn)行快照讀(普通select查詢)操作的時(shí)候生產(chǎn)的一致性讀視圖,在該事務(wù)執(zhí)行的快照讀的那一刻,會(huì)生成數(shù)據(jù)庫(kù)系統(tǒng)當(dāng)前的一個(gè)快照,它由執(zhí)行查詢時(shí)所有未提交的事務(wù)id數(shù)組(數(shù)組里最小的id為min_id)和已經(jīng)創(chuàng)建的最大事務(wù)id(max_id)組成,查詢的數(shù)據(jù)結(jié)果需要跟read view做對(duì)比從而得到快照結(jié)果。

          快照規(guī)則

          版本鏈比對(duì)規(guī)則:

          1. 如果落在綠色部分(trx_id<min_id),表示這個(gè)版本是已經(jīng)提交的事務(wù)生成的,這個(gè)數(shù)據(jù)是可見(jiàn)的;
          2. 如果落在紅色部分(trx_id>max_id),表示這個(gè)版本是由將來(lái)啟動(dòng)的事務(wù)生成的,是肯定不可見(jiàn)的;
          3. 如果落在黃色部分(min_id<=trx_id<=max_id),那就包含兩種情況:a.若row的trx_id在數(shù)組中,表示這個(gè)版本是由還沒(méi)提交的事務(wù)生成的,不可見(jiàn);如果是自己的事務(wù),則是可見(jiàn)的;b.若row的trx_id不在數(shù)組中,表示這個(gè)版本是已經(jīng)提交了的事務(wù)生成的,可見(jiàn)。

          光說(shuō)不練假把式,接下來(lái)就讓我們用例子來(lái)演示一下:首先我們要準(zhǔn)備兩張表,一張test和一張account表,然后我們以accountundo log來(lái)畫(huà)版本鏈,準(zhǔn)備數(shù)據(jù)和原始記錄圖如下

          //test表中數(shù)據(jù)
          id=1,c1='11';
          id=5,c1='22';
          //account表數(shù)據(jù)
          id=1,name=‘lilei’;

          如下圖,我們將按照里面的順序執(zhí)行sql當(dāng)我們執(zhí)行到第7行的select的語(yǔ)句時(shí),會(huì)生成readview[100,200],300,版本鏈如圖所示:此時(shí)我們查詢到的數(shù)據(jù)為lilei300。我們首先要拿最新版本的數(shù)據(jù)trx_id=300來(lái)readview中匹配,落在黃色區(qū)間內(nèi),一看該數(shù)據(jù)已經(jīng)提交了,所以是可見(jiàn)的。繼續(xù)往下執(zhí)行,當(dāng)執(zhí)行到第10行的select語(yǔ)句時(shí),因?yàn)?code style="">trx_id=100并未提交,所以版本鏈依然為readview[100,200],300,版本鏈如圖所示:此時(shí)我們查詢到的數(shù)據(jù)為lilei300。我們按上邊操作,從最新版本依次往下匹配,我們首先要拿最新版本的數(shù)據(jù)trx_id=100來(lái)readview中匹配,落在黃色區(qū)間內(nèi),一看該數(shù)據(jù)在未提交的數(shù)組中,且不是自己的事務(wù),所以是不可見(jiàn)的;然后我們選擇前一個(gè)版本的數(shù)據(jù),結(jié)果同上;繼續(xù)向上找,當(dāng)找到trx_id=300的數(shù)據(jù)時(shí),會(huì)落在黃色區(qū)間,且是提交的,所以數(shù)據(jù)可見(jiàn)。繼續(xù)往下執(zhí)行,當(dāng)執(zhí)行到第13行的select語(yǔ)句時(shí),此時(shí)盡管trx_id=100已經(jīng)提交了,因?yàn)槭?code style="">InnoDB的RR模式,所以readview不會(huì)更改,仍為readview[100,200],300,版本鏈如圖所示:此時(shí)我們查詢到的數(shù)據(jù)為lilei300。原因同上邊的步驟,不再贅述。

          當(dāng)執(zhí)行update語(yǔ)句時(shí),都是先讀后寫(xiě)的,而這個(gè)讀,是當(dāng)前讀,只能讀當(dāng)前的值,跟readview查找時(shí)的快照讀區(qū)分開(kāi)。

          剛才演示的是InnoDB下的RR模式,接下來(lái)我們簡(jiǎn)單說(shuō)一下RC模式,上文中提到的RC模式的數(shù)據(jù)讀都是讀最新的即當(dāng)前讀,所以readview是實(shí)時(shí)生成的,執(zhí)行語(yǔ)句如圖所示:當(dāng)我們執(zhí)行到第13行的select的語(yǔ)句時(shí),會(huì)生成readview[200],300,版本鏈還和之前一樣,此時(shí)我們查詢到的數(shù)據(jù)為lilei2。原因和上邊講的RR模式下的比對(duì)規(guī)則相同。

          此處我們演示的是update的情況,對(duì)于刪除的情況可以認(rèn)為是update的特殊情況,會(huì)將版本鏈上最新的數(shù)據(jù)復(fù)制一份,然后將trx_id改成刪除操作的trx_id,同時(shí)在該條記錄的頭信息(record header)里的(deleted_flag)標(biāo)記位上寫(xiě)上true,來(lái)表示當(dāng)前記錄已經(jīng)被刪除,在查詢時(shí)按照上邊的規(guī)則查到對(duì)應(yīng)的記錄,如果delete_flag標(biāo)記位為true,意味著記錄已被刪除,則不返回?cái)?shù)據(jù)。

          大家應(yīng)該還關(guān)心一個(gè)問(wèn)題,即undo log什么時(shí)候刪除呢?系統(tǒng)會(huì)判斷,沒(méi)有比這個(gè)undo log更早的read view的時(shí)候,undo log會(huì)被刪除。所以這里也就是為什么我們建議你盡量不要使用長(zhǎng)事務(wù)的原因。長(zhǎng)事務(wù)意味著系統(tǒng)里面會(huì)存在很老的事務(wù)視圖。由于這些事務(wù)隨時(shí)可能訪問(wèn)數(shù)據(jù)庫(kù)里面的任何數(shù)據(jù),所以這個(gè)事務(wù)提交之前,數(shù)據(jù)庫(kù)里面它可能用到的回滾記錄都必須保留,這就會(huì)導(dǎo)致大量占用存儲(chǔ)空間。

          以上就是今天的全部?jī)?nèi)容了,感謝大家的閱讀!

          —————END—————

          推薦閱讀:


          最近面試BAT,整理一份面試資料Java面試BAT通關(guān)手冊(cè),覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫(kù)、數(shù)據(jù)結(jié)構(gòu)等等。
          獲取方式:關(guān)注公眾號(hào)并回復(fù) java 領(lǐng)取,更多內(nèi)容陸續(xù)奉上。
          明天見(jiàn)(??ω??)??
          瀏覽 71
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  就去操逼| 免费性交网站 | 操网站 | 日韩三级中文字幕电影在线 | 亚州的图五月丁香婷婷 |