<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>

          阿里二面:了解 MySQL 事務(wù)底層原理嗎

          共 7195字,需瀏覽 15分鐘

           ·

          2021-10-29 21:36

          MySQL 可以說是每個 Java 程序員必會的技能之一,作為 Java 的高級進階必備技能點,MySQL 的調(diào)優(yōu)和底層原理必然是需要知道的。

          但是大家似乎形成了一種思維定勢,那就是提到 MySQL 好像就一股腦的往 MySQL 的索引啊、優(yōu)化啊、之類的上面去鉆。本文我們拋開“熱門”的話題,來和大家一起來聊一聊比較冷門但比較重要的技術(shù)點:MySQL 事務(wù)的底層原理

          這事情還得從頭說起

          首先大家需要知道的是 MySQL 是支持事務(wù)并發(fā)執(zhí)行的,這又回到了最原始的問題了,「并發(fā)安全性問題」。在數(shù)據(jù)庫事務(wù)中并發(fā)問題是這樣子的:A 事務(wù)來寫某條記錄的數(shù)據(jù),B 事務(wù)也在寫該條記錄的數(shù)據(jù)。那如果啥也不做,勢必會造成數(shù)據(jù)的錯亂,MySQL 在設(shè)計之初就考慮到了這個問題。

          那么 MySQL 到底是如何解決這樣的問題的呢?其實是使用了 MVCC 多版本控制機制事務(wù)隔離機制鎖機制等辦法來解決事務(wù)并發(fā)問題。那說到這里不知道各位有沒有想過這樣一個問題:在數(shù)據(jù)庫中如果并發(fā)事務(wù)不做控制和處理,會有什么樣的危害呢

          帶著這樣的疑問,請繼續(xù)往下看。

          臟數(shù)據(jù)

          什么是臟數(shù)據(jù),它有哪些類型

          臟數(shù)據(jù)的具體概念有以下四種,分別是:臟寫、臟讀、不可重復(fù)讀、幻讀。我們來看看這幾個概念的意思

          1、臟寫

          臟寫是指一個事務(wù)修改且已經(jīng)提交的數(shù)據(jù)被另外一個事務(wù)給回滾了

          首先來分析一下概念:假設(shè)有兩個事務(wù) A、B。事務(wù) A 先開啟事務(wù),并且修改了一條 id 為 1 的記錄,將 name 改成 ?A(假設(shè)原來為 null),但是此時 事務(wù) A 還沒有提交。這個時候事務(wù) B 開啟了。事務(wù) B 將 id 為 1 的記錄中 name 改成了 B,并且將事務(wù)提交 了。但是這個時候事務(wù) A 不想修改了,就像之前自己修改的數(shù)據(jù)回滾了。也就是說此時導(dǎo)致的結(jié)果就是 id 為 1 的這條記錄的 name 還是為 null。

          然后事務(wù) B 去查詢這條記錄。結(jié)果蒙了。name 居然為 null。這就是臟寫。事務(wù) B 已經(jīng)寫入的記錄被事務(wù) A 給回滾了。

          看不懂沒有關(guān)系,我們先來看一張圖

          對著圖再來看一下上面的分析過程。

          那 MySQL 是如何來解決臟寫這種問題的?沒錯,就是。MySQL 在開啟一個事務(wù)的時候,他會將某條記錄和事務(wù)做一個綁定。這個其實和 JVM 鎖是類似的。因為此時事務(wù) A 先開啟了,并關(guān)聯(lián)綁定了這條記錄。所以事務(wù) B 此時如果想操作同樣的記錄,只能等待。當事務(wù) A 執(zhí)行完成了,就會通知正在等在事務(wù)。然后下一個事務(wù)繼續(xù)操作執(zhí)行。

          啥?說好的并發(fā),這說到底不還是串行嗎?這樣數(shù)據(jù)庫豈不是慢的要死。實際上這些操作都是在內(nèi)存中執(zhí)行的。具體一點是在 Buffer Pool 中執(zhí)行的。所以速度是非常快的。

          2、臟讀

          臟讀是指一個事務(wù)讀取到了另外一個事務(wù)沒有提交的記錄

          其實臟讀是最好理解的。我們還是假設(shè)有兩個事務(wù) A、B。事務(wù) A 先開啟了,將 id 為 1 的記錄中的 name 改成了 A,但是還沒有提交。此時事務(wù) B 開啟了。事務(wù) B 查詢到當前 name 的值為 A,然后就會按照 A 邏輯去執(zhí)行處理。結(jié)果事務(wù) A 回滾了事務(wù),事務(wù) B 再次查詢的時候發(fā)現(xiàn)記錄值不是 A。這就是臟讀。

          事務(wù) B 讀取到的 name 值是事務(wù) A 修改但是沒有提交的記錄。

          來張圖來直觀的理解下:

          3、不可重復(fù)讀

          不可重復(fù)讀是指前后讀取到的某條記錄的結(jié)果不一樣

          廢話少說,直接進分析:假設(shè)有三個事務(wù) A、B、C ,事務(wù) A 先開啟了,但是還沒有執(zhí)行任何的操作,事務(wù) B 開啟了,事務(wù) B 將 id 為 1 的記錄的 name 改為 B 并提交了事務(wù),此時事務(wù) A 開始活動了,查詢到的這條記錄的 name 值為 B,還是還未執(zhí)行任何操作。此時事務(wù) C 開啟了,事務(wù) C 將 id 為 1 的記錄的 name 改為 C 并提交了事務(wù)。此時事務(wù) A 又開始活動了,結(jié)果查詢到的 id 為 1 的 name 值又變成了 C。這就是不可重復(fù)讀

          其實理解起來還是很簡單的。看起來高大上名字,實際上就這么幾句話就能描述結(jié)束了。下面還是來一張圖來更直觀認識下:

          4、幻讀

          幻讀是指前后讀取到的記錄的數(shù)量不一樣

          幻讀和不可重復(fù)讀有點類似,不可重復(fù)讀強調(diào)的是數(shù)據(jù)的值不一樣,重點是修改,而幻讀強調(diào)的是記錄的數(shù)量不一樣,重點是新增或刪除。就好像是看花眼產(chǎn)生重影一樣。

          先來分析一下幻讀。還是假設(shè)有兩個事務(wù) A、B。事務(wù) A 先開啟了,并執(zhí)行了這樣的 SQL:select * from user,假設(shè)現(xiàn)在結(jié)果是 5 條,此時事務(wù) B 開啟了,并往 user 表中插入了一條記錄,并提交了事務(wù),此時事務(wù) A 又執(zhí)行了 ?select * from user結(jié)果發(fā)現(xiàn)是 6 條記錄。懵逼了。還以為自己餓昏了眼花了。這就是所謂的幻讀。

          以上的四個問題是現(xiàn)代數(shù)據(jù)庫典型的問題,這些問題會在不同的數(shù)據(jù)庫的事務(wù)隔離級別下產(chǎn)生。所以下面要分析的就是事務(wù)的隔離級別。

          事務(wù)的隔離級別

          事務(wù)的隔離級別有以下四種

          1. Read Uncommitted:讀取未提交【生產(chǎn)估計沒人這么設(shè)置的】
            意思就是一個事務(wù)能夠讀取到另一個事務(wù)未提交的修改

          2. Read Committed[簡稱 RC]:讀取已提交
            意思就是一個事務(wù)能讀取到另一個事務(wù)已經(jīng)提交了的修改

          3. Repeatable read[簡稱 RR]:可重復(fù)讀
            【MySQL 的默認隔離級別】,即事務(wù)之間只要是在進行中,彼此之間不會有任何的干擾

          4. serializable:串行化
            這個就有點狠了,就好比 Java 中的 synchroinzed 關(guān)鍵字,所有的請求只能一個一個來還行,很顯然效率最低,基本也不會使用這種隔離劑唄

          MVCC 機制

          MVCC(全稱 Multi-Version Concurrency Control),即多版本并發(fā)控制。MVCC 是一種并發(fā)控制的方法,一般在數(shù)據(jù)庫管理系統(tǒng)中,實現(xiàn)對數(shù)據(jù)庫的并發(fā)訪問;

          我們本文的重點是事務(wù)的隔離級別的底層原理,但是似乎說到現(xiàn)在也并沒有發(fā)現(xiàn)關(guān)于事務(wù)原理的影子(想發(fā)水文?)。

          實際上要了解事務(wù)的底層原理,根本沒法上來就開魯,我相信那樣的文章寫出來不僅沒人看,更是看不懂。所以為了讓大家由淺入深的慢慢掌握。我必須要做很多鋪墊,將相關(guān)的知識點進行拋磚引玉,然后一層一層剖析到原理。這里還請大家明白。

          說到這里,我們又要提到一個新的概念了。就是數(shù)據(jù)在磁盤存儲的時候,每一條存儲的記錄都會有事務(wù) ID 和回滾指針(其他的是什么本文不需要關(guān)注,學習抓住脈路即可,否則必定走火入魔)。

          這兩個到底是干嘛的?我們還是先從概念說起

          事務(wù) ID:就是每個事務(wù)的唯一標識
          回滾指針:該事務(wù)之前的記錄的引用(指針)。換句話說就是相對現(xiàn)在時間節(jié)點的老數(shù)據(jù)

          假設(shè)你需要操作某條記錄,首先該條記錄一定是先被加載到 Buffer Pool 中的,并且有這樣的一條 undo log 記錄。

          畫外音:undo log 就是修改前的記錄。用于回滾的。

          假設(shè)現(xiàn)在事務(wù) A 開啟了事務(wù),將值改為 A

          事務(wù) A 還在活躍中,這個時候事務(wù) B 開啟了,將值改為 B

          此時事務(wù) A 和事務(wù) B 都還在活躍中,這個時候事務(wù) C 開啟了,并將值改為 C

          看到這里是不是稍微有一點感覺了。這上面的圖有一個專有名詞:MVCC 版本控制鏈。同時這里又涉及到一個新的名詞:ReadView。就是每個事務(wù)在開啟的時候都會創(chuàng)建一個 ReadView 視圖。那具體什么叫 ReadView ,我們這里還不能一帶而過,相反需要我們來詳細的討論分析下。

          ReadView

          ReadView 可能是你理解事務(wù)底層原理的核心部分,那么什么是 ReadView 呢

          在每個事務(wù)開啟的時候都會創(chuàng)建一個 ReadView 視圖,作用就是用來記錄每個事務(wù)中的操作的一些 Undo Log 記錄。

          他里面涉及到幾個字段。分別是:m_ids、min_trx_id、max_trx_id、creator_trx_id。他們的具體含義如下:

          1. m_ids:用于記錄活躍中的事務(wù)的 ID;

          2. min_trx_id:當前活躍的事務(wù)中的最小的事務(wù) ID;

          3. max_trx_id:下一個即將要生成的事務(wù) ID。注意這里并不是指的最大的事務(wù) ID,這個事務(wù)一定是當前的 m_ids 中不存在的。(事務(wù) ID 的生成是遞增的);

          4. creator_trx_id:當前活躍事務(wù)的 ID;

          不要慌,光說概念一定是在耍流氓。下面我會通過圖文并茂的形式來一一說明解釋。假設(shè)有一個記錄現(xiàn)在是這樣存放的

          看到這里大家應(yīng)該知道的是,這條記錄一定是原來的某一個事務(wù)修改后的結(jié)果。也就是說這是一條原本的已經(jīng)存在的記錄。

          現(xiàn)在假設(shè)有 A、B、C 三個事務(wù),他們分別先后開啟,假設(shè)他們的事務(wù) ID 依次為:4、5、6。

          先來看事務(wù) A,此時的m_ids 為:[4、5、6],min_trx_id 為:4,max_trx_id 為 7(下圖第三行為max_trx_id),creator_trx_id 為 4。

          事務(wù) A 首先執(zhí)行了一次查詢操作,他此時是這么執(zhí)行的:

          首先他會順著 MVCC 的版本控制鏈往下找。找啥?找該條記錄的以前操作它的事務(wù) ID,他發(fā)現(xiàn)找到的這個 undolog 日志的對應(yīng)的事務(wù) ID 為 3,比自己的 4 要小,所以可以肯定這條記錄不是自己修改的,而又因為 m_ids 中的事務(wù) ID 為 4、5、6,3 是比他們都要小的,所以可推斷出查找到的這條記錄是在本次事務(wù)開啟之前就已經(jīng)存在的。所以事務(wù) A 查詢到的值為 C。

          此時事務(wù) B 同樣開始查詢這條記錄了。以此類推事務(wù) B 此時的執(zhí)行流程大概是這樣子的,首先事務(wù) B 會以同樣的方式查詢數(shù)據(jù)(PS:這些操作都是在內(nèi)存中的)同樣查詢到的結(jié)果是 C,經(jīng)過上面的的對于事務(wù) A 的分析,相信這里已經(jīng)不是問題了。但是假如現(xiàn)在事務(wù) B 將該值改成了 B,也就是下面的這張圖的樣子。

          此時事務(wù) A 又開始活躍了,還是執(zhí)行查詢操作,這個時候結(jié)果該是多少呢?

          首先事務(wù) A 發(fā)現(xiàn)同樣會順著該條記錄的 MVCC 版本控制鏈往下找,發(fā)現(xiàn)事務(wù) ID 為 5 ,比 m_ids 中的最小的事務(wù) ID 4 要大,那么可以且是存在于該集合中的,此時就可以斷定事務(wù) ID 為 5 的事務(wù)是正在進行中的事務(wù),所以事務(wù) A 是不會取該條 undo log 的值的。

          然后繼續(xù)往下找,找到了事務(wù) ID 為 3 的 undo log 記錄,對比后發(fā)現(xiàn) 3 不在 m_ids 中,且比 m_ids 中的最小的事務(wù) ID 都要小。下面的判斷就和剛開始的查詢判斷一樣了。

          假設(shè)此時事務(wù) A 將該條記錄的值改成 A ,然后事務(wù) A 再查詢這條記錄,那么請問這個時候事務(wù) A 查詢是怎么樣子的這一步非常重要)?現(xiàn)在這些事務(wù)以及數(shù)據(jù)在我們的腦袋中應(yīng)該是這樣子的:

          那么事務(wù) A 到底是怎么查詢的?查出來的結(jié)果到底是 A 還是 B?先來看下這張圖,然后根據(jù)圖一步一步來分析

          事務(wù) A 開始查詢,返現(xiàn)此時的 undo log 日志針對于該條記錄的 undo log 鏈(MVCC 版本鏈的另一種叫法)的第一條記錄的事務(wù) ID 為 4 ,一對比發(fā)現(xiàn)不就是自己修改的值嗎?那么查詢的結(jié)果就是 A。

          那么此時如果是事務(wù) B 來執(zhí)行查詢呢?結(jié)果你能否分析一下?那就是首先 B 發(fā)現(xiàn)最新的事務(wù) ID 為 4 ,且在 m_ids 中,可以斷定這是一條正在執(zhí)行中的事務(wù),且不和自己的一樣,所以是不會取該值的。

          然后繼續(xù)順著 undo log 日志鏈往下找,找到了事務(wù) ID 為 5 ?的記錄,發(fā)現(xiàn)和自己的一樣,那這個不就是需要查找的結(jié)果嗎?也就是說 事務(wù) B 查找到的結(jié)果是 B。

          以上是關(guān)于 ReadView 的相關(guān)的介紹,總體內(nèi)容不算難,但是是需要認真思考的,這里先來一個小總結(jié)

          1. ReadView 其實使用版本鏈機制

          2. 他里面的核心屬性為:

            • m_ids: 一個列表, 存儲當前系統(tǒng)活躍的事務(wù) id (重點)

            • min_trx_id: 當前 m_ids 活動事務(wù)中的最小的事務(wù) ID

            • max_trx_id: 下一個即將被分配出來的事務(wù) ID

            • creator_trx_id: 當前的事務(wù)的 ID

          3. ReadView 記錄的是:每個事務(wù)中的 Undo log 日志

          說到這里,下面繼續(xù)來分析本文的主題知識點:事務(wù)的底層原理(其實上面多多少少都說到了)。其實事務(wù)的底層就是基于 ReadView 來設(shè)計的。關(guān)于事務(wù)的底層原理,我們以 RC(Read Commit)和 RR(Repeatable read)來分析

          1. Read commit

          Read Commit 是事務(wù)隔離級別的其中一種,含義是:讀取已經(jīng)提交的記錄。舉個例子來說,假設(shè)有事務(wù) A 和事務(wù) B 都在活動中,事務(wù) B 提交的記錄是能夠被事務(wù) A 讀取到的。

          具體我們開始一步一步來分析。首先需要大家知道的是在 RC 隔離級別下,一個事務(wù)的每次查詢操作,數(shù)據(jù)庫都會為其創(chuàng)建一個新的 ReadView,這就是 RC 的核心思想。

          假設(shè)有事務(wù) A 和事務(wù) B ,事務(wù) ID 分別為 10 和 11,事務(wù) A 還沒開始活躍,事務(wù) B 就將某條記錄的值改為 B(假設(shè)原來的值為 X),但是還未提交,現(xiàn)在你可以想象一下下面這張圖:

          此時事務(wù) A 開始活躍了,他首先執(zhí)行了一次查詢操作。按照上面的核心思想,此時數(shù)據(jù)庫會重新創(chuàng)建一個 ReadView ?里面的幾個屬性的值分別為:

          • m_ids:[10,11]

          • min_trx_id:10

          • max_trx_id: 12

          • creator_trx_id:10

          接著就是就是和上面說過的一樣的查詢過程了,首先 A 查詢到的最近的一個事務(wù) ID 為 11,發(fā)現(xiàn)在 m_ids 中,但是又和自己的事務(wù) ID 不相等,所以就會順著 undo log 鏈繼續(xù)查找,然后找到了事務(wù) ID 為 3 的記錄,發(fā)現(xiàn)不在 m_ids ?中且,比最小的事務(wù) ID 10 還要小,所以可以斷定出事務(wù) ID 為 3 的這個記錄是原本就存在的記錄,所以查詢到的結(jié)果就是 X。

          接著事務(wù) B 又開始活躍了,事務(wù) B 直接提交了事務(wù),然后事務(wù) A 又發(fā)起了一起查詢操作。現(xiàn)在這個時候就是 RC 的核心了:這個時候數(shù)據(jù)庫會再次為事務(wù) A 創(chuàng)建一個新的 ReadView 里面的四個屬性分別為:

          • m_ids:[10]

          • min_trx_id:10

          • max_trx_id: 12

          • creator_trx_id:10

          然后 A 按照正常的流程去查詢,首先查詢到的是事務(wù) ID 為 11 的記錄,結(jié)果發(fā)現(xiàn)不在 m_ids 中,那這個時候就可以斷定的是:這個是最近已經(jīng)提交的記錄,所以是能夠查詢到 B 這個值的,也就是說這次查詢得到的結(jié)果就是 B 。

          這就是 RC,是不是如果看懂了 ReadView 原理,這些再看起來就非常簡單了?

          2. Repeatable read

          Repeatable read 是 MySQL 默認的隔離級別,既然是默認的,那一定是很厲害咯?其實你看完會發(fā)現(xiàn) just so so ?

          RR 的核心思想是:ReadView 創(chuàng)建以后直到事務(wù)提交,都不會再次重新生成

          首先還是有事務(wù) A 和事務(wù) B,事務(wù) ID 分別為 10 和 11 ,事務(wù) B 首先將值改為 B (假設(shè)原來值為 X),然后 事務(wù) A 發(fā)起了一次查詢的操作:

          查詢過程和前面的一模一樣。我就不再贅述了。

          接著事務(wù) B 又開始活躍了,直接提交了事務(wù),然后事務(wù) A 又發(fā)起了一次查詢。這個時候奇跡就出現(xiàn)了。因為我們剛剛說了:ReadView 創(chuàng)建以后直到事務(wù)提交,都不會再次重新生成。因為事務(wù) A 在創(chuàng)建 ReadView 的時候 m_ids 是 10 和 11,所以現(xiàn)在查詢的時候里面仍然是這個值,現(xiàn)在的查詢是這樣子的:事務(wù) A 首先查詢到的事務(wù) ID 為 11 ,結(jié)果發(fā)現(xiàn)在 m_ids 中,也就不會取該值,會繼續(xù)查找,當查找到事務(wù) ID 為 3 的時候,發(fā)現(xiàn)不在 m_ids 中,所以查詢到的就是 X。

          現(xiàn)在你知道為什么這個隔離級別下的事務(wù)不會互相干擾了吧?這就是原理

          本文小結(jié)

          本文為了說明事務(wù)的底層原理,做了大量的鋪墊,相信大家看完不光對不同隔離級別下事務(wù)的實現(xiàn)會有更深刻地理解,也同時明白了 undo log 記錄的作用,所以多探索一下底層你會發(fā)現(xiàn)各種知識點是如何串在一起工作的,這種通透的感覺確實很奇妙^_^

          ··············? END? ··············


          也許你還想看
          ? |?我在 B 站淘了 2 個 Java 實戰(zhàn)項目! 小破站,YYDS!
          ??|?我常用的20+個學習編程的網(wǎng)站!蕪湖起飛!
          ? |?1w+字的 Dubbo 面試題/知識點總結(jié)!(2021 最新版)
          ? |?7年前,24歲,出版了一本 Redis 神書
          ? |?京東二面:為什么需要分布式ID?你項目中是怎么做的?
          ? |?再見 Spring Task,這個定時任務(wù)框架真香!
          ? |?一鍵生成數(shù)據(jù)庫文檔,堪稱數(shù)據(jù)庫界的Swagger
          ? |?來看看這個超好用的項目腳手架吧!5分鐘搭建一個Spring Boot 前后端分離系統(tǒng)!
          ? |?看了這些 Java 八股文視頻,我直呼好家伙!!!

          我是 Guide哥,一個工作2年有余,接觸編程已經(jīng)6年有余的程序員。大三開源 JavaGuide,目前已經(jīng) 100k+ Star。未來幾年,希望持續(xù)完善 JavaGuide,爭取能夠幫助更多學習 Java 的小伙伴!共勉!凎!點擊即可了解我的個人經(jīng)歷


          瀏覽 47
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  欧美午夜精品久久久久免费视 | 日本精品在线视频 | 久久ww| 就爱操逼网 | 无码人妻一区二区 |