MySQL學習系列之InnoDB下事務隔離機制

一. MySQL經常使用存儲引擎

MyISAM(MySQL 5.5.5 以前默認的存儲引擎)

特色:

  • 訪問速度快。
  • 不支持事務,所以適用不要求事務完整性或以select和insert爲主。
  • 鎖粒度是支持併發的表級鎖,這樣的優勢是加鎖快,開銷小,而對應的是併發性比較弱,容易引起鎖衝突。

應用場景

  1. 在讀寫操做很是頻繁的時候時候忌用,由於這樣容易產生大量的鎖衝突,造成大量的等待。
  2. 適用於查詢爲主的應用場景。

InnoDB(MySQL 5.5.5及之後默認的存儲引擎)

特色:

  • 支持事務,支持回滾,所以適用於須要進行事務處理的應用場景
  • 鎖粒度是支持MVCC(這裏包含樂觀鎖的概念,下文進行解釋)的行級鎖,所以有很高的併發性
  • 支持外鍵

應用場景:

  • 適用於須要事務操做,例如修改、刪除爲主的項目中

ACID事務:

  • A事務的原子性(Atomicity):事務是一個不可分割的單元,事務中的全部操做,要麼全作完,要麼全不作html

  • C 事務的一致性(Consistency):一致性,即在事務開始以前和事務結束之後,數據庫的完整性約束沒有被破壞。好比用戶A給用戶B轉帳,用戶B給用戶A轉帳,而約束就是兩我的的總金額仍是同樣的mysql

  • I 事務的隔離性(Isolation):多個事務併發訪問時,事務之間是隔離的,一個事務不該該影響其它事務運行效果。在下面會擴展MySQL事務隔離級別。laravel

  • D 事務的持久性(Durability):意味着在事務完成之後,該事務所對數據庫所做的更改便持久的保存在數據庫之中,並不會被回滾。sql

Archive

特色:

  • 不支持事務
  • 鎖粒度是行級鎖
  • 只支持insert和select

應用場景:

  • 適用於存儲操做日誌記錄的數據

2、InnoDB中事務隔離級別

什麼是髒讀,不可重複讀,幻讀

  • 髒讀: 對於兩個事務A,B,事務A讀取了已經被B更新但尚未提交的字段,以後,若B回滾,T1讀取到的內容就是臨時無效的內容。數據庫

  • 不可重複讀: 在事務A中,屢次對同一數據進行讀取,此時事務B也對該數據進行訪問,也許事務B的修改,事務A屢次得到的數據可能不同。安全

  • 幻讀:事務A讀取與搜索條件相匹配的若干行。事務B以插入或刪除行等方式來修改事務A的結果集,而後再提交。這裏幻讀與不可重複讀的差異在於不可重複讀強調的是修改和刪除,而幻讀強調的是插入網絡

MySQL中如何避免髒讀、不可重複讀、幻讀

MySQL中存在四種隔離等級:併發

隔離級別 髒讀 不可重複讀 幻讀
未提交讀(Read uncommitted) 可能 可能 可能
已提交讀(Read committed) 不可能 可能 可能
可重複讀(Repeatable read) 不可能 不可能 可能
可串行讀(Serializable) 不可能 不可能 不可能
  • 未提交讀:容許髒讀,可能會讀取到別的事務中未提交的臨時數據
  • 已提交讀:只已經提交的數據,Oracle等數據庫默認是這個級別
  • 可重複讀:可重複讀,InnoDB默認的等級是這個,但是仍是出現幻讀
  • 可串行讀:徹底串行化的讀,表示每次加鎖都是表級鎖,並且讀寫相互阻塞,讀是共享鎖,寫是排他鎖

這裏思考一下,爲啥阻止幻讀的隔離級別比不可重複讀的高,由於InnoDB是行級鎖,不可重複讀是針對於對於正在修改或刪除的數據行加鎖,但仍是能夠對錶進行插入,因此可能出現幻讀,要避免幻讀就要把表的讀寫都變成表級鎖,才能避免幻讀,也所以變成了隔離等級爲「可串行讀」。數據庫設計

PS:以上內容以 悲觀鎖 的概念能夠更好理解,不過實際中出於性能考慮,是用以樂觀鎖 爲理論基礎的MVCC(多版本併發控制,Multi-Version Concurrency Control )高併發

什麼是悲觀鎖,什麼是樂觀鎖

悲觀鎖:

悲觀鎖,正如其名,具備強烈的獨佔和排他特性。它指的是對數據被外界(包括本系統當前的其餘事務,以及來自外部系統的事務處理)修改持保守態度,所以,在整個數據處理過程當中,將數據處於鎖定狀態。悲觀鎖的實現,每每依靠數據庫提供的鎖機制(也只有數據庫層提供的鎖機制才能真正保證數據訪問的排他性,不然,即便在本系統中實現了加鎖機制,也沒法保證外部系統不會修改數據)。
即在事務A中數據在進行讀取(共享鎖)的時候,其餘事務不能進行修改(排他鎖),當在事務A的數據進行修改(排他鎖)的時候,不能進行讀取(共享鎖)。

優勢:能夠獨自佔有,直到執行操做完成,而後釋放鎖,爲數據處理的安全提供了保障。

缺點:可是在效率方面,處理加鎖的機制會讓數據庫產生額外的開銷,還有增長產生死鎖的機會


樂觀鎖:

悲觀鎖大多數狀況下依靠數據庫的鎖機制實現,以保證操做最大程度的獨佔性。但隨之而來的就是數據庫 性能的大量開銷,特別是對長事務而言,這樣的開銷每每沒法承受。
而樂觀鎖就很好的解決了這個問題,樂觀鎖能夠有兩種方式實現,一種是version,一種是時間戳,這裏以version爲例,假設數據通常狀況下不會發生衝突,只有在提交的時候,纔會進行加鎖,並判斷這提交的事務的版本與當前數據庫的版本的對比,若是提交的數據版本號大於數據庫表當前版本號,則予以更新,不然認爲是過時數據,則把請求駁回。

優勢:

樂觀鎖機制避免了長事務中的數據庫加鎖開銷,大大提高了大併發量下的系統總體性能表現。

缺點:

若是是在高併發下,不少用戶容易出現衝突,即請求容易駁回


InnoDB中MVCC的實現:

MVCC的實現沒有固定的規範,每一個數據庫中都會有不一樣的實現,這裏討論InnoDB的MVCC,其內部原理是經過樂觀鎖,在InnoDB中,會在每行數據後面添加兩個額外的隱藏的值來實現MVCC,一個是這個數據什麼時候被建立,一個是這數據什麼時候過時。在實際中,這裏存儲的是版本號,每開啓一個新的事務,事務的版本號就回增長。在可重讀Repeatable reads事務隔離級別下:

  1. SELECT時,讀取建立的版本號<=當前數據庫的版本。刪除版本爲空或>當前當前事務版本的
  2. INSERT時,保存當前事務版本爲行的建立版本
  3. DELETE時,保存當前版本爲行的刪除版本
  4. UPDATE時,插入一條新數據。保存當前事務版本爲行建立版本,同一時候保存當前事務版本到原來刪除的行
  5. 經過MVCC,儘管每行記錄都需要額外的存儲空間,不少其它的行檢查工做以及一些額外的維護工做。但可以下降鎖的使用,大多數讀操做都不用加鎖,讀數據操做很是easy,性能很是好,並且也能保證僅僅會讀取到符合標準的行。也僅僅鎖住必要行。

經過MVCC,雖然每行記錄都須要額外的存儲空間,更多的行檢查工做以及一些額外的維護工做,但能夠減小鎖的使用,大多數讀操做都不用加鎖,讀數據操做很簡單,性能很好,而且也能保證只會讀取到符合標準的行,也只鎖住必要行。

咱們無論從數據庫方面的教課書中學到,仍是從網絡上看到,大都是上文中事務的四種隔離級別這一模塊列出的意思,RR級別是可重複讀的,但沒法解決幻讀,而只有在Serializable級別才能解決幻讀。因而我就加了一個事務C來展現效果。在事務C中添加了一條teacher_id=1的數據commit,RR級別中應該會有幻讀現象,事務A在查詢teacher_id=1的數據時會讀到事務C新加的數據。可是測試後發現,在MySQL中是不存在這種狀況的,在事務C提交後,事務A仍是不會讀到這條數據。可見在MySQL的RR級別中,是解決了幻讀的讀問題的。參見下圖

MVCC中RR隔離等級下解決幻讀示例圖


MVCC中可能讀取的以前版本的數據,要如何讀取當前數據呢?

這裏又引伸出兩個概念:快照讀和當前讀
快照讀:就是普通的select

  • select * from table ...;

當前讀:特殊的讀操做(是要獲取鎖的),例如插入/更新/刪除就是當前讀

  • select * from table where ? lock in share mode;(共享鎖)
  • select * from table where ? for update;(排他鎖)
  • insert;
  • update;
  • delete;

事務的隔離級別實際上都是定義了當前讀的級別,MySQL爲了減小鎖處理(包括等待其它鎖)的時間,提高併發能力,引入了快照讀的概念,使得select不用加鎖。事務的隔離只定義了讀數據的要求,而寫的要求固然是"當前讀"。

這裏注意,InnoDB默認的是行級鎖,行級鎖都是基於索引的。在當前讀的查詢語句中,若是一條SQL語句用不到索引是不會使用行級鎖的,會使用表級鎖把整張表鎖住,這點須要注意。

在MySQL中,insert、update、delete語句默認會對涉及的數據集加排他鎖,在Read committed等級下select語句默認是不會加的,若是要加的話,則須要顯示在後面加 lock in share mode。在Repeatable read以及在Serializable隔離機制下,select是加共享鎖的。


我是MySQL菜鳥,若是對於本文中有什麼疑問或者問題,歡迎互相討論提升

參考內容:
《深刻理解樂觀鎖與悲觀鎖》
《Innodb中的事務隔離級別和鎖的關係》
《慕課網-數據庫設計那些事》
《MySQL經常使用數據存儲引擎區別》

相關文章
相關標籤/搜索