事務的四大特性ACID和資料庫的隔離級別
事務的四大特性ACID
資料庫四大屬性原子性(Atomicity),一致性(Consistency),隔離性(Isolation),一致性(Durability)
- 原子性。整個事務中的全部操作要麼全部完成,要麼全部都不完成。如果在事務執行的某個階段發生錯誤,則將資料庫回滾到執行事務之前。
- 一致性。事務必須保證系統處於一致的狀態
- 隔離性。事務之間的隔離級別
- 永續性。在事務完成之後,該事務對資料庫所做的更改便永久儲存到資料庫中
資料庫的隔離級別
當多個事務併發執行時,可能會純在下面幾個問題
-
髒讀:事務A讀到了事務B中尚未提交的資料
-
不可重複讀:事務A中多次讀同一資料,讀到的結果不一樣。一般是因為在事務A執行的過程中存在事務B update了這組資料
-
幻讀:事務A中多次查詢一組資料,查到的資料行數不一樣。一般是因為在事務A執行的過程中存在事務B 插入或者刪除了資料
為了解決這三類問題,資料庫提供四層事務的隔離級別,隔離基本由低到高依次是:
-
讀取未提交(Read Uncommitted),存在髒讀, 不可重複讀, 幻讀的問題
-
讀取已提交(read commit),存在不可重複讀, 幻讀的問題;能夠解決髒讀
-
可重複讀(repeatable),存在幻讀的問題;能夠解決髒讀,不可重複讀
-
序列化(Serializable),能夠解決髒讀,幻讀,不可重複讀
需要注意的是隔離級別越高,執行速度越慢
讀取未提交(READ UNCOMMITTED)
讀取未提交(READ UNCOMMITTED):即使事務B沒有提交,事務A也可以讀到事務B中已經執行的修改。
舉個X向Y轉賬的例子, 大致流程如下
時刻 | 事務A | 事務B | 事務A讀到的餘額 | 事務B讀到的餘額 |
---|---|---|---|---|
T0 | 開啟事務 | 開啟事務 | X: 10000元, Y: 10000元 | X: 10000元, Y: 10000元 |
T1 | X向Y轉賬1000 | X: 9000元, Y: 11000元 | X: 9000元, Y: 11000元 | |
T2 | 提交事務 | X: 9000元, Y: 11000元 | - | |
T3 | 回滾操作 | - | - |
建表語句
DROP TABLE IF EXISTS account;
CREATE TABLE account (
id INT PRIMARY KEY,
name VARCHAR(20) not null ,
balance INT
);
INSERT INTO account VALUES(1, "X", 10000), (2, "Y", 10000);
開啟兩個視窗訪問同一資料庫
把兩個session的事務隔離級別修改為Read Uncommitted
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
在session A中開啟事務
BEGIN;
在session B中開啟事務
BEGIN;
在事務A中模擬X向Y轉賬1000元在事務A中把X的賬戶減去1000元,Y的賬戶增加1000元
UPDATE account SET balance = balance - 1000 where id = 1;
UPDATE account SET balance = balance + 1000 where id = 2;
在事務B中查詢兩個賬戶的餘額, 可以看到事務B中可以查到事務A做出的更改(這就是髒讀),此時Y可能認為轉賬已經到賬了,然後就發貨了
提交事務B
COMMIT;
回滾事務A
ROLLBACK;
在事務B中查詢兩個賬戶的餘額, 可以X和Y的餘額又變成之前的狀態了。Y並沒有收到轉賬,由此可見,髒讀是有危害的。
提交事務B
讀已提交(READ COMMITTED)
讀寫提交(READ COMMITTED): 事務B提交之之後,事務A才能讀到B中已經執行的修改。如果事務B沒有提交,事務A不能讀到B中已經執行的修改
舉個X向Y轉賬的例子, 大致流程如下
時刻 | 事務A | 事務B | 事務A餘額 | 事務B餘額 |
---|---|---|---|---|
T0 | 開啟事務 | 開啟事務 | X: 10000元, Y: 10000元 | X: 10000元, Y: 10000元 |
T1 | X向Y轉賬1000 | X: 9000元, Y: 11000元 | X: 10000元, Y: 10000元 | |
T2 | 提交事務 | - | X: 9000元, Y: 11000元 | |
T3 | 提交事務 | - | - |
開啟兩個視窗訪問同一資料庫
把兩個session的事務隔離級別修改為READ COMMITTED
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
在session A, session B中開啟事務
BEGIN;
在事務A中模擬X向Y轉賬1000元在事務A中把X的賬戶減去1000元,Y的賬戶增加1000元
UPDATE account SET balance = balance - 1000 where id = 1;
UPDATE account SET balance = balance + 1000 where id = 2;
在事務B中查詢兩個賬戶的餘額, 可以看到沒有讀到事務A做的修改,這樣就避免了髒讀的問題。
提交事務A
在事務B中查詢兩個賬戶的餘額, 可以看到在提交事務A之後,事務B讀到了事務A的修改,這就是不可重複讀。
提交事務B
不可重複讀的危害:假設事務B需要先計算X的存款的利息,然後在核算一次,但是由於事務A中X向Y轉賬了。事務B中計算到的兩次利息的值對不上,就會很奇怪
可重複讀(REPEATABLE READ)
可重複讀(REPEATABLE READ): 事務中讀到的資料都是事務開始時的值,其他事務的修改不會影響到該事務。
把賬戶X和Y的餘額從新設定為10000
舉個X向Y轉賬的例子, 大致流程如下
時刻 | 事務A | 事務B | 事務A餘額 | 事務B餘額 |
---|---|---|---|---|
T0 | 開啟事務 | 開啟事務 | X: 10000元, Y: 10000元 | X: 10000元, Y: 10000元 |
T1 | X向Y轉賬1000 | X: 9000元, Y: 11000元 | X: 10000元, Y: 10000元 | |
T2 | 提交事務 | - | X: 10000元, Y: 10000元 | |
T3 | 提交事務 | - | - |
開啟兩個視窗訪問同一資料庫
把兩個session的事務隔離級別修改為REPEATABLE READ
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
在session A, session B中開啟事務
BEGIN;
在事務A中模擬X向Y轉賬1000元在事務A中把X的賬戶減去1000元,Y的賬戶增加1000元
UPDATE account SET balance = balance - 1000 where id = 1;
UPDATE account SET balance = balance + 1000 where id = 2;
在事務B中查詢兩個賬戶的餘額, 可以看到沒有讀到事務A做的修改,這樣就避免了髒讀的問題。
提交事務A
在事務B中查詢兩個賬戶的餘額, 可以看到在事務A提交之後,事務B也沒有讀到事務A做的修改,這樣就避免了不可重複讀的問題。
提交事務B
需要注意的是MYSQL中無法看到幻讀的現象。
序列化(SERIALIZABLE)
序列化(SERIALIZABLE):一旦有事務操作某個表,它就會鎖住這個表,直到提交該事務,其他事務才能修改這張表;
舉個轉賬的例子
把賬戶X和Y的餘額從新設定為10000
開啟兩個視窗訪問同一資料庫
把兩個session的事務隔離級別修改為SERIALIZABLE
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
開啟兩個視窗訪問同一資料庫
把兩個session的事務隔離級別修改為REPEATABLE READ
在事務B中查詢兩個賬戶的餘額
在事務A中模擬X向Y轉賬1000元在事務A中把X的賬戶減去1000元,Y的賬戶增加1000元,發現會卡住
UPDATE account SET balance = balance - 1000 where id = 1;
UPDATE account SET balance = balance + 1000 where id = 2;
提交事務B;
在事務A再次模擬X向Y轉賬1000元在事務A中把X的賬戶減去1000元
提交事務A
再次查詢餘額
UPDATE account SET balance = balance - 1000 where id = 1;
UPDATE account SET balance = balance + 1000 where id = 2;