MySQL默認數據庫隔離級別為什么是RR?而互聯網大廠為什么把它修改為RC?
前言
大家好,我是田螺。
大家都知道mysql的默認數據庫隔離級別嘛? 是的,就是RR,但是呢,為什么阿里這些互聯網大廠,把mysql的數據隔離級別設置為RC呢?
本文田螺哥,按自己的回答思路,跟大家聊聊~
- RR和RC的區別
- mysql的主從復制
- binlog 日志的三種格式
- mysql的默認數據庫隔離級別為什么是RR
- 互聯網大廠為什么把隔離級別設置為RC
1. RR和RC的區別
大家應該都記得mysql數據庫的四種隔離級別吧。
- RC,也就是讀已提交,當前事務只能讀取到其他事務提交的數據,所以這種事務的隔離級別解決了臟讀問題,但還是會存在重復讀、幻讀問題。
- RR,可重復讀隔離級別,在一個事務執行期間,無論其他事務如何修改數據,只要本事務未結束,多次讀取同一數據集都會獲得與事務開始時一致的結果。它解決了不可重復讀的問題,但是還是可能存在幻讀~~
RR隔離級別,會設置了間隙鎖(Gap Lock和 Next-Key Lock),為了盡可能減少幻讀。但是特殊場景還是會存在幻讀的。
我歸納了它們的主要區別:
RC | RR | |
數據可見性 | 可能出現不可重復讀 | 解決了不可重復讀問題 |
鎖機制 | 僅加行鎖,無間隙鎖,臨鍵鎖退化為行鎖 | 過行鎖 + 間隙鎖 + 臨鍵鎖組合控制 |
是否存在幻讀 | 存在 | 盡可能解決,特殊情況也存在 |
性能 | 并發性能更高,鎖沖突少 | 一致性更強但鎖開銷大 |
適合場景 | 如電商秒殺 | 適合金融、庫存等核心業務 |
2. mysql的主從復制
在這里先跟大家一起復習一下mysql的主從復制。因為mysql的默認數據庫隔離級別,跟主從復制有關系的
主從復制原理,簡言之,分三步曲進行:
- 主數據庫有個binlog二進制文件,記錄了所有增刪改SQL語句。(binlog線程)
- 從數據庫把主數據庫的binlog文件的SQL 語句復制到自己的中繼日志 relay log(io線程)
- 從數據庫的relay log重做日志文件,再執行一次這些sql語句。(Sql執行線程)
詳細的主從復制過程如圖:
圖片
上圖主從復制過程分了五個步驟進行:
- 主庫的更新SQL(update、insert、delete)被寫到binlog
- 從庫發起連接,連接到主庫。
- 此時主庫創建一個binlog dump thread,把bin log的內容發送到從庫。
- 從庫啟動之后,創建一個I/O線程,讀取主庫傳過來的binlog內容并寫入到relay log
- 從庫還會創建一個SQL線程,從relay log里面讀取內容,從ExecMasterLog_Pos位置開始執行讀取到的更新事件,將更新內容寫入到slave的db
3. binlog日志三種格式
我們再來復習一下binlog日志的三種格式
- Statement:基于SQL語句的復制((statement-based replication,SBR))
- Row:基于行的復制。(row-based replication,RBR)
- Mixed:混合模式復制。(mixed-based replication,MBR)
3.1 Statement格式
每一條會修改數據的sql都會記錄在binlog中
- 優點:不需要記錄每一行的變化,減少了binlog日志量,節約了IO,提高性能。
- 缺點:由于記錄的只是執行語句,為了這些語句能在備庫上正確運行,還必須記錄每條語句在執行的時候的一些相關信息,以保證所有語句能在備庫得到和在主庫端執行時候相同的結果。
3.2 Row格式
不記錄sql語句上下文相關信息,僅保存哪條記錄被修改。
- 優點:binlog中可以不記錄執行的sql語句的上下文相關的信息,僅需要記錄那一條記錄被修改成什么了。所以rowlevel的日志內容會非常清楚的記錄下每一行數據修改的細節。不會出現某些特定情況下的存儲過程、或function、或trigger的調用和觸發無法被正確復制的問題。
- 缺點:可能會產生大量的日志內容。
3.3 Mixed格式
實際上就是Statement與Row的結合。一般的語句修改使用statment格式保存binlog,如一些函數,statement無法完成主從復制的操作,則采用row格式保存binlog,MySQL會根據執行的每一條具體的sql語句來區分對待記錄的日志形式
4. mysql的默認數據庫隔離級別為什么是RR?
MySQL早期,只有statement這種binlog格式。如果mysql的數據庫隔離級別設置為RC,在某些場景下,可能數據不一致。
比如,假設有表結構:
-- 創建測試表
CREATE TABLE accounts (
id INT PRIMARY KEY,
balance INT
);
-- 插入初始數據
INSERT INTO accounts (id, balance) VALUES
(1, 20),
(2, 10);
然后在RC的隔離級別下,并發執行這兩個事務:
時序 | 事務A | 事務B |
1 | begin; | |
2 | UPDATE accounts SET id=3 WHERE balance = 20; | |
3 | begin | |
4 | UPDATE accounts SET balance = 20 WHERE balance = 10; | |
5 | commit; | |
6 | commit; |
對于主數據庫:
以上這個并發事務,執行完后,數據庫記錄會變為:(3,20)和(2,20)。
如果是對于從庫: 因為有statement格式的binlog 日志,記錄的是SQL原文,然后事務2的先提交,再執行事務1的。
于是事務2執行完,變為(1,20)和(2,20)。然后事務1執行后,變為(3,20)和(3,20)
其實這就導致數據庫的主庫和備庫數據不一致啦~
因此,MySQL就把數據庫的默認隔離級別設置成了Repetable Read(RR)。RR隔離級別下,是如何解決這個問題的呢,其實就是加了GAP鎖(間隙鎖)。
我們剛那個例子,在事務2執行的時候,因為事務1增加了GAP鎖,就會導致事務執行被卡住,需要等事務1提交或者回滾后才能繼續執行。
5. 阿里為什么把隔離級別設置為RC
其實,不僅是阿里,現在我們公司,數據庫隔離級別嗎,也是設置為RC,為什么互聯網公司的數據庫隔離級別都改為RC呢?
5.1 提升并發
其實就是為了提升訪問速度。換幾句話說,其實是,并發高的時候,因為
- RC隔離級別中,不需要Gap鎖和Next-Key鎖,只是對需要修改的記錄加行鎖。
- 而RR隔離級別中,是可能加Gap鎖和Next-Key鎖的,為了解決幻讀問題。
正是因為這個間隙鎖的存在,所以RR隔離級別增加了死鎖的可能性,如果并發高,耗時就增加了。RR的隔離級別修改為RC隔離級別,并發上來時,整體的耗時是相對更少的。
5.2 修改為RC隔離級別,需要注意哪些問題?
如果修改為RC隔離級別,首先,就需要直面幻讀問題啦。
其實也還好,因為很多時候的幻讀問題,問題不大的,甚至可以忽略的。或者有時候,可以通過其他手段解決。
其次,設置為RC時,binlog的日志格式,不能設置為statement哈。其實吧,從MySQL是在5.1+版本開始,陸續支持row和mixed的格式啦~~