理解MySQL資料庫事務-隔離性
Transaction事務是指一個邏輯單元,執行一系列操作的SQL語句。
事務中一組的SQL語句,要麼全部執行,要麼全部回退。在Oracle資料庫中有個名字,叫做transaction ID
在關係型資料庫中,事務必須ACID的特性。
- 原子性,事務中的操作,要不全部執行,要不都不執行
- 一致性,事務完成前後,資料的必須保持一致。
- 隔離性,多個使用者併發訪問資料庫時,每一個使用者開啟的事務,相互隔離,不被其他事務的操作所幹擾。
- 永續性,事務一旦commit,它對資料庫的改變是永續性的。
目前重點討論隔離性。資料庫一共有四個隔離級別
未提交讀(RU,Read Uncommitted)。它能讀到一個事物的中間狀態,不符合業務中安全性的保證,違背 了ACID特性,存在髒讀的問題,基本不會用到,可以忽略
提交讀(RC,Read Committed)。顧名思義,事務提交之後,那麼我們可以看到。這是一種最普遍的適用的事務級別。我們生產環境常用的使用級別。
可重複讀(RR,Repeatable Read)。是目前被使用得最多的一種級別。其特點是有GAP鎖,目前還是預設級別,這個級別下會經常發生死鎖,低併發等問題。
可序列化,這種實現方式,其實已經是不是多版本了,而是單版本的狀態,因為它所有的實現都是通過鎖來實現的。
因此目前資料庫主流常用的是RC
和RR
隔離級別。
隔離性的實現方式,我們通常用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) | 不可能 | 不可能 | 不可能 |
實驗環節
舉例在隔離級別RR
和RC
下,說明“不可重複讀”問題。
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)模式隔離級別,很大程度上提高資料庫的讀寫並行度,提高資料庫的效能