Mysql為什么不和Oracle一樣使用RC,而用RR
使用RC的原因
這個是有歷史原因的,當然要從我們的主從復制開始講起了!
主從復制,是基於什么復制的?
是基於binlog復制的!這里不想去搬binlog的概念了,就簡單理解為binlog是一個記錄數據庫更改的文件吧~
binlog有幾種格式?
OK,三種,分別是
statement:記錄的是修改SQL語句
row:記錄的是每行實際數據的變更
mixed:statement和row模式的混合
那Mysql在5.0這個版本以前,binlog只支持STATEMENT這種格式!而這種格式在讀已提交(Read Commited)這個隔離級別下主從復制是有bug的,因此Mysql將可重復讀(Repeatable Read)作為默認的隔離級別!
接下來,就要說說當binlog為STATEMENT格式,且隔離級別為讀已提交(Read Commited)時,有什么bug呢?如下圖所示,在主(master)上執行如下事務
此時在主(master)上執行下列語句
select * from test;
輸出如下
+---+ | b | +---+ | 3 | +---+ 1 row in set
但是,你在此時在從(slave)上執行該語句,得出輸出如下
Empty set
這樣,你就出現了主從不一致性的問題!原因其實很簡單,就是在master上執行的順序為先刪后插!而此時binlog為STATEMENT格式,它記錄的順序為先插后刪!從(slave)同步的是binglog,因此從機執行的順序和主機不一致!就會出現主從不一致!
Mysql5.0前 slave與master執行順序不一致解決方案
(1) 隔離級別設為可重復讀(Repeatable Read),在該隔離級別下引入間隙鎖。當Session 1執行delete語句時,會鎖住間隙。那么,Ssession 2執行插入語句就會阻塞住!
(2) 將binglog的格式修改為row格式,此時是基於行的復制,自然就不會出現sql執行順序不一樣的問題!奈何這個格式在mysql5.1版本開始才引入。
因此由於歷史原因,mysql將默認的隔離級別設為可重復讀(Repeatable Read),保證主從復制不出問題!
互聯網項目推薦將隔離級別設為讀已提交(Read Commited)的原因
對比RR級別。
測試表結構如下:
CREATE TABLE `test` ( `id` int(11) NOT NULL, `color` varchar(20) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB
數據如下:
+----+-------+ | id | color | +----+-------+ | 1 | red | | 2 | white | | 5 | red | | 7 | white | +----+-------+
在RR隔離級別下,存在間隙鎖,導致出現死鎖的幾率比RC大的多
此時執行語句:
select * from test where id <3 for update;
在RR隔離級別下,存在間隙鎖,可以鎖住(2,5)這個間隙,防止其他事務插入數據!
而在RC隔離級別下,不存在間隙鎖,其他事務是可以插入數據!
ps:在RC隔離級別下並不是不會出現死鎖,只是出現幾率比RR低而已!
在RR隔離級別下,條件列未命中索引會鎖表!而在RC隔離級別下,只鎖行(***)
此時執行語句:
update test set color = 'blue' where color = 'white';
在RC隔離級別下,其先走聚簇索引,進行全部掃描。加鎖如下:
但在實際中,MySQL做了優化,在MySQL Server過濾條件,發現不滿足后,會調用unlock_row方法,把不滿足條件的記錄放鎖。
實際加鎖如下:
然而,在RR隔離級別下,走聚簇索引,進行全部掃描,最后會將整個表鎖上,如下所示:
ps:鎖表其實是鎖行+Gap鎖
在RC隔離級別下,半一致性讀(semi-consistent)特性增加了update操作的並發性
在5.1.15的時候,innodb引入了一個概念叫做“semi-consistent”,減少了更新同一行記錄時的沖突,減少鎖等待。
所謂半一致性讀就是,一個update語句,如果讀到一行已經加鎖的記錄,此時InnoDB返回記錄最近提交的版本,由MySQL上層判斷此版本是否滿足update的where條件。若滿足(需要更新),則MySQL會重新發起一次讀操作,此時會讀取行的最新版本(並加鎖)!
具體表現如下:
此時有兩個Session,Session1和Session2!
Session1執行
update test set color = 'blue' where color = 'red';
先不Commit事務!
與此同時Ssession2執行
update test set color = 'blue' where color = 'white';
session 2嘗試加鎖的時候,發現行上已經存在鎖,InnoDB會開啟semi-consistent read,返回最新的committed版本(1,red),(2,white),(5,red),(7,white)。MySQL會重新發起一次讀操作,此時會讀取行的最新版本(並加鎖)!
而在RR隔離級別下,Session2只能等待!
RC下的binlog格式(僅記錄)
該隔離級別下,用的binlog為row格式,是基於行的復制!Innodb的創始人也是建議binlog使用該格式!當然RR也是。