1. 程式人生 > 實用技巧 >SQL 事務

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