數據庫併發問題
髒讀
事務A讀取到了事務B未提交的數據,當事務B回滾後,事務A讀取到的就是髒數據。
不可重複讀
同一事務在事務內多次讀取,讀取到的數據不一致。例:在事務A的第一次讀取和第二次讀取中間,事務B更新或刪除了數據,導致事務A兩次讀取到不同的數據,即不可重複讀。
幻讀
同一事務在事務內多次讀取,讀取到的記錄數不一樣。例:在事務A的第一次讀取和第二次讀取中間,事務B插入了新的記錄(此記錄符合事務A的查詢條件),導致事務A讀取到的記錄數不一樣,即幻讀。
數據庫隔離級別
MySQL數據庫有4種隔離級別,隔離級別依次遞增,相應的系統開銷也依次遞增。
未提交讀:Read uncommitted
mysql> set [global|session] transaction isolation level read uncommitted;
本事務讀取了別的事務未提交的數據,即髒讀。下面以併發的A事務和B事務對銀行賬戶的金額操作進行說明:
A事務 |
B事務 |
查詢餘額 mysql> select money from t_account where id=1234567; +-------+
|
|
開啓事務 mysql>start transaction; |
|
更新餘額 mysql> update t_account set money=10001 where id=1234567; |
開啓事務 mysql> start transaction; |
查詢餘額 mysql> select money from t_account where id=1234567; +-------+ |
|
回滾事務 mysql> rollback; |
其它事務操作 mysql> |
提交事務 mysql> commit; |
從上面表格可以看出,B事務在A事務更新了餘額且未提交事務時,讀取到了髒數據money=10001。
由於可以讀到別的事務未提交的數據,所以同樣會有不可重複讀和幻讀的情況發生,此隔離級別很少用於實際應用中。
已提交讀:Read committed
mysql> set [global|session] transaction isolation level read committed;
本事務只能讀取到別的事務提交後的數據。即事務未提交時,處於中間狀態(不一致狀態)的數據對其它事務是不可見的。下面以併發的A事務和B事務對銀行賬戶的金額操作進行說明:
A事務 |
B事務 |
查詢餘額 mysql> select money from t_account where id=1234567; +-------+
|
|
開啓事務 mysql>start transaction; |
|
更新餘額 mysql> update t_account set money=10001 where id=1234567; |
開啓事務 mysql> start transaction; |
查詢餘額 mysql> select money from t_account where id=1234567; +-------+ |
|
提交事務 mysql> commit; |
|
查詢餘額 mysql> select money from t_account where id=1234567; +-------+ |
|
提交事務 mysql> commit; |
從上面表格可以看出,B事務第一次查詢餘額時,A事務還未提交,所以讀到的數據仍是A事務開始前的值money=10000。當B事務第二次查詢餘額時,此時A事務已提交,B事務讀到的數據亦是A事務更新後的數據,即money=10001。
已提交讀避免了髒讀的情況,但仍然有不可重複讀和幻讀的情況發生。
可重複讀:Repeatable read
mysql> set [global|session] transaction isolation level repeatable read;
對同一數據的多次讀取,結果是一致的,除非數據被自身事務所修改。可重複讀可以阻止髒讀和不可重複讀,但仍然有幻讀的情況發生。
可序列化:Serializable
mysql> set [global|session] transaction isolation level serializable;
最高的隔離級別,完全服從ACID的隔離級別。所有事務依次逐個執行,這樣事務之間就完全不可能產生干擾。該隔離級別可阻止髒讀、不可重讀和幻讀。
以上4種隔離級別和相應的副作用關係如下:
隔離級別 | 髒讀 | 不可重複讀 | 幻讀 |
未提交讀 | ✅ | ✅ | ✅ |
已提交讀 | ❎ | ✅ | ✅ |
可重複讀 | ❎ | ❎ | ✅ |
可序列化 | ❎ | ❎ | ❎ |