程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 數據庫知識 >> MYSQL數據庫 >> MySQL綜合教程 >> MySQL多版本並發控制分析

MySQL多版本並發控制分析

編輯:MySQL綜合教程

MySQL多版本並發控制分析


讀未提交時,讀事務直接讀取主記錄,無論更新事務是否完成READ_COMMITTED
讀提交時,讀事務每次檢查主記錄上有沒有鎖,如果沒有鎖就讀取主記錄;如果有鎖,就讀取undo log中最近的版本。這樣每次讀到的都是最新COMMITTED的數據。因此兩次對同一字段的讀可能讀到不同的數據(幻讀),但能保證每次都讀到最新的數據。REPEATABLE_READ
第一次讀的時候檢查主記錄上有沒有鎖,如果沒有鎖就讀取主記錄;如果有鎖,就讀取undo log中最近的版本。我猜測update的時候創建新的記錄,然後將原先主記錄內容拷貝到當前主記錄中,原先的主記錄就變為最新的undo log。以後每次都讀取第一次讀的版本,這樣保證不會產生幻讀,但可能讀不到最新的數據SERIALIZABLE
鎖表,讀寫相互阻塞,使用較少


Mysql到底是怎麼實現MVCC的?這個問題無數人都在問,但google中並無答案,本文嘗試從Mysql源碼中尋找答案。
在Mysql中MVCC是在Innodb存儲引擎中得到支持的,Innodb為每行記錄都實現了三個隱藏字段:
6字節的事務ID(DB_TRX_ID )7字節的回滾指針(DB_ROLL_PTR)隱藏的ID 6字節的事物ID用來標識該行所述的事務,7字節的回滾指針需要了解下Innodb的事務模型。

1. Innodb的事務相關概念

為了支持事務,Innbodb引入了下面幾個概念: redo log

redo log就是保存執行的SQL語句到一個指定的Log文件,當Mysql執行recovery時重新執行redo log記錄的SQL操作即可。當客戶端執行每條SQL(更新語句)時,redo log會被首先寫入log buffer;當客戶端執行COMMIT命令時,log buffer中的內容會被視情況刷新到磁盤。redo log在磁盤上作為一個獨立的文件存在,即Innodb的log文件。undo log

與redo log相反,undo log是為回滾而用,具體內容就是copy事務前的數據庫內容(行)到undo buffer,在適合的時間把undo buffer中的內容刷新到磁盤。undo buffer與redo buffer一樣,也是環形緩沖,但當緩沖滿的時候,undo buffer中的內容會也會被刷新到磁盤;與redo log不同的是,磁盤上不存在單獨的undo log文件,所有的undo log均存放在主ibd數據文件中(表空間),即使客戶端設置了每表一個數據文件也是如此。rollback segment

回滾段這個概念來自Oracle的事物模型,在Innodb中,undo log被劃分為多個段,具體某行的undo log就保存在某個段中,稱為回滾段。可以認為undo log和回滾段是同一意思。鎖

Innodb提供了基於行的鎖,如果行的數量非常大,則在高並發下鎖的數量也可能會比較大,據Innodb文檔說,Innodb對鎖進行了空間有效優化,即使並發量高也不會導致內存耗盡。

對行的鎖有分兩種:排他鎖、共享鎖。共享鎖針對對,排他鎖針對寫,完全等同讀寫鎖的概念。如果某個事務在更新某行(排他鎖),則其他事物無論是讀還是寫本行都必須等待;如果某個事物讀某行(共享鎖),則其他讀的事物無需等待,而寫事物則需等待。通過共享鎖,保證了多讀之間的無等待性,但是鎖的應用又依賴Mysql的事務隔離級別。隔離級別

隔離級別用來限制事務直接的交互程度,目前有幾個工業標准:

- READ_UNCOMMITTED:髒讀

- READ_COMMITTED:讀提交

- REPEATABLE_READ:重復讀

- SERIALIZABLE:串行化

Innodb對四種類型都支持,髒讀和串行化應用場景不多,讀提交、重復讀用的比較廣泛,後面會介紹其實現方式。

2. 行的更新過程

下面演示下事務對某行記錄的更新過程:

1. 初始數據行

\

F1~F6是某行列的名字,1~6是其對應的數據。後面三個隱含字段分別對應該行的事務號和回滾指針,假如這條數據是剛INSERT的,可以認為ID為1,其他兩個字段為空。

2.事務1更改該行的各字段的值

\

當事務1更改該行的值時,會進行如下操作: 用排他鎖鎖定該行記錄redo log把該行修改前的值Copy到undo log,即上圖中下面的行修改當前行的值,填寫事務編號,使回滾指針指向undo log中的修改前的行

3.事務2修改該行的值

\

與事務1相同,此時undo log,中有有兩行記錄,並且通過回滾指針連在一起。 因此,如果undo log一直不刪除,則會通過當前記錄的回滾指針回溯到該行創建時的初始內容,所幸的時在Innodb中存在purge線程,它會查詢那些比現在最老的活動事務還早的undo log,並刪除它們,從而保證undo log文件不至於無限增長。

4. 事務提交

當事務正常提交時Innbod只需要更改事務狀態為COMMIT即可,不需做其他額外的工作,而Rollback則稍微復雜點,需要根據當前回滾指針從undo log中找出事務修改前的版本,並恢復。如果事務影響的行非常多,回滾則可能會變的效率不高,根據經驗值沒事務行數在1000~10000之間,Innodb效率還是非常高的。很顯然,Innodb是一個COMMIT效率比Rollback高的存儲引擎。據說,Postgress的實現恰好與此相反。

5. Insert Undo log

上述過程確切地說是描述了UPDATE的事務過程,其實undo log分insert和update undo log,因為insert時,原始的數據並不存在,所以回滾時把insert undo log丟棄即可,而update undo log則必須遵守上述過程。

3. 事務級別

眾所周知地是更新(update、insert、delete)是一個事務過程,在Innodb中,查詢也是一個事務,只讀事務。當讀寫事務並發訪問同一行數據時,能讀到什麼樣的內容則依賴事務級別: READ_UNCOMMITTED

讀未提交時,讀事務直接讀取主記錄,無論更新事務是否完成READ_COMMITTED

讀提交時,讀事務每次都讀取undo log中最近的版本,因此兩次對同一字段的讀可能讀到不同的數據(幻讀),但能保證每次都讀到最新的數據。REPEATABLE_READ

每次都讀取指定的版本,這樣保證不會產生幻讀,但可能讀不到最新的數據SERIALIZABLE

鎖表,讀寫相互阻塞,使用較少 讀事務一般有SELECT語句觸發,在Innodb中保證其非阻塞,但帶FOR UPDATE的SELECT除外,帶FOR UPDATE的SELECT會對行加排他鎖,等待更新事務完成後讀取其最新內容。就整個Innodb的設計目標來說,就是提供高效的、非阻塞的查詢操作。

4. MVCC

上述更新前建立undo log,根據各種策略讀取時非阻塞就是MVCC,undo log中的行就是MVCC中的多版本,這個可能與我們所理解的MVCC有較大的出入,一般我們認為MVCC有下面幾個特點: 每行數據都存在一個版本,每次數據更新時都更新該版本修改時Copy出當前版本隨意修改,個事務之間無干擾保存時比較版本號,如果成功(commit),則覆蓋原記錄;失敗則放棄copy(rollback) 就是每行都有版本號,保存時根據版本號決定是否成功,聽起來含有樂觀鎖的味道。。。,而Innodb的實現方式是: 事務以排他鎖的形式修改原始數據把修改前的數據存放於undo log,通過回滾指針與主數據關聯修改成功(commit)啥都不做,失敗則恢復undo log中的數據(rollback) 二者最本質的區別是,當修改數據時是否要排他鎖定,如果鎖定了還算不算是MVCC?

Innodb的實現真算不上MVCC,因為並沒有實現核心的多版本共存,undo log中的內容只是串行化的結果,記錄了多個事務的過程,不屬於多版本共存。但理想的MVCC是難以實現的,當事務僅修改一行記錄使用理想的MVCC模式是沒有問題的,可以通過比較版本號進行回滾;但當事務影響到多行數據時,理想的MVCC據無能為力了。

比如,如果Transaciton1執行理想的MVCC,修改Row1成功,而修改Row2失敗,此時需要回滾Row1,但因為Row1沒有被鎖定,其數據可能又被Transaction2所修改,如果此時回滾Row1的內容,則會破壞Transaction2的修改結果,導致Transaction2違反ACID。

理想MVCC難以實現的根本原因在於企圖通過樂觀鎖代替二段提交。修改兩行數據,但為了保證其一致性,與修改兩個分布式系統中的數據並無區別,而二提交是目前這種場景保證一致性的唯一手段。二段提交的本質是鎖定,樂觀鎖的本質是消除鎖定,二者矛盾,故理想的MVCC難以真正在實際中被應用,Innodb只是借了MVCC這個名字,提供了讀的非阻塞而已。

5.總結

也不是說MVCC就無處可用,對一些一致性要求不高的場景和對單一數據的操作的場景還是可以發揮作用的,比如多個事務同時更改用戶在線數,如果某個事務更新失敗則重新計算後重試,直至成功。這樣使用MVCC會極大地提高並發數,並消除線程鎖。 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved