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

          不就隔個(gè)視圖嘛,你怎么看不到我啊?

          共 5660字,需瀏覽 12分鐘

           ·

          2021-09-01 08:06

          預(yù)熱

          比較喜歡的一段話:不經(jīng)一番寒徹骨,怎得梅花撲鼻香,學(xué)習(xí)是枯燥的請大家堅(jiān)持!這篇文章的是向丁奇老師學(xué)習(xí)的。不懂的自己搜一下哈! 閱讀這篇文章大概需要35分鐘!

          大家好前面我們大概了解了事務(wù)隔離級別,行鎖的兩階段鎖。今天我們結(jié)合兩篇文章解決一個(gè)隔離性中的數(shù)值問題!

          開始

          事務(wù)中我們提到的事務(wù)A開啟一個(gè)事務(wù)后,事務(wù)B也開始一個(gè)事務(wù)。那么事務(wù)A從頭到尾看到的數(shù)值都是一樣的。

          行鎖中又提到的是一個(gè)事務(wù)要更新一行會(huì)拿到這行的鎖,另一個(gè)事務(wù)也要操作這一行的話也會(huì)拿到行鎖。那在拿行鎖的時(shí)候就沒那么簡單了,會(huì)被鎖住,進(jìn)入等待狀態(tài)。等事務(wù)A執(zhí)行完之后,事務(wù)B成功拿到了鎖在更新數(shù)據(jù)的時(shí)候。事務(wù)B讀到的數(shù)值又是什么呢?

          開始之前,我們先復(fù)習(xí)一下 事務(wù)的啟動(dòng)機(jī)制begin/start transaction 命令并不是一個(gè)事務(wù)的起點(diǎn),在執(zhí)行到它們之后的第一個(gè)操作 InnoDB 表的語句,事務(wù)才真正啟動(dòng)。如果你想要馬上啟動(dòng)一個(gè)事務(wù),可以使用 start transaction with consistent snapshot 這個(gè)命令。

           第一種啟動(dòng)方式,一致性視圖是在執(zhí)行第一個(gè)快照讀語句時(shí)創(chuàng)建的;
          第二種啟動(dòng)方式,一致性視圖是在執(zhí)行 start transaction with consistent snapshot 時(shí)創(chuàng)建的。

          舉一個(gè)例子吧,以下是事務(wù)A,B,C的執(zhí)行流程。可以先猜一下k的值分別是多少。

          CREATE TABLE `t` (
          `id` INT ( 11 ) NOT NULL,
          `k` INT ( 11 ) DEFAULT NULL,
          PRIMARY KEY ( `id` )) ENGINE = INNODB;
          INSERT INTO t (id,k) VALUES (1,1),(2,2);

          圖 1 事務(wù) A、B、C 的執(zhí)行流程

          事務(wù)A的值是1,事務(wù)B的值是3 你是不是很驚訝。我當(dāng)初第一次學(xué)習(xí)的時(shí)候也是一樣的驚訝。甚至是懷疑我之前的知識(shí)點(diǎn)是否有問題。沒關(guān)系 我們一步一步解析。

          視圖概念

          第一種視圖create view 創(chuàng)建視圖也是我們常用的視圖。平時(shí)在做報(bào)表查詢復(fù)雜SQL的時(shí)候會(huì)做一些視圖的封裝。

          第二種視圖就屬于MySQL的實(shí)現(xiàn)視圖了。read view 視圖,我們都說是一致性視圖。用來實(shí)現(xiàn)mvcc里的讀提交和可重復(fù)讀隔離級別的。

          快照在MVCC中是如何工作的?

          在可重復(fù)讀隔離級別下,事務(wù)在啟動(dòng)的時(shí)候就“拍了個(gè)快照”。注意,這個(gè)快照是基于整庫的。

          這時(shí),你會(huì)說這看上去不太現(xiàn)實(shí)啊。如果一個(gè)庫有 100G,那么我啟動(dòng)一個(gè)事務(wù),MySQL 就要拷貝 100G 的數(shù)據(jù)出來,這個(gè)過程得多慢啊。可是,我平時(shí)的事務(wù)執(zhí)行起來很快啊。

          實(shí)際上,我們并不需要拷貝出這 100G 的數(shù)據(jù)。我們先來看看這個(gè)快照是怎么實(shí)現(xiàn)的。

          InnoDB 里面每個(gè)事務(wù)有一個(gè)唯一的事務(wù) ID,叫作 transaction id。它是在事務(wù)開始的時(shí)候向 InnoDB 的事務(wù)系統(tǒng)申請的,是按申請順序嚴(yán)格遞增的。

          而每行數(shù)據(jù)也都是有多個(gè)版本的。每次事務(wù)更新數(shù)據(jù)的時(shí)候,都會(huì)生成一個(gè)新的數(shù)據(jù)版本,并且把 transaction id 賦值給這個(gè)數(shù)據(jù)版本的事務(wù) ID,記為 row trx_id。同時(shí),舊的數(shù)據(jù)版本要保留,并且在新的數(shù)據(jù)版本中,能夠有信息可以直接拿到它。

          也就是說,數(shù)據(jù)表中的一行記錄,其實(shí)可能有多個(gè)版本 (row),每個(gè)版本有自己的 row trx_id。

          如圖 2 所示,就是一條記錄被多個(gè)事務(wù)連續(xù)更新后的狀態(tài)。

          圖中虛線框里是同一行數(shù)據(jù)的 4 個(gè)版本,當(dāng)前最新版本是 V4,k 的值是 22,它是被 transaction id 為 25 的事務(wù)更新的,因此它的 row trx_id 也是 25。

          你可能會(huì)問,前面的文章不是說,語句更新會(huì)生成 undo log(回滾日志)嗎?那么,undo log 在哪呢?

          實(shí)際上,圖 2 中的三個(gè)虛線箭頭,就是 undo log;而 V1、V2、V3 并不是物理上真實(shí)存在的,而是每次需要的時(shí)候根據(jù)當(dāng)前版本和 undo log 計(jì)算出來的。比如,需要 V2 的時(shí)候,就是通過 V4 依次執(zhí)行 U3、U2 算出來。

          明白了多版本和 row trx_id 的概念后,我們再來想一下,InnoDB 是怎么定義那個(gè)“100G”的快照的。

          按照可重復(fù)讀的定義,一個(gè)事務(wù)啟動(dòng)的時(shí)候,能夠看到所有已經(jīng)提交的事務(wù)結(jié)果。但是之后,這個(gè)事務(wù)執(zhí)行期間,其他事務(wù)的更新對它不可見。

          因此,一個(gè)事務(wù)只需要在啟動(dòng)的時(shí)候聲明說,“以我啟動(dòng)的時(shí)刻為準(zhǔn),如果一個(gè)數(shù)據(jù)版本是在我啟動(dòng)之前生成的,就認(rèn);如果是我啟動(dòng)以后才生成的,我就不認(rèn),我必須要找到它的上一個(gè)版本”。

          當(dāng)然,如果“上一個(gè)版本”也不可見,那就得繼續(xù)往前找。還有,如果是這個(gè)事務(wù)自己更新的數(shù)據(jù),它自己還是要認(rèn)的。

          在實(shí)現(xiàn)上, InnoDB 為每個(gè)事務(wù)構(gòu)造了一個(gè)數(shù)組,用來保存這個(gè)事務(wù)啟動(dòng)瞬間,當(dāng)前正在“活躍”的所有事務(wù) ID。“活躍”指的就是,啟動(dòng)了但還沒提交。

          數(shù)組里面事務(wù) ID 的最小值記為低水位,當(dāng)前系統(tǒng)里面已經(jīng)創(chuàng)建過的事務(wù) ID 的最大值加 1 記為高水位。

          這個(gè)視圖數(shù)組和高水位,就組成了當(dāng)前事務(wù)的一致性視圖(read-view)。

          而數(shù)據(jù)版本的可見性規(guī)則,就是基于數(shù)據(jù)的 row trx_id 和這個(gè)一致性視圖的對比結(jié)果得到的。

          這個(gè)視圖數(shù)組把所有的 row trx_id 分成了幾種不同的情況。

          圖 3 數(shù)據(jù)版本可見性規(guī)則

          這樣,對于當(dāng)前事務(wù)的啟動(dòng)瞬間來說,一個(gè)數(shù)據(jù)版本的 row trx_id,有以下幾種可能:

          如果落在綠色部分,表示這個(gè)版本是已提交的事務(wù)或者是當(dāng)前事務(wù)自己生成的,這個(gè)數(shù)據(jù)是可見的;
          如果落在紅色部分,表示這個(gè)版本是由將來啟動(dòng)的事務(wù)生成的,是肯定不可見的;
          如果落在黃色部分,那就包括兩種情況。若 row trx_id 在數(shù)組中,表示這個(gè)版本是由還沒提交的事務(wù)生成的,不可見;若 row trx_id 不在數(shù)組中,表示這個(gè)版本是已經(jīng)提交了的事務(wù)生成的,可見。

          比如,對于圖 2 中的數(shù)據(jù)來說,如果有一個(gè)事務(wù),它的低水位是 18,那么當(dāng)它訪問這一行數(shù)據(jù)時(shí),就會(huì)從 V4 通過 U3 計(jì)算出 V3,所以在它看來,這一行的值是 11。

          你看,有了這個(gè)聲明后,系統(tǒng)里面隨后發(fā)生的更新,是不是就跟這個(gè)事務(wù)看到的內(nèi)容無關(guān)了呢?因?yàn)橹蟮母拢傻陌姹疽欢▽儆谏厦娴?2 或者 3(a) 的情況,而對它來說,這些新的數(shù)據(jù)版本是不存在的,所以這個(gè)事務(wù)的快照,就是“靜態(tài)”的了。

          所以你現(xiàn)在知道了,InnoDB 利用了“所有數(shù)據(jù)都有多個(gè)版本”的這個(gè)特性,實(shí)現(xiàn)了“秒級創(chuàng)建快照”的能力。

          接下來,我們繼續(xù)看一下圖 1 中的三個(gè)事務(wù),分析下事務(wù) A 的語句返回的結(jié)果,為什么是 k=1。

          這里,我們不妨做如下假設(shè):

          事務(wù) A 開始前,系統(tǒng)里面只有一個(gè)活躍事務(wù) ID 是 99;
          事務(wù) A、B、C 的版本號(hào)分別是 100、101、102,且當(dāng)前系統(tǒng)里只有這四個(gè)事務(wù);
          三個(gè)事務(wù)開始前,(1,1)這一行數(shù)據(jù)的 row trx_id 是 90。

          這樣,事務(wù) A 的視圖數(shù)組就是[99,100], 事務(wù) B 的視圖數(shù)組是[99,100,101], 事務(wù) C 的視圖數(shù)組是[99,100,101,102]。

          為了簡化分析,我先把其他干擾語句去掉,只畫出跟事務(wù) A 查詢邏輯有關(guān)的操作:

          圖 4 事務(wù) A 查詢數(shù)據(jù)邏輯圖

          從圖中可以看到,第一個(gè)有效更新是事務(wù) C,把數(shù)據(jù)從 (1,1) 改成了 (1,2)。這時(shí)候,這個(gè)數(shù)據(jù)的最新版本的 row trx_id 是 102,而 90 這個(gè)版本已經(jīng)成為了歷史版本。

          第二個(gè)有效更新是事務(wù) B,把數(shù)據(jù)從 (1,2) 改成了 (1,3)。這時(shí)候,這個(gè)數(shù)據(jù)的最新版本(即 row trx_id)是 101,而 102 又成為了歷史版本。

          你可能注意到了,在事務(wù) A 查詢的時(shí)候,其實(shí)事務(wù) B 還沒有提交,但是它生成的 (1,3) 這個(gè)版本已經(jīng)變成當(dāng)前版本了。但這個(gè)版本對事務(wù) A 必須是不可見的,否則就變成臟讀了。

          好,現(xiàn)在事務(wù) A 要來讀數(shù)據(jù)了,它的視圖數(shù)組是[99,100]。當(dāng)然了,讀數(shù)據(jù)都是從當(dāng)前版本讀起的。所以,事務(wù) A 查詢語句的讀數(shù)據(jù)流程是這樣的:

          找到 (1,3) 的時(shí)候,判斷出 row trx_id=101,比高水位大,處于紅色區(qū)域,不可見;
          接著,找到上一個(gè)歷史版本,一看 row trx_id=102,比高水位大,處于紅色區(qū)域,不可見;
          再往前找,終于找到了(1,1),它的 row trx_id=90,比低水位小,處于綠色區(qū)域,可見。

          這樣執(zhí)行下來,雖然期間這一行數(shù)據(jù)被修改過,但是事務(wù) A 不論在什么時(shí)候查詢,看到這行數(shù)據(jù)的結(jié)果都是一致的,所以我們稱之為一致性讀。

          這樣執(zhí)行下來,雖然期間這一行數(shù)據(jù)被修改過,但是事務(wù) A 不論在什么時(shí)候查詢,看到這行數(shù)據(jù)的結(jié)果都是一致的,所以我們稱之為一致性讀。

          所以,我來給你翻譯一下。一個(gè)數(shù)據(jù)版本,對于一個(gè)事務(wù)視圖來說,除了自己的更新總是可見以外,有三種情況:

          1.(1,3) 還沒提交,屬于情況 1,不可見;
          2.(1,2) 雖然提交了,但是是在視圖數(shù)組創(chuàng)建之后提交的,屬于情況 2,不可見;
          3. (1,1) 是在視圖數(shù)組創(chuàng)建之前提交的,可見。

          你看,去掉數(shù)字對比后,只用時(shí)間先后順序來判斷,分析起來是不是輕松多了。所以,后面我們就都用這個(gè)規(guī)則來分析。

          更新邏輯

          看圖 5 中!

          事務(wù) B 的視圖數(shù)組是先生成的 (事務(wù)啟動(dòng)數(shù)組生成)

          之后事務(wù) C 才提交 ,不是應(yīng)該看不見 (1,2) 嗎,怎么能算出 (1,3) 來?(事務(wù)隔離級別,語句執(zhí)行完自動(dòng)提交)

          是的,如果事務(wù)B在更新之前查詢一遍數(shù)據(jù)k的值的確是1。(隔離級別不可見)

          但是當(dāng)他去更新數(shù)據(jù)的時(shí)候,已經(jīng)不能正常更新了。他會(huì)判斷當(dāng)前這條記錄的版本。他發(fā)現(xiàn)這條記錄已經(jīng)被其他事務(wù)執(zhí)行過了。如果無視的話事務(wù)C就被覆蓋了,就沒意義了。所以事務(wù)B必須在事務(wù)C的基礎(chǔ)上進(jìn)行操作。

          所以,這里就用到了這樣一條規(guī)則:更新數(shù)據(jù)都是先讀后寫的,而這個(gè)讀,只能讀當(dāng)前的值,稱為“當(dāng)前讀”(current read)

          現(xiàn)在事務(wù)B查詢了最新值之后,就會(huì)記錄當(dāng)前時(shí)刻的版本號(hào)。他發(fā)現(xiàn)當(dāng)前最新版本與自己的版本號(hào)一致。說明數(shù)據(jù)是正確的。就開始執(zhí)行更新操作。于是事務(wù)B就在事務(wù)C的基礎(chǔ)上繼續(xù)了update。

          我們解讀一下上面的當(dāng)前讀,除了 update 語句外,select 語句如果加鎖,也是當(dāng)前讀。

          所以,如果把事務(wù) A 的查詢語句 select * from t where id=1 修改一下,加上 lock in share mode 或 for update,也都可以讀到版本號(hào)是 101 的數(shù)據(jù),返回的 k 的值是 3。下面這兩個(gè) select 語句,就是分別加了讀鎖(S 鎖,共享鎖)和寫鎖(X 鎖,排他鎖)。

          select k from t where id=1 lock in share mode;
          select k from t where id=1 for update;

          圖 5 事務(wù) B 更新邏輯圖

          繼續(xù)舉個(gè)例子展開一些細(xì)節(jié)聊一下。如圖6,這個(gè)是上面的事務(wù)A,B,C的流程。只不過這里的C換成了C‘。C’的區(qū)別就是執(zhí)行完語句之后不立刻自動(dòng)提交,而是采用commit手動(dòng)提交的方式,而且這個(gè)手動(dòng)提交等事務(wù)B執(zhí)行完SQL語句之后再執(zhí)行。

          那么事務(wù)B會(huì)如何處理呢?是否跟上文的流程一樣呢?

          這時(shí)候,我們在上一篇文章中提到的“兩階段鎖協(xié)議”就要上場了。事務(wù) C’沒提交,也就是說 (1,2) 這個(gè)版本上的寫鎖還沒釋放。而事務(wù) B 是當(dāng)前讀,必須要讀最新版本,而且必須加鎖,因此就被鎖住了,必須等到事務(wù) C’釋放這個(gè)鎖,才能繼續(xù)它的當(dāng)前讀。

          圖 6 事務(wù) A、B、C’的執(zhí)行流程

          圖 7 配合圖6理解圖

          到這里,我們把一致性讀、當(dāng)前讀和行鎖就串起來了。

          可重復(fù)讀的能力是怎么實(shí)現(xiàn)的

          現(xiàn)在,我們再回到文章開頭的問題:事務(wù)的可重復(fù)讀的能力是怎么實(shí)現(xiàn)的?

          可重復(fù)讀的核心就是一致性讀(consistent read);而事務(wù)更新數(shù)據(jù)的時(shí)候,只能用當(dāng)前讀。如果當(dāng)前的記錄的行鎖被其他事務(wù)占用的話,就需要進(jìn)入鎖等待。

          而讀提交的邏輯和可重復(fù)讀的邏輯類似,它們最主要的區(qū)別是:

          1. 在可重復(fù)讀隔離級別下,只需要在事務(wù)開始的時(shí)候創(chuàng)建一致性視圖,之后事務(wù)里的其他查詢都共用這個(gè)一致性視圖;
          2. 在讀提交隔離級別下,每一個(gè)語句執(zhí)行前都會(huì)重新算出一個(gè)新的視圖。

          讀提交隔離級別下,數(shù)值流程?

          start transaction with consistent snapshot從這個(gè)語句開始,創(chuàng)建一個(gè)持續(xù)整個(gè)事務(wù)的一致性快照。所以,在讀提交隔離級別下,這個(gè)用法就沒意義了,等效于普通的 start transaction

          下面是讀提交時(shí)的狀態(tài)圖,可以看到這兩個(gè)查詢語句的創(chuàng)建視圖數(shù)組的時(shí)機(jī)發(fā)生了變化,就是圖中的 read view 框。(注意:這里,我們用的還是事務(wù) C 的邏輯直接提交,而不是事務(wù) C’)

          圖 8 讀提交隔離級別下的事務(wù)狀態(tài)圖

          這時(shí),事務(wù) A 的查詢語句的視圖數(shù)組是在執(zhí)行這個(gè)語句的時(shí)候創(chuàng)建的,時(shí)序上 (1,2)、(1,3) 的生成時(shí)間都在創(chuàng)建這個(gè)視圖數(shù)組的時(shí)刻之前。但是,在這個(gè)時(shí)刻:

          • (1,3) 還沒提交,屬于情況 1,不可見;

          • (1,2) 提交了,屬于情況 3,可見。

          所以,這時(shí)候事務(wù) A 查詢語句返回的是 k=2。顯然地,事務(wù) B 查詢結(jié)果 k=3。

          結(jié)尾

          下篇主要學(xué)習(xí)一下 普通索引和唯一索引,應(yīng)該怎么選擇?并且總結(jié)更新分享出來


          瀏覽 28
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(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>
                  国产精品无码中文在线 | 亚洲卡一卡二卡三在线观看 | www.操屄| 国产偷窥盗摄精品视频 | 天天色天天爱 |