轉自:https://blog.csdn.net/aoxida/article/details/50689619mysql
多版本並發控制技術已經被普遍運用於各大數據庫系統中,如Oracle,MS SQL Server 2005+, Postgresql, Firebird, Maria等等,開源數據庫MYSQL中流行的INNODB引擎也採用了相似的並發控制技術.本文就將結合實例來解析不一樣事務隔離等級下INNODB的MVCC實現原理.sql
1 MVCC概述數據庫
1.1 MVCC簡介session
MVCC (Multiversion Concurrency Control),即多版本併發控制技術,它使得大部分支持行鎖的事務引擎,再也不單純的使用行鎖來進行數據庫的併發控制,取而代之的是,把數據庫的行鎖與行的多個版本結合起來,只須要很小的開銷,就能夠實現非鎖定讀,從而大大提升數據庫系統的併發性能.併發
1.2 實現原理mvc
MVCC能夠提供基於某個時間點的快照,使得對於事務看來,老是能夠提供與事務開始時刻相一致的數據,而無論這個事務執行的時間有多長.因此在不一樣的事務看來,同一時刻看到的相同行的數據多是不同的,即一個行可能有多個版本.是否聽起來難以想象呢?性能
原來,爲了實現mvcc, innodb對每一行都加上了兩個隱含的列,其中一列存儲行被更新的」時間」,另一列存儲行被刪除的」時間」. 可是innodb存儲的並非絕對的時間,而是與時間對應的數據庫系統的版本號,每當一個事務開始的時候,innodb都會給這個事務分配一個遞增的版本號,因此版本號也能夠被認爲是事務號.對於每個」查詢」語句,innodb都會把這個查詢語句的版本號同這個查詢語句遇到的行的版本號進行對比,而後結合不一樣的事務隔離等級,來決定是否返回該行.大數據
下面分別以select、delete、 insert、 update語句來講明:優化
1) SELECTspa
對於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數據類型.