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)模式隔離級別,很大程度上提高數據庫的讀寫並行度,提高數據庫的性能。