數據庫併發事務控制 三:mysql數據庫MVCC

mysql之innodb的mvcc實現,網上找一個拿來歸類,以後繼續


轉自:

blog.csdn.net/chen77716/article/details/6742128



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會極大地提高併發數,並消除線程鎖。

6. 參考資料




########################################

上面的博文結束了,這個引出了innodb的存儲格式,找了兩篇放到這兒,遺憾的是看到的多數文檔沒有引用


MyISAM和InnoDB的行格式



MyISAM3種行存儲格式:fixed/dynamic/compressed

其中fixed爲默認格式,只有當表不包含變長字段(varchar/varbinary/blob/text)時使用,該每行都是固定的,所以很容易獲取行在頁上的具體位置,存取效率比較高,但是佔用磁盤空間較多;

dynamic

每行都有一個行頭部,包含bitmap,用以記錄那些列爲空(NULL列不算爲空)

相比於fixed,其有如下特性:

所有字符串列都是動態存儲的,除非長度小於4

字符類型若長度爲0/數字類型爲0都會不佔用存儲空間,由bitmap標註,NULL值不包含在內;

如果要update行,其擴展後很容易導致行鏈接既而產生碎片,一旦crashlink丟失則比較難恢復,fixed模式update不會產生碎片;

 

compressed只能通過myisampack創建且爲只讀;

 

MyISAM的索引文件包含一個flag記錄基表是否正常關閉,如果mysqld啓動時指定了--myisam-recover-options,則在打開表時檢測並自動修復表

 

 

 

InnoDB行存儲

Innodb plugin新引入Barracuda梭子魚,其包含compressed/dynamic兩種行格式,而之前的compact/redundant統屬於antelope羚羊;

 

Barracuda VS antelope

innodb_file_format(動態)參數決定,目前可選值由AntelopeBarracuda,默認爲前者;要想要此參數生效,

因爲共享表空間默認爲Antelope,因此要想使用Barracuda爲默認值,還必須先聲明innodb_file_per_table

Innodb_file_format用於控制行格式,全局變量可動態調整,5.5默認依舊是Antelope

 

下面只看antelope格式

 

Redundant行結構

字段長度偏移列表

記錄頭信息

列1數據

列2數據

….

 

行頭部爲字段長度偏移信息,包括變長和非變長的, 還包含了3個隱藏列:RowID(沒有主鍵時使用)/Transaction ID/Roll Point; 而compact只包含變長的,節約了空間;

冗餘行格式沒有NULL標誌位;對於redundant格式,varchar爲Null時不佔用空間,但是char爲NULL需要佔用空間,因爲其沒有Null標誌位;

記錄頭信息佔用6個字節,比compact多1字節;

對於定長char,若爲NULL依舊填充整個字段,而varchar爲Null時不佔用空間;

記錄頭信息,與compact相比,多了黑體字部分,缺失record_type

名稱

長度bit

功能

Deleted_flag

1

是否被刪除

Min_rec_flag

1

1則表示該記錄爲預先被定義的最小記錄

N_owned

4

該記錄擁有的總記錄數

Heap_no

13

索引中該行的排序記錄

N_fields

10

記錄中列數量

1byte_offs_flag

1

偏移量列表是1字節還是2字節

Next_recorder

16

下一條記錄相對位置

()

1

未知

()

1

未知

 

Create table test(t1 varchar(10), t2 varchar(10), t3 char(10),t4 varchar(10)) charset=latin1 row_format=redundant;

--該表有3個變長列

Insert into test values(‘a’,’bb’,’bb’,’ccc’);

使用hexdump –C –v test.idb查看其二進制代碼

--長度偏移列表,

 

 

 

compact行格式

字段長度偏移列表

NULL標誌位

記錄頭信息

列1數據

列2數據

….

 

5.0引入

行頭存放該行內變長字段的length,當列小於255字節時佔用1個字節,大於255而小於65535時佔用2個字節;故varchar最大長度爲2的16次方-1;

第2個指示該行是否有NULL值,佔用1字節;NULL列不佔用數據存儲空間;

記錄頭信息:5個字節共計40bit,用於鏈接相鄰的記錄案的行級鎖

名稱

長度bit

功能

Deleted_flag

1

是否被刪除

Min_rec_flag

1

1則表示該記錄爲預先被定義的最小記錄

N_owned

4

該記錄擁有的總記錄數

Heap_no

13

索引中該行的排序記錄

Record_type

3

行類型 0=普通 1=B+節點指針

Next_recorder

16

下一條記錄相對位置

()

1

未知

()

1

未知

 

除此之外,每頁還有兩個隱含字段:

DB_TRX_ID6字節,記錄最近的一個事務標示符

DB_ROLL_ID7字節,指向回滾日誌記錄

--若沒有主鍵,則還會有DB_ROW_ID6字節,包含在clustered索引中

創建一個compact行格式的表

Create table test(t1 varchar(10), t2 varchar(10), t3 char(10),t4 varchar(10)) row_format=compact;

--該表有3個變長列

Insert into test values(‘a’,’bb’,’bb’,’ccc’);

使用hexdump –C –v test.idb查看其二進制代碼

第一行

03 02 01—變長字段長度列表(逆序),實際順序爲01 02 03,這也是t1,t2,t4的實際長度

00—Null標誌位,第一行沒有NULL

00 00 10 00 2c—記錄頭信息,5字節,後4個字節指向下一個記錄next_recorder

00 00 00 2b 68 00—6字節rowid,因爲沒有主鍵

00 00 00 00 06 05 –事務ID,6字節

80 00 00 00 32 01 10—回滾指針,7字節

61 –列1

62 62 –列2

62 62 20 20 20 20 20 20 20 20 –列3,char會填充餘下部分

63 63 63 –列4

 

餘下的爲列數據,其中t3由於採用固定長度,故會填充滿10個字節;

第二行

Insert into test values(‘d’,null,null,’fff’);

03 01--變長字段長度列表,逆序

06-- Null標誌位,有NULL值,轉換爲二進制00000110,表示第2/3列爲null

……

64—列1數據

66 66 66—列4數據,而第2/3列爲NULL不佔用存儲空間

注:對於redundant格式,varchar爲Null時同樣不佔用空間,但是char爲NULL需要佔用空間,因爲其沒有Null標誌位

 

 

行溢出

Innodb表爲IOT,採用了B+數類型,故每個頁面至少要存儲2行數據,如果行過大則會產生行溢出;

理論上mysqlvarchar可存儲65525字節,強於oracle4000,但對於InnoDB其實際上限爲65532,且該值爲表所有varchar列長度總和;對於utf8字符集,一個字符佔3個字節,則其上限又縮小爲1/3

如果強行創建varchar(65535)的字段,在sql_mode不爲restricted的情況下,其會被隱式轉換爲mediumtext

 

不論是varchar還是blob/text,只要保證一個16k的頁面能容下2行數據,應該不會行溢出;

而一旦行溢出,字段前768字節依舊存放於當前頁面,數據一般使用B-tree Node頁,而溢出的行存放於Uncompress Blob頁;

 

barracuda採用了完全行溢出,即只保留字段的前20字節;



###########################

MySQL Antelope和Barracuda的區別分析

http://www.jb51.net/article/52530.htm


這篇文章主要介紹了MySQL Antelope和Barracuda的區別分析,Antelope和Barracude都是一種文件格式,需要的朋友可以參考下


Antelope是innodb-base的文件格式,Barracude是innodb-plugin後引入的文件格式,同時Barracude也支持Antelope文件格式。兩者區別在於:

文件格式 支持行格式 特性
Antelope

(Innodb-base)

ROW_FORMAT=COMPACT

ROW_FORMAT=REDUNDANT

Compact和redumdant的區別在就是在於首部的存存內容區別。

compact的存儲格式爲首部爲一個非NULL的變長字段長度列表

redundant的存儲格式爲首部是一個字段長度偏移列表(每個字段佔用的字節長度及其相應的位移)。

在Antelope中對於變長字段,低於768字節的,不會進行overflow page存儲,某些情況下會減少結果集IO.

Barracuda

(innodb-plugin)

ROW_FORMAT=DYNAMIC

ROW_FORMAT=COMPRESSED

 

這兩者主要是功能上的區別功能上的。 另外在行裏的變長字段和Antelope的區別是隻存20個字節,其它的overflow page存儲。

另外這兩都需要開啓innodb_file_per_table=1

(這個特性對一些優化還是很有用的)

備註:

這裏有一點需要注意,如果要使用壓縮,一定需要先使用innodb_file_format =Barracuda格式,不然沒作用。

下面我們看一下區別:

複製代碼 代碼如下:

(testing)root@localhost [(none)]> use wubx;

Database changed

(testing)root@localhost [wubx]> CREATE TABLE t1

->  (c1 INT PRIMARY KEY)

->  ROW_FORMAT=COMPRESSED

->  KEY_BLOCK_SIZE=8;

Query OK, 0 rows affected, 4 warnings (0.01 sec)


報出來4個warnings查看一下報錯:
複製代碼 代碼如下:

(testing)root@localhost [wubx]> show warnings;

+———+——+———————————————————————–+

| Level   | Code | Message                                                               |

+———+——+———————————————————————–+

| Warning | 1478 | InnoDB: KEY_BLOCK_SIZE requires innodb_file_format > Antelope.        |

| Warning | 1478 | InnoDB: ignoring KEY_BLOCK_SIZE=8.                                    |

| Warning | 1478 | InnoDB: ROW_FORMAT=COMPRESSED requires innodb_file_format > Antelope. |

| Warning | 1478 | InnoDB: assuming ROW_FORMAT=COMPACT.                                  |

+———+——+———————————————————————–+

4 rows in set (0.00 sec)

從以上報錯可以看出來不支持壓縮。但看一下表結構如下:

複製代碼 代碼如下:

(testing)root@localhost [wubx]> show create table t1;

+——-+———————————————————————————————————————————————–+

| Table | Create Table                                                                                                                                  |

+——-+———————————————————————————————————————————————–+

| t1    | CREATE TABLE t1 (

c1 int(11) NOT NULL,

PRIMARY KEY (c1)

) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8 |

+——-+———————————————————————————————————————————————–+

1 row in set (0.00 sec)

這個是比較坑的地方,所以在使用壓縮需要注意。

複製代碼 代碼如下:

(testing)root@localhost [wubx]>create table t2 ( c1 int(11) NOT NULL, primary key(c1));

(testing)root@localhost [wubx]> insert into t2 select * from t1;

Query OK, 5417760 rows affected (37.12 sec)

Records: 5417760  Duplicates: 0  Warnings: 0

創建支持壓縮的表:

複製代碼 代碼如下:

(testing)root@localhost [wubx]>SET GLOBAL  innodb_file_per_table=1

(testing)root@localhost [wubx]>SET GLOBAL innodb_file_format=Barracuda;

(testing)root@localhost [wubx]>CREATE TABLE t3

(c1 INT PRIMARY KEY)

ROW_FORMAT=COMPRESSED

KEY_BLOCK_SIZE=8;

(testing)root@localhost [wubx]> insert into t3 select * from t1;

Query OK, 5417760 rows affected (1 min 10.98 sec)

Records: 5417760  Duplicates: 0  Warnings: 0

看一下表的物理大小如下:

複製代碼 代碼如下:

-rw-rw—- 1 mysql mysql 8.4K Jul  5 16:58 t1.frm

-rw-rw—- 1 mysql mysql 136M Jul  5 19:40 t1.ibd

-rw-rw—- 1 mysql mysql 8.4K Jul  5 19:43 t2.frm

-rw-rw—- 1 mysql mysql 136M Jul  5 19:44 t2.ibd

-rw-rw—- 1 mysql mysql 8.4K Jul  5 19:46 t3.frm

-rw-rw—- 1 mysql mysql  96M Jul  5 19:47 t3.ibd

可見t1, t2都沒進行壓縮, t3是支持壓縮的





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章