mysql的默認隔離級別

引言

開始咱們的內容,相信你們必定遇到過下面的一個面試場景mysql

面試官:「講講mysql有幾個事務隔離級別?」
你:「讀未提交,讀已提交,可重複讀,串行化四個!默認是可重複讀」
面試官:「爲何mysql選可重複讀做爲默認的隔離級別?」
(你面露苦色,不知如何回答!)
面試官:"大家項目中選了哪一個隔離級別?爲何?"
你:「固然是默認的可重複讀,至於緣由。。呃。。。」
(而後你就能夠回去等通知了!)
面試

爲了不上述尷尬的場景,請繼續往下閱讀!
Mysql默認的事務隔離級別是可重複讀(Repeatable Read),那互聯網項目中Mysql也是用默認隔離級別,不作修改麼?
OK,不是的,咱們在項目中通常用讀已提交(Read Commited)這個隔離級別!
what!竟然是讀已提交,網上不是說這個隔離級別存在不可重複讀幻讀問題麼?不用管麼?好,帶着咱們的疑問開始本文!sql

正文

咱們先來思考一個問題,在Oracle,SqlServer中都是選擇讀已提交(Read Commited)做爲默認的隔離級別,爲何Mysql不選擇讀已提交(Read Commited)做爲默認隔離級別,而選擇可重複讀(Repeatable Read)做爲默認的隔離級別呢?
數據庫

Why?Why?Why?

這個是有歷史緣由的,固然要從咱們的主從複製開始講起了!
主從複製,是基於什麼複製的?
是基於binlog複製的!這裏不想去搬binlog的概念了,就簡單理解爲binlog是一個記錄數據庫更改的文件吧~
binlog有幾種格式?
OK,三種,分別是session

  • 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,所以從機執行的順序和主機不一致!就會出現主從不一致!
如何解決?
解決方案有兩種!
(1)隔離級別設爲可重複讀(Repeatable Read),在該隔離級別下引入間隙鎖。當Session 1執行delete語句時,會鎖住間隙。那麼,Ssession 2執行插入語句就會阻塞住!
(2)將binglog的格式修改成row格式,此時是基於行的複製,天然就不會出現sql執行順序不同的問題!奈何這個格式在mysql5.1版本開始才引入。所以因爲歷史緣由,mysql將默認的隔離級別設爲可重複讀(Repeatable Read),保證主從複製不出問題!優化

那麼,當咱們瞭解完mysql選可重複讀(Repeatable Read)做爲默認隔離級別的緣由後,接下來咱們將其和讀已提交(Read Commited)進行對比,來講明爲何在互聯網項目爲何將隔離級別設爲讀已提交(Read Commited)ui

對比

ok,咱們先明白一點!項目中是不用讀未提交(Read UnCommitted)串行化(Serializable)兩個隔離級別,緣由有二

  • 採用讀未提交(Read UnCommitted),一個事務讀到另外一個事務未提交讀數據,這個不用多說吧,從邏輯上都說不過去!
  • 採用串行化(Serializable),每一個次讀操做都會加鎖,快照讀失效,通常是使用mysql自帶分佈式事務功能時才使用該隔離級別!(筆者從未用過mysql自帶的這個功能,由於這是XA事務,是強一致性事務,性能不佳!互聯網的分佈式方案,多采用最終一致性的事務解決方案!)

也就是說,咱們該糾結都只有一個問題,究竟隔離級別是用讀已經提交呢仍是可重複讀?
接下來對這兩種級別進行對比,講講咱們爲何選讀已提交(Read Commited)做爲事務隔離級別!
假設表結構以下

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 | +----+-------+

爲了便於描述,下面將

  • 可重複讀(Repeatable Read),簡稱爲RR;
  • 讀已提交(Read Commited),簡稱爲RC;

原因一:在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隔離級別下,走聚簇索引,進行所有掃描,最後會將整個表鎖上,以下所示

原因三:在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級別下,不可重複讀問題須要解決麼?
不用解決,這個問題是能夠接受的!畢竟你數據都已經提交了,讀出來自己就沒有太大問題!Oracle的默認隔離級別就是RC,大家改過Oracle的默認隔離級別麼?

在RC級別下,主從複製用什麼binlog格式?
OK,在該隔離級別下,用的binlog爲row格式,是基於行的複製!Innodb的創始人也是建議binlog使用該格式!

總結

本文囉裏八嗦了一篇文章只是爲了說明一件事,互聯網項目請用:讀已提交(Read Commited)這個隔離級別!

 

做者:孤獨煙 出處: http://rjzheng.cnblogs.com/

相關文章
相關標籤/搜索