SQL 事務
事務
事務基本概念
-- 從id=1的賬戶給id=2的賬戶轉賬100元
-- 第一步:將id=1的A賬戶餘額減去100
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- 第二步:將id=2的B賬戶餘額加上100
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
以上操作必須同時執行成功,如果有一個失敗,則必須全部失敗。
將兩個操作看成一個整體操作時,可以使用事務。事務可以保證操作全部成功或全部失敗。
資料庫事務的ACID特性
- A - Atomic 原子性,要麼全部執行成功,要麼全部執行失敗
- C - Consistent 一致性,A 賬戶減少了100,B賬戶則必定加上了100
- I - Isolation 隔離性,多個事務併發執行,互相隔離,互不影響
- D - Duration 永續性,事務完成後,發生的修改被資料庫持久化儲存
使用事務
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
對於單條SQL語句,資料庫系統自動將其作為一個事務執行,這種事務被稱為隱式事務。
COMMIT
是指提交事務,即試圖把事務內的所有SQL所做的修改永久儲存。如果COMMIT
有些時候,我們希望主動讓事務失敗,這時,可以用ROLLBACK
回滾事務,整個事務會失敗:
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
ROLLBACK;
隔離級別
SQL 中的隔離級別
隔離級別(Isolation Level) | 髒讀(Dirty Read) | 不可重複讀(Non Repeatable Read) | 幻讀(Phantom Read) |
---|---|---|---|
Read Uncommitted | Yes | Yes | Yes |
Read Committed | - | Yes | Yes |
Repeatable Read | - | - | Yes |
Serializable | - | - | - |
下面使用例子演示,建立一個數據庫表,插入一條資料,結果如下:
mysql> select * from students;
+----+-------+
| id | name |
+----+-------+
| 1 | Alice |
+----+-------+
1 row in set (0.00 sec)
Read Uncommitted
讀未提交,會產生:髒讀,不可重複讀,幻讀
分別開啟兩個MySQL客戶端連線,並按時間順序執行下面的事務A和事務B:
時刻 | 事務A | 事務B | 說明 |
---|---|---|---|
1 | SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; | SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; | 設定事務的級別為 Read Uncommitted |
2 | BEGIN; | BEGIN; | 開啟事務 |
3 | UPDATE students SET name = 'Bob' WHERE id = 1; | 先在事務A中將id為1的student的name改為Bob | |
4 | SELECT * FROM students WHERE id = 1; | 事務B查出 id為1的student的name為Bob | |
5 | ROLLBACK; | 事務A回滾,Bob回滾為Alice,結束 | |
6 | SELECT * FROM students WHERE id = 1; | 事務B查出 id為1的student的name為Alice | |
7 | COMMIT; | 提交事務,結束 |
上面演示的是出現髒讀的情況:
一個事務會讀取到另一個事務更新後但未提交的結果,如果另一個事務回滾了,則這個事務當前讀取到的資料是髒資料。
Read Committed
讀已提交,不會出現髒讀,在上面的情況中,B事務始終不會讀取到A事務做的修改
但是會產生不可重複讀和幻讀
分別開啟兩個MySQL客戶端連線,並按時間順序執行下面的事務A和事務B:
時刻 | 事務A | 事務B | 說明 |
---|---|---|---|
1 | SET TRANSACTION ISOLATION LEVEL READ COMMITTED; | SET TRANSACTION ISOLATION LEVEL READ COMMITTED; | 設定事務的級別為 Read Committed |
2 | BEGIN; | BEGIN; | 開啟事務 |
3 | SELECT * FROM students WHERE id = 1; | 讀取到的name為Alice | |
4 | UPDATE students SET name = 'Bob' WHERE id = 1; | 修改Alice為Bob | |
5 | COMMIT; | 事務A提交事務,資料被修改了,結束 | |
6 | SELECT * FROM students WHERE id = 1; | 再次查詢,name變為Bob了 | |
7 | COMMIT; | 事務B提交事務,結束 |
上面演示的是出現不可重複讀的情況:
在一個事務內,這個事務還沒結束,如果有另一個事務恰好修改了這個資料並提交了,該事務中,兩次讀取的資料可能不一樣。
Repeatable Read
可重複讀,不會出現髒讀,不可重複讀,在上面的情況中事務B讀取的資料會一直為Alice
但是會出現幻讀的情況
分別開啟兩個MySQL客戶端連線,並按時間順序執行下面的事務A和事務B:
時刻 | 事務A | 事務B | 說明 |
---|---|---|---|
1 | SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; | SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; | 設定事務的級別為 Repeatable Read |
2 | BEGIN; | BEGIN; | 開啟事務 |
3 | SELECT * FROM students WHERE id = 99; | 事務B查詢id為99的資料,不存在 | |
4 | INSERT INTO students (id, name) VALUES (99, 'Bob'); | 事務A插入一條id為99的資料 | |
5 | COMMIT; | 事務A提交,結束 | |
6 | SELECT * FROM students WHERE id = 99; | 事務B查詢id為99的資料,查不到資料 | |
7 | UPDATE students SET name = 'Alice' WHERE id = 99; | 更新id為99的name為Alice,可以更新成功 | |
8 | SELECT * FROM students WHERE id = 99; | 查詢id為id為99的資料,可以查到資料,name為Alice | |
9 | COMMIT; | 提交事務,結束 |
上面演示的是出現幻讀的情況:
在一個事務中,第一次查詢某條記錄,發現沒用,但是試圖去更新該條不存在的資料,居然可以更新成功,並且再一次讀取同一條記錄,竟然又出現了,就像出現了幻覺一樣。
Serializable
Serializable是最嚴格的隔離級別。在Serializable隔離級別下,所有事務按照次序依次執行,因此,髒讀、不可重複讀、幻讀都不會出現。
雖然Serializable隔離級別下的事務具有最高的安全性,但是,由於事務是序列執行,所以效率會大大下降,應用程式的效能會急劇降低。如果沒有特別重要的情景,一般都不會使用Serializable隔離級別。
預設隔離級別
如果沒有指定隔離級別,資料庫就會使用預設的隔離級別。
在MySQL中,如果使用InnoDB,預設的隔離級別是Repeatable Read。
參考:https://www.liaoxuefeng.com/wiki/1177760294764384/1179611198786848