MySQL是如何實現事務的隔離級別

摘要

本文旨在瞭解MySQL InnoDB引擎如何支持事務的隔離級別。
文章主要內容分兩個部分。
第一部分闡述數據庫的併發問題以及爲之產生的ANSI SQL 標準隔離級別。
第二部分根據 MySQL 官方文檔解釋 InnoDB 是如何支持這些隔離級別的。html

數據庫事務的併發問題

ANSI SQL 隔離級別的定義是來源於三個異象問題,ANSI SQL在權衡系統的可靠性和性能之間定義了不一樣的級別。因此這裏先介紹主流的三個併發問題是什麼。mysql

讀異象 (read phenomena)

  • 髒讀 (dirty read)
    • 一個事務讀取到了另外一個事務更新但未提交的數據。當另外一個事務回滾或者再次修改,就會讀取到髒數據
  • 不可重複讀 (non-repeatable read / Fuzzy Read)
    • 現象爲一個事務屢次一樣讀取數據的操做其結果不一致。例如讀取時有其餘事務提交了修改
  • 幻讀 (phantom)
    • 在屢次一樣的查詢操做下,後面的查詢出現了新行的數據。
    • 例如在執行屢次查詢的時候,其餘事務插入了一個新行或者修改了某行數據使得能匹配上Where條件,那麼後一次查詢必然將查詢到這個新數據(也就感受出現了幻覺,莫名其妙多出了一行)
    • 不可重複讀和幻讀其實相似,可是幻讀偏向於查詢新增數據(因此專門弄了間隙鎖來防止幻讀),不可重複讀則是修改數據。

    Def: The so-called phantom problem occurs within a transaction when the same query produces different sets of rows at different times. For example, if a SELECT is executed twice, but returns a row the second time that was not returned the first time, the row is a 「phantom」 row.sql

    • 注:幻象 (Phantom) ,幻讀 (Phantom Read) ,幻象問題 (Phantom Problem) 都是一個表述。
  • 總結:上面介紹的三個讀併發問題,其本質都是一個事務在讀其餘事務修改過的數據。

標準事務隔離級別

  • 由於事務在併發執行的過程當中會存在相互干擾,須要有隔離性的保障,故引入事務隔離級別規範,以平衡操做的性能、可靠和一致

ANSI SQL下規定的隔離級別(1992 - 很老的標準了)數據庫

  1. 未提交讀 - Read Uncommited
    • 風險挺高,可是若是隻是存粹的讀操做能夠推薦使用(MyISAM也挺香呀)
  2. 已提交讀 - Read Commited(互聯網主流默認使用的隔離級別)
    • 事務沒法看見其餘未提交事務的修改
  3. 可重複讀 - Repeatable Read (MySQL 默認)
    • 只讀事務開始時的快照數據
  4. 可序列化 - Serializable
  • ANSI SQL規定的隔離級別其實仍是根據主流存在的數據庫併發問題來定義的,每一個隔離級別來解決不一樣的問題。
    若是細分繼續細分不一樣的併發問題,隔離級別還能更加細化。
  • ANSI SQL Isolation Levels Defined in terms of the Three Original Phenomena

MySQL是如何支持不一樣隔離級別的

知識準備(術語解釋)

  • 一致性讀取 (consistent read)
    • 事務在進行讀操做時,使用的是事務開始時的行快照數據,這樣就不用擔憂讀到其餘其餘事務修改的數據。
    • 在[可重複讀]下,事務快照是基於第一次讀操做的快照(經過undo log 回溯)
    • 在[可提交讀]下,每一次一致性讀操做都會重置快照
    • 優勢:不上鎖,容許其餘事務進行修改
  • 半一致性讀取 (semi-consistent read)
    • UPDATE語句中的讀/匹配操做,當UPDATE語句執行的時候,InnoDB會取最後一次提交到MySQL的數據來進行 Where 子句中的匹配。
      • 若是匹配上了(也就是要更新),那就重讀該行並加鎖(或等待加鎖)
        • todo 爲何要重讀該行不是很理解,直接用那條數據不就行了
    • 僅用於[可提交讀]隔離級別
    • 我的理解其實就是一致性讀取在[可提交讀]隔離級別下 UPDATE的表現
  • 鎖定讀 (locking read)
    • 即加鎖的查詢語句
    • E.g SELECT ... FOR UPDATE | SELECT ... FOR SHARE
  • 行鎖 (record lock) : 即鎖定索引記錄的鎖,即便沒有索引也會找到對應行記錄鎖主鍵哦
  • 間隙鎖 (gap lock) : 鎖在了兩條索引記錄之間的鎖,或者(無窮小,某索引)/(某索引,無窮大),他們鎖住的是一個範圍,且不一樣的間隙鎖不互斥,他們排斥的只是在鎖範圍內的插入操做
    • mark: R.C 隔離級別下是被禁用的
  • next-key lock : 行鎖和間隙鎖的組合實現

未提交讀 - Read Uncommited

  • SELECT 語句採用的無鎖策略,也就更容易產生髒讀

已提交讀 - Read Commited

  • 該隔離級別下的一致性讀取,讀的都是最新版的快照,每次讀快照都會被重置成最新

    Each consistent read, even within the same transaction, sets and reads its own fresh snapshot.併發

  • 對於Locking Reads、UPDATE、DELETE 語句,InnoDB的鎖策略是隻鎖索引記錄,並無用間隙鎖來鎖範圍,因此容易產生幻讀問題

特別的對於 R.C 隔離級別有如下變更

  • 對於UPDATE語句,若是行已經被加鎖了,InnoDB會使用 「semi-consistent」read,來讀取快照中最新的值(而不是最原始的快照)來進行 WHERE 匹配更新。(也就是說 UPDATE 也採用 R.C 特供版一致性讀取)
  • 對於UPDATE或 DELETE語句,InnoDB僅對其更新或刪除的行持有鎖。MySQL評估WHERE條件後,將釋放不匹配行的記錄鎖。這大大下降了死鎖的可能性。
    • 好比掃表過程當中,掃到這行不是要修改的,那麼就釋放鎖,要改的就一直加鎖直到事務結束
    • R.R 級別下則不會釋放

關於這個「semi-consistent」 read

  • 他讀取的是最後一次提交到MySQL的版本,而不是快照中最先讀的那個版本
  • 優勢:MySQL能夠判斷當前的更新操做是否符合Where條件,若是不匹配就不用更新了,也就節省了一次寫操做,若是匹配就從新讀取嘗試加鎖
  • 缺點:若是都採用一致性讀consistent read,讀取都是最先事務的快照,就不會產生幻象問題了(Phantom Problem);可是採用了半一致性,兩次查詢的結果可能會不一致。

可重複讀 - Repeatable Read

  • 採用一致性讀取,同事務中的每次讀取都取第一次讀的快照。(R.R 版一致性讀取)

    Consistent reads within the same transaction read the snapshot established by the first read.性能

  • 對於 locking reads,UPDATE,DELETE ,其加鎖策略取決因而否是惟一索引惟一條件查詢
    • 惟一索引配合惟一查詢條件,引擎只鎖定那條索引記錄,不鎖間隙
    • 其餘場景,引擎使用Gap Lock 或 Next-Record Lock來鎖定掃描的索引範圍,以阻止其餘事務插入新行到該間隙

串行化 - Serializable

  • InnoDB 默默的把全部純 SELECT 語句都轉成了 SELECT ... FOR SHARE ,也就默認都加讀鎖

小結

  • InnoDB 是依賴於不一樣的鎖策略實現了不一樣隔離級別的要求
  • R.C 隔離級別使用了當前讀 (R.C 下的一致性讀取) 且 禁用了間隙鎖
  • 理論上 R.R 隔離級別下由於使用了一致性讀和間隙鎖是不會產生的幻讀問題的,因此標準可能有點老了
    • todo MySQL在介紹幻讀的時候說 R.R 下面會有幻讀,但又沒有實際的例子,很難讓人信服。(除非這個也算:兩次查詢前一次是普通讀,後一次是鎖定讀)

擴展

  • 在介紹 ANSI 隔離級別標準的時候,提到了ANSI標準是根據主流的三個併發問題來對症下藥的,但其實仍然有一些併發問題被 ANSI 標準給默認忽略或者說就不關心了,好比典型的髒讀和丟失修改。更詳細的能夠參考下圖(1995)
    • Isolation Types Characterized by Possible Anomalies Allowed.

參考文獻

相關文章
相關標籤/搜索