MySQL事務的隔離級別

techcoder發表於2019-03-27

說道事務,你肯定不陌生,和資料庫打交道免不了要用事務。事務最常見的應用就是轉賬,比如公司發工資,如果當公司的賬戶轉賬給員工的瞬間伺服器壞了,公司的賬戶錢變少了,而員工的賬戶的錢並沒有相應的增多,這就很尷尬了。事務的出現就是用來解決這種問題的。

我們知道MySQL是支援不同的資料庫引擎的,在MySQL5.5.5之前,MySQL的預設引擎都是MyISAM,到了5.5.5之後MySQL預設的資料庫引擎改為InnoDB,這兩個資料庫引擎有個最大的區別就是MyISAM引擎是不支援事務的,這就使得MyISAM引擎很難支撐複雜的業務場景,包括金融交易等。

簡單來說事務就是保證MySQL的一組操作,要麼全部成功,要麼全部失敗。想下,如果上面轉賬的例子,引入事務的概念,保證公司賬戶轉出錢和員工賬戶轉入錢要麼全部成功,要麼全部失敗,就可以完美解決問題了。

提到事務,大家肯定會想到ACID(Atomicity、Consistency、Isolation、Durability即原子性、一致性、隔離性、永續性),下面我們就來對其中之一的隔離性(Isolation)做一些介紹。

隔離級別(ISOLATION LEVEL)

隔離性其實比想象要複雜。在SQL中定義了四種隔離的級別,每一種隔離級別都規定了一個事務中的修改,哪些是在事務內和事務間是可見的,哪些是不可見的。較低階別的隔離通常來說能承受更高的併發,系統的開銷也會更小。

下面簡單的介紹一下這四種事務的隔離級別,並新增一些實踐。

READ UNCOMMITTED(未提交讀)

在READ UNCOMMITTED級別,事務的修改,即使沒有提交,對其他事務也都是可見的。事務可以讀取未提交的資料,這也被稱為髒讀(Dirty Read)。這個級別的隔離會導致很多問題,雖然在效能方面是最優的,但是缺乏其他級別的很多好處,所以這種隔離的級別很少在實際中應用。

  • CREATE TABLE

    CREATE TABLE `t` (
      `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
      `point` int(11) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
    複製程式碼
  • INSERT INTO

    INSERT INTO t(point) VALUES(90);
    複製程式碼
  • READ UNCOMMITTED實踐 開啟兩個MySQL SESSION,並將MySQL的預設隔離級別設定為READ UNCOMMITTED

    SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
    複製程式碼

    READ UNCOMMITTED PNG

    READ UNCOMMITTED GIF
    以上的一段GIF圖顯示了具體的實踐過程,左邊部分是SESSION A,右邊是SESSION B從上面的實踐中我們可以看到,當隔離級別設定為READ COMMITTED:

    • SESSION A第一次查詢point的值為90
    • SESSION B更新point為100
    • 此時SESSION B的更改並未提交
    • SESSION A第二次查詢point的值為100

READ COMMITTED(讀已提交)

大多數資料庫系統預設的隔離級別都是READ COMMITTED(但MySQL不是),"讀已提交"簡單的定義:一個事務只能看見已經提交的事務的修改結果。換句話說,一個事務從開啟事務到提交事務之前,對其他事務都是不可見的,因此在同一個事務中的兩次相同查詢結果可能不一樣。故這種隔離級別有時候也叫不可重複讀(NONREPEATABLE READ)。

  • READ COMMITTED 實踐

    READ COMMITTED PNG

    READ COMMMITED GIF
    以上的一段GIF圖顯示了"讀已提交"隔離級別下的實踐。我們將隔離級別設定為READ COMMITTED,從執行過程我們可以看到:

    • 第一次SESSION A查詢point為90
    • SESSION B更新point為100
    • 第二次SESSION A查詢point為90
    • SESSION B提交事務
    • 第三次SESSION A查詢point為100

    從實踐中我們可以看到當SESSION B的事務提交後,SESSION A就能讀取到SESSION B修改的資料。

REPEATABLE READ(可重複讀)

"可重複讀"是MySQL的預設事務隔離級別。REPEATABLE READ解決了髒讀的問題,該級別保證了在同一次事務中多次查詢相同的語句結果是一致的。但是"可重複讀"隔離級別無法避免產生幻行(Phantom Row)的問題,MySQL的InnoDB引擎通過多版本併發控制(MVCC,Multiversion Concurrency Controller)解決了幻讀的問題。

  • REPEATABLE READ 產生幻行的實踐

    Phantom_rows
    從上面GIF圖顯示的過程我們可以看到,最後SESSION A查詢語句的結果只有一條id為1的資料,但是我們在插入id=2的資料的時候產生了報錯

    ERROR 1062 (23000): Duplicate entry '2' for key 'PRIMARY'
    複製程式碼

    從報錯中很容易就能看到是因為id=2的行已經存在了,前面讀取行資料的結果就是幻讀。

  • REPEATABLE READ 實踐

    REPEATABLE READ PNG

    REPEATABLE READ GIF
    上面的圖片顯示了在"可重複讀"隔離界別下的實踐,我們將隔離界別設定為REPEATABLE READ,我們可以看到:

    • 第一次SESSION A查詢point為90
    • SESSION B更新point為100
    • 第二次SESSION A查詢point為90
    • SESSION B提交事務
    • 第三次SESSION A查詢point為90

    我們從實踐中可以看到無論SESSION B怎麼改變,SESSION A在事務開啟後同一查詢語句查詢的結果都是一致的。

SERIALIZABLE(可序列化)

SERIALIZABLE是最高的隔離級別,它通常通過強制事務序列,避免了前面說的幻讀問題。簡單來說,"可序列化"會在讀取的每一行資料上都加鎖,所以可能會導致大量的鎖等待和超時問題,所以在實際的生產環境中也很少會用到這個隔離級別,只有在非常需要確保資料的一致性切可以接受沒有併發的情況下,才會考慮使用這個隔離級別。

  • SERIALIZABLE實踐

    REPEATABLE READ PNG

    SERIALIZABLE GIF
    上面的GIF顯示了在"可序列化"隔離級別下的實踐。我們將隔離級別設定為SERVILAZABLE,從執行的過程中我們可以看到:

    • 第一次SESSION A查詢結果只有id=1
    • SESSION B 插入id=2的資料,因為SESSION A的事務還未提交,此時鎖等待。
    • 第二次SESSION A查詢結果仍然是隻有一行id=1
    • SESSION A提交事務,在提交事務的瞬間SESSION A釋放鎖,SESSION B鎖等待結束
    • SESSION B提交事務
    • 第三次SEESION A查詢結果出現了id=1和id=2這兩條記錄

從上面的過程我們可以看到,"可序列化"是通過對每一行資料都加鎖的方式來避免幻行問題,這種方式效率非常的低,很容易造成較長時間的鎖等待。

總結

這篇文章介紹了事務以及四種隔離級別,隔離級別越高對於鎖的要求就越高,效能就會相對越差。每一種事務的隔離級別都有對應的應用場景,這些隔離級別沒有好壞之分,只是對應不同的業務場景可能需要用到不同的隔離級別。突然想到最近網上很流行的一句話:小孩子才分對錯,成年人只談利弊

相關文章