仍是老規矩,首先提出兩個待解決的問題:mysql
本文就將對上面這兩個問題進行解答,分析事務的隔離級別以及相關鎖機制。算法
隔離性主要是指數據庫系統提供必定的隔離機制,保證事務在不受外部併發操做影響的"獨立"環境執行,意思就是多個事務併發執行時,一個事務的執行不該影響其它事務的執行。sql
在SQL標準中定義了4種隔離級別,分別是:數據庫
上面4種隔離級別是SQL標準定義的,可是在不一樣的存儲引擎中,實現的隔離級別不盡相同。本文主要介紹MySQL InnoDB 存儲引擎中的隔離級別,在InnoDB存儲引擎中,Repeatable Read 是默認的事務隔離級別,同時該引擎的實現基於多版本的併發控制協議——MVCC (Multi-Version Concurrency Control),解決了幻讀問題,固然 髒讀和不可重複讀也是不存在的。MVCC最大的好處就在於讀不加鎖,讀寫不衝突,這樣極大的增長了系統的併發性能bash
未提交讀,這種狀況下,一個事務A能夠看到另外一個事務B未提交的數據,若是此時事務B發生回滾,那麼事務A拿到的就是髒數據,這也就是髒讀的含義。此隔離級別在MySQL InnoDB通常不會使用,不作過多說明。併發
提交讀,一個事務從開始直到提交以前,所作的任何修改對其餘事務都是不可見的。解決了髒讀問題,可是存在幻讀現象。性能
所謂幻讀,指的是在同一事務下,連續執行兩次一樣的SQL語句可能致使不一樣的結果,第二次的SQL語句可能會返回以前不存在的行,也就是"幻行"。測試
好比下面這個例子:優化
CREATE TABLE `t` (
`a` int(11) NOT NULL,
PRIMARY KEY (`a`)
) ENGINE=InnoDB
insert into t(a) values(1);
insert into t(a) values(2);
insert into t(a) values(4);
複製代碼
能夠從上圖看出,Read Committed這種隔離級別存在幻讀現象。實際上,Read Committed還可能存在不可重複讀的問題,不可重複讀,指的是一個事務內根據同一條件對行記錄進行屢次查詢,可是查詢出的數據結果不一致,緣由就是查詢區間數據被其餘事務修改了。ui
不可重複讀感受和幻讀有點像,實際上,前者強調是同一行記錄數據結果不同,後者強調的時屢次查詢返回的結果集不同,增長了或減小了。
可重複讀,該級別保證在同一事務中屢次讀取一樣記錄的結果是一致的,在InnoDB存儲引擎中同時解決了幻讀和不可重複讀問題。至於InnoDB經過什麼方式解決幻讀和不可重複讀問題,後續內容揭曉。
Serializable 是最高的隔離級別,它經過 強制事務串行執行,避免了幻讀的問題,可是 Serializable 會在讀取的每一行數據上都加鎖,因此可能致使大量的超時和鎖爭用的問題,所以併發度急劇降低,在MySQL InnoDB不被建議使用
隔離級別的實現與鎖機制密不可分,因此須要引入鎖的概念,首先咱們看下InnoDB存儲引擎提供的兩種標準的行級鎖:
注意: 共享鎖和排他鎖是不相容的。
MySQL InnoDB存儲引擎是使用多版本併發控制的,讀不加鎖,讀寫不衝突,除非特定場景下的顯示加讀鎖(這裏不去探究)。本小節主要分析Read Committed隔離級別下的加鎖狀況,在MVCC的做用下,通常也就是寫操做加X鎖了。
加鎖操做是和索引緊密相關的,對一個SQL語句進行加鎖分析時,也要仔細考究其屬性列上的索引類型。假設有數據表t1,有兩個列,name列和id列,插入了幾條數據,沒有明確索引狀況:
insert into t1(name,id) values("a",10);
insert into t1(name,id) values("b",11);
insert into t1(name,id) values("c",13);
insert into t1(name,id) values("d",20);
複製代碼
下面執行 delete from t1 where id = 10 這條SQL語句,這裏的隔離級別設置爲Read Committed,從這條SQL語句不能得知id列的索引狀況,因此須要分狀況討論:
下面是對以上幾種狀況的加鎖狀況進行概括總結,更詳細的內容能夠參閱數據庫大牛的文章:MySQL 加鎖處理分析
id列是主鍵
id是主鍵時,上述SQL只須要在id=10這條記錄上加X鎖便可
id列是二級惟一索引
若id列是惟一索引,而主鍵是name列,那麼SQL須要加上兩個X鎖,一個對應於id索引上的id=10的記錄,另外一把鎖對應於主鍵索引上的[name="a",id=10]的記錄
id列是二級非惟一索引
若id列上有非惟一索引,那麼對應的全部知足SQL查詢條件的記錄,都會被加鎖,同時,這些記錄在主鍵索引上的記錄也會被加鎖。
id列上沒有索引
若id列上沒有索引,SQL會走聚簇索引的全掃描進行過濾,因爲過濾是由MySQL Sever層面進行的,所以每條記錄,不管是否知足條件,都會被加上X鎖。
前面說過,在Repeatable Read隔離級別下,InnoDB存儲引擎解決了幻讀和不可重複讀問題,具體的原理是怎麼樣的呢?
以前簡短的介紹了InnoDB中行鎖的知識,下面來看下行鎖的三種算法:
Record Lock老是會去鎖住索引記錄,若是InnoDB存儲引擎在建表的時候沒有設置任何一個索引,那麼這時InnoDB會使用隱式的主鍵來進行鎖定。(表沒有定義主鍵的狀況,InnoDB會默認添加一個隱式的主鍵索引)
Next-Key Lock是結合了Gap Lock和Record Lock的一種鎖定算法,好比一個索引列有10,11,13和20這4個值,那麼該索引可能被Next-Key Locking的區間爲:
$-\infty$
,10)$+\infty$
)須要注意一點的是,當查詢的索引含有惟一屬性時,便是主鍵索引或者惟一索引時,InnoDB存儲引擎會對Next-Key Lock進行優化,將其降級爲Record Lock,即僅鎖住索引自己,通常加上X鎖。
Next-Key Lock機制設計的目的就是爲了解決幻讀問題,主要針對查詢列索引爲非惟一索引的時候。如下面這個例子進行說明:
CREATE TABLE `t1` (
`id` int(11) NOT NULL,
`name` varchar(200) DEFAULT NULL,
PRIMARY KEY (`name`),
KEY `id_indx` (`id`)
) ENGINE=InnoDB
insert into t1(name,id) values("a",10);
insert into t1(name,id) values("b",11);
insert into t1(name,id) values("c",13);
insert into t1(name,id) values("d",20);
複製代碼
這條SQL經過索引列id進行刪除操做,該索引爲非惟一索引,因此其使用傳統的Next-Key Locking 技術加鎖,而且因爲有主鍵索引和輔助索引兩個,須要分別進行鎖定。對於主鍵索引(即彙集索引),其僅對列name = "b"的索引加上 Record Lock,實際上就是X鎖。
而對於非惟一索引,其加上的時Next-Key Lock,鎖定範圍是(10,11),對其加上Gap Lock(間隙鎖),GAP鎖實際上就是加在兩條邊界記錄之間的位置。還須要注意的是,InnoDB還會對輔助索引下一個鍵值加上gap lock,即看到在(11,13)之間加了一個GAP鎖。對於11值自己加上Record Lock,即X鎖。
若此時開啓另一個事務執行下面的語句,就會阻塞:
1. select * from t1 where name = "b";
2. insert into t1(name,id) values("c",12);
複製代碼
好比第一條語句不能執行,由於在開始的事務中已經對彙集索引中的列name="b"的值加上了X鎖。所以執行會被阻塞。而第二個SQL,一樣不能執行,插入的值12在鎖定範圍(11,13)中,須要阻塞等待。
因此,從上例就能夠看出,GAP Lock的做用就是爲了阻止多個事務將記錄插入到同一範圍內,這樣就有效的解決了幻讀問題。
下面總結下InnoDB存儲引擎下的各類隔離級別:
隔離級別 | 髒讀可能性 | 不可重複讀可能性 | 幻讀可能性 | 加鎖讀 |
---|---|---|---|---|
Read Uncommitted | Yes | Yes | Yes | No |
Read Committed | No | Yes | Yes | No |
Repeatable Read | No | No | No | No |
Serializable | No | No | No | Yes |