MySQL的臟讀、幻讀和不可重復讀是數據庫事務處理中的三種常見問題,它們都涉及到數據的一致性和并發性。
一,臟讀
臟讀是指一個事務讀取了另一個事務未提交的數據。這可能導致數據不一致的問題。
例如:
用戶user1的初始balance是100,事務A減少了他的余額50,并進行其余的操作,但還未提交
同時
事務B正在讀取user1的數據,讀到他的balance為50
但是隨后
可能其余的操作發生了某種錯誤,事務A回滾了,user1的余額現在還是100
那么事務B讀到的user1的balance=50就是個臟讀數據
-- 事務A
START TRANSACTION;
SELECT * FROM users WHERE id = 1; -- 假設此時用戶1的balance數據為100
UPDATE users SET balance = balance - 50 WHERE id = 1;
********* 其余操作
COMMIT;-- 事務B
START TRANSACTION;
SELECT * FROM users WHERE id = 1; -- 此時用戶1的balance數據為50,因為事務A已經修改了數據
COMMIT;
解決方案:
使用事務的隔離級別來避免臟讀。MySQL提供了四種隔離級別:
READ UNCOMMITTED
READ COMMITTED
REPEATABLE READ
SERIALIZABLE
其中,READ UNCOMMITTED是最低的隔離級別,它允許臟讀;而SERIALIZABLE是最高的隔離級別,它可以避免臟讀、不可重復讀和幻讀。在創建事務時,可以通過以下命令設置隔離級別:
-- 設置隔離級別為READ UNCOMMITTED
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
二,幻讀
幻讀是指一個事務在多次查詢中返回了不一致的結果。例如,假設有兩個事務C和D,C首先按照某個范圍條件(如id>10 and id<20)查詢了表中的數據,然后D在這個范圍內插入了新的數據。當C再次查詢這個范圍時,它可能會發現多了一些新插入的數據。這就是幻讀。
解決方案:
使用事務的隔離級別來避免幻讀。與臟讀類似,通過設置合適的隔離級別可以解決幻讀問題。此外,還可以使用行級鎖或表級鎖來限制查詢的范圍,從而避免幻讀的發生。
三,不可重復讀
不可重復讀是指在一個事務內,多次讀取同一數據返回的結果不一致。這通常發生在一個事務內先進行了一次查詢操作,然后又對該數據進行了更新操作,而另一個事務在此期間也對該數據進行了更新操作。當第一個事務再次讀取該數據時,它讀取到的是更新后的值,而不是初始值。這就是不可重復讀。
例如:
事務A中讀取user1的balance是100
同時
事務B更新的user1的balance是50,并提交成功了事務
然后
事務A又來讀取user1的balance,結果是50,兩次讀取結果就不一致,導致了不可重復讀。
解決方案:同樣可以使用事務的隔離級別和行級鎖來避免不可重復讀。另外,MySQL還提供了一個特殊的鎖——可重復讀鎖(Repeatable Read),它可以避免不可重復讀的問題。要使用可重復讀鎖,可以在查詢語句前加上FOR REPLICATE READ關鍵字
四,隔離級別
不同的隔離級別對并發問題的解決情況:
隔離級別 臟讀 幻讀 不可重復讀 第一類丟失更新 第二類丟失更新
READ UNCOMMITED(讀未提交) 允許 允許 允許 不允許 允許
READ COMMITTED(讀已提交) 不允許 允許 允許 不允許 允許
REPEATABLE READ(可重復讀) 不允許 允許 不允許 不允許 不允許
SERIALIZABLE (串行化) 不允許 不允許 不允許 不允許 不允許
隔離級別 | 臟讀 | 幻讀 | 不可重復讀 |
---|---|---|---|
READ UNCOMMITED(讀未提交) | 允許 | 允許 | 允許 |
READ COMMITTED(讀已提交 | 不允許 | 允許 | 允許 |
REPEATABLE READ(可重復讀) | 不允許 | 允許 | 不允許 |
SERIALIZABLE (串行化) | 不允許 | 不允許 | 不允許 |
注意:事務的隔離級別和數據庫并發性是成反比的,隔離級別越高,并發性越低。
在MySQL的InnoDB存儲引擎中,REPEATABLE READ(RR)隔離級別通過多版本并發控制(MVCC)和一致性快照來實現非鎖定讀取,并防止幻讀。
五,設置隔離級別
- 查看當前的用戶隔離級別
select @@tx_isolation;
- 修改當前登錄用戶的隔離級別
set session transaction isolation level read uncommitted;
- 修改全局的隔離級別需要使用root登錄執行
set global transaction isolation level read uncommitted;
或者修改mysql.ini配置文件,在最后加上
[mysqld]
transaction-isolation = REPEATABLE-READ
其中可以選擇隔離級別有:READ-UNCOMMITTED、READ-COMMITTED、 REPEATABLE-READ、SERIALIZABLE