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
1. Innodb的事務相關概念
- 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. 初始數據行
2.事務1更改該行的各字段的值
- 用排他鎖鎖定該行
- 記錄redo log
- 把該行修改前的值Copy到undo log,即上圖中下面的行
- 修改當前行的值,填寫事務編號,使回滾指針指向undo log中的修改前的行
3.事務2修改該行的值
4. 事務提交
5. Insert Undo log
3. 事務級別
- READ_UNCOMMITTED
讀未提交時,讀事務直接讀取主記錄,無論更新事務是否完成 - READ_COMMITTED
讀提交時,讀事務每次都讀取undo log中最近的版本,因此兩次對同一字段的讀可能讀到不同的數據(幻讀),但能保證每次都讀到最新的數據。 - REPEATABLE_READ
每次都讀取指定的版本,這樣保證不會產生幻讀,但可能讀不到最新的數據 - SERIALIZABLE
鎖表,讀寫相互阻塞,使用較少
4. MVCC
- 每行數據都存在一個版本,每次數據更新時都更新該版本
- 修改時Copy出當前版本隨意修改,個事務之間無干擾
- 保存時比較版本號,如果成功(commit),則覆蓋原記錄;失敗則放棄copy(rollback)
- 事務以排他鎖的形式修改原始數據
- 把修改前的數據存放於undo log,通過回滾指針與主數據關聯
- 修改成功(commit)啥都不做,失敗則恢復undo log中的數據(rollback)
5.總結
6. 參考資料
- Mysql官網
- http://blog.chinaunix.net/link.php?url=http://forge.mysql.com%2Fwiki%2FMySQL_Internals
- Understanding MySQL Internals
########################################
上面的博文結束了,這個引出了innodb的存儲格式,找了兩篇放到這兒,遺憾的是看到的多數文檔沒有引用
MyISAM和InnoDB的行格式
MyISAM有3種行存儲格式:fixed/dynamic/compressed;
其中fixed爲默認格式,只有當表不包含變長字段(varchar/varbinary/blob/text)時使用,該每行都是固定的,所以很容易獲取行在頁上的具體位置,存取效率比較高,但是佔用磁盤空間較多;
dynamic
每行都有一個行頭部,包含bitmap,用以記錄那些列爲空(NULL列不算爲空);
相比於fixed,其有如下特性:
所有字符串列都是動態存儲的,除非長度小於4;
字符類型若長度爲0/數字類型爲0都會不佔用存儲空間,由bitmap標註,NULL值不包含在內;
如果要update行,其擴展後很容易導致行鏈接既而產生碎片,一旦crash若link丟失則比較難恢復,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(動態)參數決定,目前可選值由Antelope和Barracuda,默認爲前者;要想要此參數生效,
因爲共享表空間默認爲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_ID:6字節,記錄最近的一個事務標示符
DB_ROLL_ID:7字節,指向回滾日誌記錄
--若沒有主鍵,則還會有DB_ROW_ID:6字節,包含在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行數據,如果行過大則會產生行溢出;
理論上mysql的varchar可存儲65525字節,強於oracle的4000,但對於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