1. 程式人生 > >理解MySQL資料庫事務-隔離性

理解MySQL資料庫事務-隔離性

Transaction事務是指一個邏輯單元,執行一系列操作的SQL語句。

事務中一組的SQL語句,要麼全部執行,要麼全部回退。在Oracle資料庫中有個名字,叫做transaction ID

在關係型資料庫中,事務必須ACID的特性。

  • 原子性,事務中的操作,要不全部執行,要不都不執行
  • 一致性,事務完成前後,資料的必須保持一致。
  • 隔離性,多個使用者併發訪問資料庫時,每一個使用者開啟的事務,相互隔離,不被其他事務的操作所幹擾。
  • 永續性,事務一旦commit,它對資料庫的改變是永續性的。

目前重點討論隔離性。資料庫一共有四個隔離級別

  • 未提交讀(RU,Read Uncommitted)。它能讀到一個事物的中間狀態,不符合業務中安全性的保證,違背 了ACID特性,存在髒讀的問題,基本不會用到,可以忽略

  • 提交讀(RC,Read Committed)。顧名思義,事務提交之後,那麼我們可以看到。這是一種最普遍的適用的事務級別。我們生產環境常用的使用級別。

  • 可重複讀(RR,Repeatable Read)。是目前被使用得最多的一種級別。其特點是有GAP鎖,目前還是預設級別,這個級別下會經常發生死鎖,低併發等問題。

  • 可序列化,這種實現方式,其實已經是不是多版本了,而是單版本的狀態,因為它所有的實現都是通過鎖來實現的。

因此目前資料庫主流常用的是RCRR隔離級別。

隔離性的實現方式,我們通常用Read View表示一個事務的可見性。

RC級別,事務可見性比較高,它可以看到已提交的事務的所有修改。因此在提交讀(RC,Read Committed)隔離級別下,每一次select語句,都會獲取一次Read View,得到資料庫最新的事務提交狀態。因此對於資料庫,併發效能也最好。

RR級別,則不是。它為了避免幻讀和不可重複讀。保證在一個事務內前後資料讀取的一致。其可見性檢視Read View只有在自己當前事務提交之後,才會更新。

那如何保證資料的一致性?其核心是通過redo log和undo log來保證的。

而在資料庫中,為了實現這種高併發訪問,就需要對資料庫進行多版本控制,通過事務的可見性來保證事務看到自己想看到的那個資料版本(或者是最新的Read View亦或者是老的Read View)。這種技術叫做MVCC

多版本是如何實現的?通過undo日誌來保證。每一次資料庫的修改,undo日誌會儲存之前的修改記錄值。如果事務未提交,會回滾至老版本的資料。其MVCC的核心原理,以後詳談

舉例論證:

##  開啟事務
MariaDB [scott]> begin;                   
Query OK, 0 rows affected (0.000 sec)

##檢視當前的資料
MariaDB [scott]>  select * from dept;
+--------+------------+----------+
| deptno | dname      | loc      |
+--------+------------+----------+
|     10 | ACCOUNTING | beijing  |
|     20 | RESEARCH   | DALLAS   |
|     30 | SALES      | CHICAGO  |
|     40 | OPERATIONS | beijing  |
|     50 | security   | beijing  |
|     60 | security   | nanchang |
+--------+------------+----------+
6 rows in set (0.001 sec)

##更新資料
MariaDB [scott]> update dept set loc ='beijing' where deptno = 20;
Query OK, 1 row affected (0.001 sec)

## 其行記錄| 20 | RESEARCH | DALLAS |已經被放置在undo日誌中,目前最新的記錄被改為'beijing':
MariaDB [scott]> select * from dept;
+--------+------------+----------+
| deptno | dname      | loc      |
+--------+------------+----------+
|     10 | ACCOUNTING | beijing  |
|     20 | RESEARCH   | beijing  |
|     30 | SALES      | CHICAGO  |
|     40 | OPERATIONS | beijing  |
|     50 | security   | beijing  |
|     60 | security   | nanchang |
+--------+------------+----------+

##事務不提交,回滾。資料回滾至老版本的資料。
MariaDB [scott]> rollback;
Query OK, 0 rows affected (0.004 sec)

MariaDB [scott]> select * from dept;
+--------+------------+----------+
| deptno | dname      | loc      |
+--------+------------+----------+
|     10 | ACCOUNTING | beijing  |
|     20 | RESEARCH   | DALLAS   |
|     30 | SALES      | CHICAGO  |
|     40 | OPERATIONS | beijing  |
|     50 | security   | beijing  |
|     60 | security   | nanchang |
+--------+------------+----------+
6 rows in set (0.000 sec)

因為MVCC,讓資料庫有了很強的併發能力。隨著資料庫併發事務處理能力大大增強,從而提高了資料庫系統的事務吞吐量,可以支援更多的使用者併發訪問。但併發訪問,會出現帶來一系列問題。如下:

資料庫併發帶來的問題 概述解釋
髒讀(Dirty Reads) 當一個事務A正在訪問資料,並且對資料進行了修改,而這種修改還沒有提交到資料庫中,這時,另外一個事務B也訪問這同一個資料,如不控制,事務B會讀取這些"髒"資料,並可能做進一步的處理。這種現象被稱為"髒讀"(Dirty Reads)
不可重複讀(Non-Repeatable Reads) 指在一個事務A內,多次讀同一資料。在這個事務還沒有結束時,另外一個事務B也訪問該同一資料。那麼,在事務A的兩次讀資料之間,由於第二個事務B的修改,那麼第一個事務兩次讀到的的資料可能是不一樣的 。出現了"不可重複讀"(Non-Repeatable Reads)的現象
幻讀(Phantom Reads) 指在一個事務A內,按相同的查詢條件重新檢索以前檢索過的資料,同時發現有其他事務插入了資料,其插入的資料滿足事務A的查詢條件。因此查詢出了新的資料,這種現象就稱為"幻讀"(Phantom Reads)

隔離級別和上述現象之間的聯絡。

隔離級別有:未提交讀(RU,Read Uncommitted),提交讀(RC,Read Committed),可重複讀(RR,Repeatable Read),可序列化(Serializable)

隔離級別 髒讀 不可重複讀 幻讀
未提交讀(RU,Read Uncommitted) 可能 可能 可能
提交讀(RC,Read Committed) 不可能 可能 可能
可重複讀(RR,Repeatable Read) 不可能 不可能 可能
(間隙鎖解決)
可序列化(Serializable) 不可能 不可能 不可能

實驗環節

舉例在隔離級別RRRC下,說明“不可重複讀”問題。

MySQL的預設級別是Repeatable Read,如下:

MariaDB [(none)]> select @@global.tx_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| REPEATABLE-READ       |
+-----------------------+
1 row in set (0.000 sec)

這裡修改當前會話級別為Read Committed

MariaDB [scott]> set session transaction isolation level read committed;
Query OK, 0 rows affected (0.001 sec)

MariaDB [scott]> select @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| READ-COMMITTED |
+----------------+
1 row in set (0.000 sec)

在隔離級別已提交讀(RC,Read Committed)下,出現了不可重複讀的現象。在事務A中可以讀取事務B中的資料。

在隔離級別可重複讀(RR,Repeatable Read),不會出現不可重複讀現象,舉例如下:

舉例說明“幻讀”的現象。

行鎖可以防止不同事務版本的資料在修改(update)提交時造成資料衝突的問題。但是插入資料如何避免呢?

在RC隔離級別下,其他事務的插入資料,會出現幻讀(Phantom Reads)的現象。

而在RR隔離級別下,會通過Gap鎖,鎖住其他事務的insert操作,避免"幻讀"的發生。

因此,在MySQL事務中,鎖的實現方式與隔離級別有關,如上述實驗所示。在RR隔離級別下,MySQL為了解決幻讀的問題,已犧牲並行度為代價,通過Gap鎖來防止資料的寫入。這種鎖,並行度差,衝突多。容易引發死鎖。

目前流行的Row模式可以避免很多衝突和死鎖問題,因此建議資料庫使用ROW+RC(Read Committed)模式隔離級別,很大程度上提高資料庫的讀寫並行度,提高資料庫的效能