為什么MySQL默認使用RR隔離級別?
對于數(shù)據(jù)庫的默認隔離級別,Oracle默認的隔離級別是 RC,而MySQL默認的隔離級別是 RR。
那么,你知道為什么Oracle選擇RC作為默認級別,而MySQL要選擇RR作為默認的隔離級別嗎?
Oracle的隔離級別
Oracle支持ANSI/ISO SQL定義的Serializable和Read Committed兩種隔離級別,根據(jù)Oracle官方文檔的介紹,Oracle的隔離級別包括Read Committed、Serializable和Read-Only。
圖片
Read-Only的隔離級別類似于Serializable,然而僅允許只讀事務(wù)進行數(shù)據(jù)檢索,不允許在事務(wù)中修改數(shù)據(jù),除非使用者是SYS用戶。
在Oracle的這三種隔離級別中,顯而易見,Serializable和Read-Only都不適合作為默認隔離級別,因此唯一的選擇就是Read Committed了。
MySQL的隔離級別
與Oracle相比,MySQL提供的默認隔離級別范圍更加廣泛。
首先,我們排除了Serializable和Read Uncommitted這兩種級別,原因是一個隔離級別過高會影響并發(fā)度,另一個過低則存在臟讀問題。
剩下的RR和RC兩種,如何選擇呢?
MySQL在設(shè)計之初就旨在提供一個穩(wěn)定的關(guān)系型數(shù)據(jù)庫。為解決MySQL單點故障問題,MySQL采取了主從復制機制。
所謂的主從復制,即通過建立MySQL集群,以整體向外提供服務(wù)。集群內(nèi)的機器分為主服務(wù)器(Master)和從服務(wù)器(Slave),主服務(wù)器負責提供寫服務(wù),而從服務(wù)器則提供讀服務(wù)。
在MySQL主從復制過程中,數(shù)據(jù)的同步通過binlog進行。簡單來說,主服務(wù)器將數(shù)據(jù)變更記錄到binlog中,然后將binlog同步傳輸給從服務(wù)器。從服務(wù)器接收到binlog后,將其中的數(shù)據(jù)恢復到自己的數(shù)據(jù)庫存儲中。
那么,binlog里記錄的究竟是什么內(nèi)容?它的格式又是怎樣的呢?
MySQL的binlog主要支持三種格式,即statement、row和mixed。MySQL從5.1.5版本開始支持row格式,在5.1.8版本中開始支持mixed格式。
statement和row之間最重要的區(qū)別在于,當binlog的格式為statement時,binlog記錄的是SQL語句的原文。
由于MySQL早期僅支持statement這一種binlog格式,因此在使用提交讀(Read Committed)和未提交讀(Read Uncommitted)這兩種隔離級別時都可能會出現(xiàn)問題。
舉個例子,有一個數(shù)據(jù)庫表t1,表中有如下兩條記錄:
CREATE TABLE `t1` (
`a` int(11) DEFAULT NULL,
`b` int(11) DEFAULT NULL,
KEY `b` (`b`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
insert into t1 values(10,1);
接著開始執(zhí)行兩個事務(wù)的寫操作:
Session 1 | Session 2 |
set session transaction isolation level read committed; | |
set autocommit = 0; | set session transaction isolation level read committed; |
begin; | begin; |
delete from t1 where b < 100; | |
insert into t1 values(10,99); | |
commit; | |
commit; |
以上兩個事務(wù)執(zhí)行之后,數(shù)據(jù)庫里面的記錄會只有一條記錄(10,99),這個發(fā)生在主庫的數(shù)據(jù)變更大家都能理解。
★
即使 Session 1 的刪除操作在 Session 2 的插入操作之后提交,由于 READ COMMITTED 的隔離級別,Session 2 的插入操作不會看到 Session 1 的刪除操作,所以最后數(shù)據(jù)庫中仍然會留下 Session 2 插入的記錄 (10,99)。
這種行為是 READ COMMITTED 隔禽級別的一種特性,它會在事務(wù)開始時創(chuàng)建一個快照。確保事務(wù)之間的隔離性,避免了數(shù)據(jù)不一致性的問題。
以上兩個事務(wù)執(zhí)行之后,會在bin log中記錄兩條記錄,因為事務(wù)2先提交,所以insert into t1 values(10,99);會被優(yōu)先記錄,然后再記錄delete from t1 where b < 100;(再次提醒:statement格式的bin log記錄的是SQL語句的原文)
這樣bin log同步到備庫之后,SQL語句回放時,會先執(zhí)行insert into t1 values(10,99);,再執(zhí)行delete from t1 where b < 100;。
這時候,數(shù)據(jù)庫中的數(shù)據(jù)就會變成 EMPTY SET,即沒有任何數(shù)據(jù)。這就導致主庫和備庫的數(shù)據(jù)不一致了?。。?/p>
為了解決這種問題,MySQL將數(shù)據(jù)庫的默認隔離級別設(shè)置為Repeatable Read。在Repeatable Read隔離級別下,針對更新數(shù)據(jù)時會不僅對更新的行加行級鎖,還會增加GAP鎖和next-key鎖。在上述例子中,當事務(wù) 2 執(zhí)行時,由于事務(wù) 1 添加了GAP鎖和next-key鎖,這將導致事務(wù) 2 執(zhí)行被阻塞,需要等待事務(wù) 1 提交或回滾后才能繼續(xù)執(zhí)行。
除了設(shè)置默認的隔離級別外,MySQL還禁止在使用statement格式的binlog的情況下,將事務(wù)隔離級別設(shè)置為READ COMMITTED。
一旦用戶主動修改隔離級別,嘗試更新時,會報錯:
ERROR 1598 (HY000): Binary logging not possible. Message: Transaction level 'READ-COMMITTED' in InnoDB is not safe for binlog mode 'STATEMENT'
因此,我們現(xiàn)在明白了為什么MySQL選擇Repeatable Read作為默認的數(shù)據(jù)庫隔離級別了,實際上是為了與歷史上那種statement格式的binlog保持兼容性。