爲什麼MySQL 默認隔離級別是RR,又被阿里設置爲RC

我們知道,我們可以通過這個命令查看數據庫當前的隔離級別,MySQL 默認隔離級別是RR. 

select @@tx_isolation;

 

ANSI/ISO SQL定義的標準隔離級別有四種,從高到底依次爲:可序列化(Serializable)、可重複讀(Repeatable Reads)、提交讀(Read Committed)、未提交讀(Read Uncommitted)。

-w1215

  • RU隔離級別下,可能發生髒讀、幻讀、不可重複讀等問題。

    未提交讀的數據庫鎖情況(實現原理)

    事務在讀數據的時候並未對數據加鎖。

    事務在修改數據的時候只對數據增加行級共享鎖。

     
  • RC隔離級別下,解決了髒讀的問題,存在幻讀、不可重複讀的問題。

    提交讀的數據庫鎖情況

    事務對當前被讀取的數據加 行級共享鎖(當讀到時才加鎖),一旦讀完該行,立即釋放該行級共享鎖;

    事務在更新某數據的瞬間(就是發生更新的瞬間),必須先對其加 行級排他鎖,直到事務結束才釋放。

     
  • RR隔離級別下,解決了髒讀、不可重複讀的問題,存在幻讀的問題。

    可重複讀的數據庫鎖情況

    事務在讀取某數據的瞬間(就是開始讀取的瞬間),必須先對其加 行級共享鎖,直到事務結束才釋放;

    事務在更新某數據的瞬間(就是發生更新的瞬間),必須先對其加 行級排他鎖,直到事務結束才釋放。

  • Serializable隔離級別下,解決了髒讀、幻讀、不可重複讀的問題。

    可序列化的數據庫鎖情況

    事務在讀取數據時,必須先對其加 表級共享鎖 ,直到事務結束才釋放;

    事務在更新數據時,必須先對其加 表級排他鎖 ,直到事務結束才釋放。

雖然可序列化解決了髒讀、不可重複讀、幻讀等讀現象。但是序列化事務會產生以下效果:

1.無法讀取其它事務已修改但未提交的記錄。

2.在當前事務完成之前,其它事務不能修改目前事務已讀取的記錄。

3.在當前事務完成之前,其它事務所插入的新記錄,其索引鍵值不能在當前事務的任何語句所讀取的索引鍵範圍中。

這四種隔離級別是ANSI/ISO SQL定義的標準定義的,我們比較常用的MySQL對這四種隔離級別是都支持的。

 

Oracle默認的隔離級別是 RC,而MySQL默認的隔離級別是 RR。那麼,你知道爲什麼嗎?

Oracle 的隔離級別

前面我們說過,Oracle只只支持ANSI/ISO SQL定義的Serializable和Read Committed,其實,根據Oracle官方文檔給出的介紹,Oracle支持三種隔離級別:

即Oracle支持Read Committed、Serializable和Read-Only。

Read-Only只讀隔離級別類似於可序列化隔離級別,但是只讀事務不允許在事務中修改數據,除非用戶是SYS。

在Oracle這三種隔離級別中,Serializable和Read-Only顯然都是不適合作爲默認隔離級別的,那麼就只剩Read Committed這個唯一的選擇了。

MySQL 的隔離級別

在MySQL設計之處,他的定位就是提供一個穩定的關係型數據庫。而爲了要解決MySQL單點故障帶來的問題,MySQL採用主從複製的機制。

所謂主從複製,其實就是通過搭建MySQL集羣,整體對外提供服務,集羣中的機器分爲主服務器(Master)和從服務器(Slave),主服務器提供寫服務,從服務器提供讀服務。

爲了保證主從服務器之間的數據的一致性,就需要進行數據同步.

 

MySQL在主從複製的過程中,數據的同步是通過bin log進行的,簡單理解就是主服務器把數據變更記錄到bin log中,然後再把bin log同步傳輸給從服務器,從服務器接收到bin log之後,再把其中的數據恢復到自己的數據庫存儲中。

那麼,binlog裏面記錄的是什麼內容呢?格式是怎樣的呢?

MySQL的bin log主要支持三種格式,分別是statement、row以及mixed。MySQL是在5.1.5版本開始支持row的、在5.1.8版本中開始支持mixed。

statement和row最大的區別,當binlog的格式爲statemen時,binlog 裏面記錄的就是 SQL 語句的原文(這句話很重要!!!後面會用的到)。

關於這幾種格式的區別,就不在這裏詳細展開了,之所以要支持row格式,主要是因爲statement格式中存在很多問題,最明顯的就是可能會導致主從數據庫的數據不一致。

那麼,這個主從同步和bin log我們要講的隔離級別有啥關係呢?

有關係,而且關係很大。

因爲MySQL早期只有statement這種bin log格式,這時候,如果使用提交讀(Read Committed)、未提交讀(Read Uncommitted)這兩種隔離級別會出現問題。

比如,在MySQL官網上,有人就給官方曾經提過一個相關的Bug

-w1661

這個bug的復現過程如下:

有一個數據庫表t1,表中有如下兩條記錄:

CREATE TABLE `t1` (
  `a` int(11) DEFAULT NULL,
  `b` int(11) DEFAULT NULL,
  KEY `a` (`a`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

insert into t1 values(10,2),(20,1);

接着開始執行兩個事務的寫操作:

以上兩個事務執行之後,數據庫裏面的記錄會變成(11,2)和(20,2),這個發上在主庫的數據變更大家都能理解。

因爲事務的隔離級別是read committed,所以,事務1在更新時,只會對b=2這行加上行級鎖,不會影響到事務2對b=1這行的寫操作

以上兩個事務執行之後,會在bin log中記錄兩條記錄,因爲事務2先提交,所以UPDATE t1 SET b=2 where b=1;會被優先記錄,然後再記錄UPDATE t1 SET a=11 where b=2;(再次提醒:statement格式的bin log記錄的是SQL語句的原文)

這樣bin log同步到備庫之後,SQL語句回放時,會先執行UPDATE t1 SET b=2 where b=1;,再執行UPDATE t1 SET a=11 where b=2;

這時候,數據庫中的數據就會變成(11,2)和(11,2)。這就導致主庫和備庫的數據不一致了!!!

爲了避免這樣的問題發生。MySQL就把數據庫的默認隔離級別設置成了Repetable Read,那麼,Repetable Read的隔離級別下是如何解決這樣問題的那?

那是因爲Repetable Read這種隔離級別,會在更新數據的時候不僅對更新的行加行級鎖,還會增加GAP lock。上面的例子,在事務2執行的時候,因爲事務1增加了GAP lock,就會導致事務執行被卡住,需要等事務1提交或者回滾後才能繼續執行。

除了設置默認的隔離級別外,MySQL還禁止在使用statement格式的bin log的情況下,使用READ COMMITTED作爲事務隔離級別。

一旦用戶主動修改隔離級別,嘗試更新時,會報錯:

ERROR 1598 (HY000): Binary logging not possible. Message: Transaction level 'READ-COMMITTED' in InnoDB is not safe for binlog mode 'STATEMENT'

小結

所以,爲什麼MySQL選擇RR作爲默認的數據庫隔離級別,其實就是爲了兼容歷史上的那種statement格式的bin log。

 

 

那麼,爲啥阿里要把這個數據庫隔離級別修改成 RC 呢,背後有什麼思考嗎?

RR 和 RC 的區別

想要搞清楚這個問題,我們需要先弄清楚 RR 和 RC 的區別,分析下各自的優缺點。

一致性讀

一致性讀,又稱爲快照讀。快照即當前行數據之前的歷史版本。快照讀就是使用快照信息顯示基於某個時間點的查詢結果,而不考慮與此同時運行的其他事務所執行的更改。

在MySQL 中,只有READ COMMITTED 和 REPEATABLE READ這兩種事務隔離級別纔會使用一致性讀。

在 RC 中,每次讀取都會重新生成一個快照,總是讀取行的最新版本。

在 RR 中,快照會在事務中第一次SELECT語句執行時生成,只有在本事務中對數據進行更改纔會更新快照。

在數據庫的 RC 這種隔離級別中,還支持"半一致讀" ,一條update語句,如果 where 條件匹配到的記錄已經加鎖,那麼InnoDB會返回記錄最近提交的版本,由MySQL上層判斷此是否需要真的加鎖。

鎖機制

數據庫的鎖,在不同的事務隔離級別下,是採用了不同的機制的。在 MySQL 中,有三種類型的鎖,分別是Record Lock、Gap Lock和 Next-Key Lock。

Record Lock表示記錄鎖,鎖的是索引記錄。

Gap Lock是間隙鎖,鎖的是索引記錄之間的間隙。

Next-Key Lock是Record Lock和Gap Lock的組合,同時鎖索引記錄和間隙。他的範圍是左開右閉的。

在 RC 中,只會對索引增加Record Lock,不會添加Gap Lock和Next-Key Lock。

在 RR 中,爲了解決幻讀的問題,在支持Record Lock的同時,還支持Gap Lock和Next-Key Lock;

主從同步

在數據主從同步時,不同格式的 binlog 也對事務隔離級別有要求。

MySQL的binlog主要支持三種格式,分別是statement、row以及mixed,但是,RC 隔離級別只支持row格式的binlog。如果指定了mixed作爲 binlog 格式,那麼如果使用RC,服務器會自動使用基於row 格式的日誌記錄。

而 RR 的隔離級別同時支持statement、row以及mixed三種。

爲什麼互聯網公司選擇使用 RC

提升併發

互聯網公司和傳統企業最大的區別是什麼?

高併發!

沒錯,互聯網業務的併發度比傳統企業要高處很多。2020年雙十一當天,訂單創建峯值達到 58.3 萬筆/秒。

要怎麼做才能扛得住這麼大的併發量,要做的、可以做的事情實在是太多了。

而有一個是通過修改數據庫的隔離級別來提升併發度。

爲什麼 RC 比 RR 的併發度要好呢?

首先,RC 在加鎖的過程中,是不需要添加Gap Lock和 Next-Key Lock 的,只對要修改的記錄添加行級鎖就行了。

這就使得併發度要比 RR 高很多。

另外,因爲 RC 還支持"半一致讀",可以大大的減少了更新語句時行鎖的衝突;對於不滿足更新條件的記錄,可以提前釋放鎖,提升併發度。

減少死鎖

因爲RR這種事務隔離級別會增加Gap Lock和 Next-Key Lock,這就使得鎖的粒度變大,那麼就會使得死鎖的概率增大。

死鎖:一個事務鎖住了表A,然後又訪問表B;另一個事務鎖住了表B,然後企圖訪問表A;這時就會互相等待對方釋放鎖,就導致了死鎖。

總結

MySQL數據庫的 RR 和 RC 兩種事務隔離級別,主要在加鎖機制、主從同步以及一致性讀方面存在一些差異。

而很多大廠,爲了提升併發度和降低死鎖發生的概率,會把數據庫的隔離級別從默認的 RR 調整成 RC。

當然,這樣做也不是完全沒有問題,首先使用 RC 之後,就需要自己解決幻讀的問題,這個很多時候幻讀問題其實是可以忽略的,或者可以用其他手段解決。

還有就是使用 RC 的時候,不能使用statement格式的 binlog,這種影響其實可以忽略不計了,因爲MySQL是在5.1.5版本開始支持row的、在5.1.8版本中開始支持mixed,後面這兩種可以代替 statement格式。

 

那麼我們回答了以下問題,

1、RR和RC到底有什麼區別?RR是如何解決不可重複讀問題的?

2、既然MySQL數據庫默認選擇了RR,那麼,爲啥大的互聯網公司會把默認的隔離級別改成RC?

然而你或許還會有以下問題:

1、row格式和statement有什麼區別?使用row的情況下,可以使用RR嗎?

2、文中提到的RC的GAP lock到底是什麼?Next-key Lock

 

 數據庫使用鎖是爲了支持更好的併發,提供數據的完整性和一致性。InnoDB是一個支持行鎖的存儲引擎,鎖的類型有:共享鎖(S)、排他鎖(X)、意向共享(IS)、意向排他(IX)。爲了提供更好的併發,InnoDB提供了非鎖定讀:不需要等待訪問行上的鎖釋放,讀取行的一個快照。該方法是通過InnoDB的一個特性:MVCC來實現的。

InnoDB有三種行鎖的算法:

1,Record Lock:單個行記錄上的鎖。

2,Gap Lock:間隙鎖,鎖定一個範圍,但不包括記錄本身。GAP鎖的目的,是爲了防止同一事務的兩次當前讀,出現幻讀的情況。

3,Next-Key Lock:1+2,鎖定一個範圍,並且鎖定記錄本身。對於行的查詢,都是採用該方法,主要目的是解決幻讀的問題。

 

mysql binlog的三種格式簡單概括總結

1、三種格式:row、statement、mixed

2、區別:row格式文件比較大,statement比較小,row格式保存的是一行一行的數據,statement保存的是sql語句,mixed格式介於二者之間,statement容易丟數據,row格式則不會

3、statement容易丟數據原因是,有時候,SQL語句裏面會用到一些函數,比如說取當前日期的函數sysdate,你要是用statement,binlog裏同步過去的就是這個帶有函數的SQL語句,而主庫的當前日期,和binlog同步到slave上的當前日期,肯定是有差異的,這樣兩條數據就不一致了,所以這樣同步的數據,就會有問題

4、row是直接把表插入到備份庫中,statement是導出主庫語句後,導入到備份庫中,存在時間差。

每種格式的概括

STATEMENT

記錄的是執行的SQL語句

優點:
日誌記錄量相對較小, 節約磁盤及網絡IO


缺點:
可能造成MySQL複製的主備服務器數據不一致
必須記錄上下文信息, 以保證語句在從服務器上執行結果相同
對於特定函數如 UUID(), user() 這種非確定性函數是無法正確複製

ROW

記錄的是每一行數據的修改, MySQL5.7+的默認ROW格式.

優點:
可以避免MySQL複製中出現主從不一致的問題
對每一行數據的修改比STATEMENT模式高效
可在誤刪改數據後, 同時無備份可以恢復時, 通過分析binlog日誌進行反向處理達到恢復數據目的

缺點:
由於記錄每一行數據的修改, 所以日誌量比較大
可通過binlog_row_image=FULL | MINIMAL | NOBLOB 設置日誌記錄的方式.

FULL: 記錄行中所有列修改前後的數據.
MINIMAL: 記錄行中所有列修改前的數據+被修改列修改後的數據.
NOBLOB: 記錄行中所有列修改前的數據+(未對行中TEXT和BLOB類型列修改時, 記錄TEXT和BLOB類型以外的列的數據.)

MIXED

混合STATEMENT和ROW兩種格式, MySQL會根據執行的SQL語句自動選擇.
一般的複製使用STATEMENT格式,對於STATEMENT格式無法複製的操作使用ROW格式.

如何選擇binlog日誌格式?
在同一個IDC機房中, 建議使用MIXED或ROW格式, 當使用ROW格式時, 建議設置binlog_row_image=MINIMAL

 

關於以上幾個問題,你對哪個更感興趣呢?

talk is easy, show me the code

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