Mysql加鎖過程詳解--05innodb 多版本併發控制原理詳解

轉載自:http://www.cnblogs.com/crazylqy/p/7610831.html

多版本併發控制技術已經被廣泛運用於各大數據庫系統中,如Oracle,MS SQL Server 2005+, Postgresql, Firebird, Maria等等,開源數據庫MYSQL中流行的INNODB引擎也採用了類似的併發控制技術.本文就將結合實例來解析不同事務隔離等級下INNODB的MVCC實現原理.

1 MVCC概述

1.1 MVCC簡介

MVCC (Multiversion Concurrency Control),即多版本併發控制技術,它使得大部分支持行鎖的事務引擎,不再單純的使用行鎖來進行數據庫的併發控制,取而代之的是,把數據庫的行鎖與行的多個版本結合起來,只需要很小的開銷,就可以實現非鎖定讀,從而大大提高數據庫系統的併發性能.

1.2 實現原理

MVCC可以提供基於某個時間點的快照,使得對於事務看來,總是可以提供與事務開始時刻相一致的數據,而不管這個事務執行的時間有多長.所以在不同的事務看來,同一時刻看到的相同行的數據可能是不一樣的,即一個行可能有多個版本.是否聽起來不可思議呢?

原來,爲了實現mvcc, innodb對每一行都加上了兩個隱含的列,其中一列存儲行被更新的”時間”,另外一列存儲行被刪除的”時間”. 但是innodb存儲的並不是絕對的時間,而是與時間對應的數據庫系統的版本號,每當一個事務開始的時候,innodb都會給這個事務分配一個遞增的版本號,所以版本號也可以被認爲是事務號.對於每一個”查詢”語句,innodb都會把這個查詢語句的版本號同這個查詢語句遇到的行的版本號進行對比,然後結合不同的事務隔離等級,來決定是否返回該行.

下面分別以select、delete、 insert、 update語句來說明:

1) SELECT

對於select語句,只有同時滿足了下面兩個條件的行,才能被返回:

•行的被修改版本號小於或者等於該事務號

•行的被刪除版本號要麼沒有被定義,要麼大於事務的版本號:行的刪除版本號如果沒有被定義,說明該行沒有被刪除過;如果刪除版本號大於當前事務的事務號,說明該行是被該事務後面啓動的事務刪除的,由於是repeatable read隔離等級,後開始的事務對數據的影響不應該被先開始的事務看見,所以該行應該被返回.

2) INSERT

對新插入的行,行的更新版本被修改爲該事務的事務號

3) DELETE

對於刪除,innodb直接把該行的被刪除版本號設置爲當前的事務號,相當於標記爲刪除,而不是實際刪除

4) UPDATE

在更新行的時候,innodb會把原來的行復制一份到回滾段中,並把當前的事務號作爲該行的更新版本

1.3 MVCC的優缺點

上述策略的結果就是,在讀取數據的時候,innodb幾乎不用獲得任何鎖, 每個查詢都通過版本檢查,只獲得自己需要的數據版本,從而大大提高了系統的併發度.

這種策略的缺點是,爲了實現多版本,innodb必須對每行增加相應的字段來存儲版本信息,同時需要維護每一行的版本信息,而且在檢索行的時候,需要進行版本的比較,因而降低了查詢的效率;innodb還必須定期清理不再需要的行版本,及時回收空間,這也增加了一些開銷

2 INNODB支持的事務隔離等級

INNODB支持並實現了ISO標準的4個事務隔離等級,即 READ-UNCOMMITTED, READ-COMMITTED, REPEATABLE-READ, SERIALIZABLE.

1) READ UNCOMMITTED (可以讀未提交的): 查詢可以讀取到其他事務正在修改的數據,即使其他事務的修改還沒有提交.這種隔離等級無法避免髒讀.

2) READ COMMITTED(只可以讀已經提交的):其他事務對數據庫的修改,只要已經提交,其修改的結果就是可見的,與這兩個事務開始的先後順序無關.這種隔離等級避免了髒讀,但是無法實現可重複讀,甚至有可能產生幻讀.

3) REPEATABLE READ(可重複讀):比read committed更進了一步,它只能讀取在它開始之前已經提交的事務對數據庫的修改,在它開始以後,所有其他事務對數據庫的修改對它來說均不可見.從而實現了可重複讀,但是仍有可能幻讀

4) SERIALIZABLE(可串行化):這是事務隔離等級的最高級別.其實現原理就是對於所有的query,即使是查詢,也會加上讀鎖,避免其他事務對數據的修改.所以它成功的避免了幻讀.但是代價是,數據庫系統的併發處理能力大大降低,所以它不會被用到生產系統中.

我們對MVCC和標準事務隔離等級有所瞭解以後,再結合實例來看看其具體表現吧.

 

3 不同事務隔離等級下的MVCC實現

MVCC由於其實現原理,只支持read committed和repeatable read隔離等級,下面分別舉例詳細說明:

每次開始之前,都先執行如下的語句:

create database if not exists mydb;

use mydb;

drop table if exists emp;

create table `emp` ( `empno` int(11) not null auto_increment, `ename` varchar(20) default null, Primary key (empno)) engine=innodb default charset=gbk;

insert into emp values(100, "yuxiangang") ;

insert into emp values(200,"2zhaoyinggang");

insert into emp values(300,"3yihongbin");

3.1 read committed隔離等級

說明:session 1和session 2表示訪問同一個數據庫的兩個不同的會話.行號用來代表不同的語句執行的時間點.

行號

session 1

session 2

1

set transaction isolation level read committed;

 

2

start transaction;

 

3

select * from emp where empno>=100; 

查詢的結果爲:

+-------+-------+

| empno | ename |

+-------+-------+

|   100 | 1yuxiangang  |

|   200 | 2zhaoyinggang  |

|   300 | 3yihongbin  |

+-------+-------+

 

4

 

set transaction isolation level read committed;

5

 

start transaction;

6

 

update emp set ename=1 where empno=100; 

delete from emp where empno=200;

說明: 修改一行,然後刪除一行,但是事務不提交.

7

select * from emp where empno>=100; 

查詢的結果爲:

+-------+-------+

| empno | ename |

+-------+-------+

|   100 | 1yuxiangang  |

|   200 | 2zhaoyinggang  |

|   300 | 3yihongbin  |

+-------+-------+

 

說明:會話2的事務沒有提交,所以會話1看不到會話2的事務對數據庫數據的修改.但是實際上修改已經發生,會話1獲取的被修改或者刪除的數據,都來自於回滾段.這是通過MVCC來實現的.

8

 

commit;

說明: 會話2提交

9

select * from emp where empno>=100; 

查詢的結果爲:

+-------+-------+

| empno | ename |

+-------+-------+

|   100 | 1     |

|   300 | 3yihongbin  |

+-------+-------+

 

說明:當事務2提交以後,由於會話1採用的是read committed隔離等級,所以會話2的提交馬上會被會話1的事務看見.對於會話1來說,第一次執行select * from emp where empno>=100;與第二次執行該語句,兩次看到的結果不一樣,第一次讀看到了3行,第二次只看到了2行,就像發生了幻覺,稱之爲幻讀;第一次看到100對應的ename爲1yuxiangang,第二次看到的100對應的是1,兩次獲取的數據內容不一樣,稱之爲不可重複讀.

3.2 repeatable read隔離等級

注意:先執行開頭的所有sql語句.

行號

session 1

session 2

session 3

session 4

1

set transaction isolation level repeatable read;

 

 

 

2

start transaction;

 

 

 

3

select * from emp where empno>=100; 

查詢的結果爲:

+-------+-------+

| empno | ename |

+-------+-------+

|   100 | 1yuxiangang |

|   200 | 2zhaoyinggang  |

|   300 | 3yihongbin |

+-------+-------+

 

 

 

4

 

 

 

set @@session.autocommit=1;

說明: 這裏讓會話4可以自動提交,便於觀察它對前面3個會話的影響

 

 

 

 

update emp set ename=1 where empno=100; 

insert into emp values(400,"4chj");

說明: 會話4先更新一行數據,然後插入一行數據,並自動提交

5

select * from emp where empno>=100; 

查詢的結果爲: +-------+-------+

| empno | ename |

+-------+-------+

|   100 | 1yuxiangang |

|   200 | 2zhaoyinggang  |

|   300 | 3yihongbin |

+-------+-------+

 

 

 

說明: 會話1執行查詢,兩次查詢得到的結果一樣.它看不到會話4對數據庫的修改,雖然會話4的事務已經提交.這是因爲會話4的事務是在會話1的事務之後纔開始.從這裏也可以看出,repeatable read實現了可重複讀

6

 

set transaction isolation level repeatable read;start transaction;

 

 

7

 

select * from emp where empno>=100; 

查詢的結果爲:

+-------+-------+

| empno | ename |

+-------+-------+

|   100 | 1     |

|   200 | 2zhaoyinggang  |

|   300 | 3yihongbin  |

|   400 | 4chj  |

+-------+-------+

 

 

說明: 會話2是在會話4之後開始的,所以它看到了會話4對數據庫的修改.同時可以看到,相同的查詢語句,不同的事務來執行的時候,得到的結果不一樣.會話2與會話3執行相同的查詢就得到不一樣的結果.

8

 

 

 

update emp set ename=2 where empno=200;

9

 

 

set transaction isolation level repeatable read;start transaction;

 

10

 

 

select * from emp where empno>=100;查詢的結果爲: 

+-------+-------+

| empno | ename |

+-------+-------+

|   100 | 1     |

|   200 | 2     |

|   300 | 3yihongbin  |

|   400 | 4chj  |

+-------+-------+

 

說明: 同樣,這個會話查詢到的結果與會話1和會話2的結果也不一樣.而且會話3看到了會話4對數據庫的修改.

11

 

 

 

update emp set ename=4 where empno=400;

12

 

 

 

select * from emp where empno>=100; 

查詢的結果爲:

+-------+-------+

| empno | ename |

+-------+-------+

|   100 | 1     |

|   200 | 2     |

|   300 | 3yihongbin  |

|   400 | 4     |

+-------+-------+

說明: 事務總是可以看到自身對數據庫數據的修改,儘管別的事務可能看不到這種修改

13

select * from emp where empno>=100; 

查詢的結果爲:

+-------+-------+

| empno | ename |

+-------+-------+

|   100 | 1yuxiangang |

|   200 | 2zhaoyinggang  |

|   300 | 3yihongbin |

+-------+-------+

select * from emp where empno>=100; 

查詢的結果爲:

+-------+-------+

| empno | ename |

+-------+-------+

|   100 | 1     |

|   200 | 2zhaoyinggang  |

|   300 | 3yihongbin  |

|   400 | 4chj  |

+-------+-------+

select * from emp where empno>=100; 

查詢的結果爲:

+-------+-------+

| empno | ename |

+-------+-------+

|   100 | 1     |

|   200 | 2     |

|   300 | 3yihongbin  |

|   400 | 4chj  |

+-------+-------+

select * from emp where empno>=100; 

查詢的結果爲:

+-------+-------+

| empno | ename |

+-------+-------+

|   100 | 1     |

|   200 | 2     |

|   300 | 3yihongbin  |

|   400 | 4     |

+-------+-------+

說明: 從上面的結果可以很清晰的看到:會話1,2,3,4執行相同的語句,即使是在同一時刻,他們看到的數據都可能不一樣:對於empno爲100的行,有 100  1yuxiangang 和 100  1兩個版本;對於empno爲200的行,有 200  2zhaoyinggang 和200  2兩個版本…,而每一行數據都可能存在多個版本,那麼這些行組合起來得到的結果集的版本就更是不計其數,這就是數據庫多版本的由來.MVCC就是通過事務發生的不同的時間點,與數據行的版本來進行對比,從而取回與事務開始的時間點相一致的數據,來實現非阻塞的一致讀.

14

commit;

commit;

commit;

commit;

15

select * from emp where empno>=100; 

查詢的結果爲:

+-------+-------+

| empno | ename |

+-------+-------+

|   100 | 1     |

|   200 | 2     |

|   300 | 3yihongbin |

|   400 | 4     |

+-------+-------+

select * from emp where empno>=100; 

查詢的結果爲:

+-------+-------+

| empno | ename |

+-------+-------+

|   100 | 1     |

|   200 | 2     |

|   300 | 3yihongbin  |

|   400 | 4     |

+-------+-------+

select * from emp where empno>=100; 

查詢的結果爲:

+-------+-------+

| empno | ename |

+-------+-------+

|   100 | 1     |

|   200 | 2     |

|   300 | 3yihongbin  |

|   400 | 4     |

+-------+-------+

select * from emp where empno>=100; 

查詢的結果爲:

+-------+-------+

| empno | ename |

+-------+-------+

|   100 | 1     |

|   200 | 2     |

|   300 | 3yihongbin  |

|   400 | 4     |

+-------+-------+

說明: 當所有事務都提交後,他們看到的結果都是一樣的


 

4 相關的Q&A

4.1 爲什麼select count(*)在myisam表上很快,而在Innodb的表上很慢?

因爲innodb採用了MVCC技術,對於相同的行,可能同時存在多個版本,innodb必須根據查詢的時間來過濾掉一些行,才能得出結果,必然要執行全表掃描,而全表掃描是非常耗時的.對於myisam的表,任何行都只有一個版本,mysql甚至不需要掃描就可以直接返回精確的統計結果,我們用explain也可以看到,對於myisam的表,執行select count(*)的時候,mysql顯示” Select tables optimized away”,查詢直接被優化了;而對於innodb的表,可能是全表掃描,也可能是”using index”,總之,速度肯定會比myisam的錶慢很多.

4.2 我的數據庫只是頻繁更新,沒有插入新數據,但是爲什麼表空間佔用會越來越大?

如果你在數據庫中執行了大事務, innodb就會把被修改數據的前映像存放到稱爲回滾段的公共表空間中,而且對於索引和表中的行的多個版本,如果innodb來不及purge,或者這些行因爲要提供一致讀而不能被purge,就會佔用越來越多的空間,甚至有可能短時間撐爆你的硬盤.所以應用程序中需要合理控制事務的大小.

4.3 能禁用MVCC嗎?

禁用MVCC可以降低innodb引擎的開銷,而同時innodb又可以支持外鍵約束,可以實現自動恢復.MVCC本身不支持read uncommitted等級,所以可以通過設置transaction_isolation = read uncommitted 來禁用MVCC.但是任何改變innodb默認隔離等級的操作,都會起到innodb_locks_unsafe_for_binlog=off類似的效果,這會導致諸如insert into t select * from t_src 之類的語句不再給源表t_src加鎖,也不再使用innodb的間隙鎖,從而產生幻讀,直接導致binlog中記錄的sql語句不能正確的串行化,從而主從數據庫的數據不再一致,而且基於binlog的增量備份也不再有效.所以除非不需要記錄binlog,否則別這麼做.當然我們可以這樣做來優化從庫的性能,因爲從庫不需要記錄binlog.

4.4 何時使用char類型,何時使用varchar類型的列?

在使用myisam引擎的情況下,定長表雖然可能佔用較多的存儲空間,但是它會加快檢索和全表掃描的速度,此時適合選用char的列,而對於表中的變長的列,可以採用分表的方法把變長的列拆分出去,提高定長表的檢索性能.而如果使用的是innodb的引擎,由於innodb的mvcc策略的實施,char數據類型相對於varchar類型幾乎沒有任何優勢,反而varchar列可能節省更多的存儲空間,建議使用varchar數據類型.

 

轉:http://blog.csdn.net/aoxida/article/details/50689619

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