前言
我們在上一章節中介紹過數據庫的帶你了解數據庫中事務的ACID特性 的相關用法。本章節主要來介紹下數據庫中一個非常重要的知識點事務的隔離級別
。如有錯誤還請大家及時指出~
問題:
- 事務的隔離級別有哪些?
- 如果並發事務沒有進行隔離,會出現什么問題?
以下都是采用mysql數據庫
在多個事務並發做數據庫操作的時候,如果沒有有效的避免機制,就會出現種種問題。大體上有以下問題:
一、引發的問題
在並發事務沒有進行隔離的情況下,會發生如下問題。
問題一:臟讀
臟讀
指一個事務讀取了另外一個事務未提交的數據。
具體看后文案例介紹
問題二:不可重復讀
不可重復讀
指在一個事務內讀取表中的某一行數據,多次讀取結果不同。
不可重復讀和臟讀的區別是,臟讀是讀取前一事務未提交的臟數據,不可重復讀是重新讀取了前一事務已提交的數據。
具體看后文案例介紹
問題三:幻讀(虛讀)
幻讀(虛讀)
指在一個事務內讀取到了別的事務插入的數據,導致前后讀取不一致。
具體看后文案例介紹
二、概念
2.1 事務的隔離級別分為:
- Read uncommitted(讀未提交)
- Read Committed(讀已提交)
- Repeatable Reads(可重復讀)
- Serializable(串行化)
Read uncommitted
讀未提交
:隔離級別最低的一種事務級別。在這種隔離級別下,會引發臟讀、不可重復讀和幻讀。
Read Committed
讀已提交
讀到的都是別人提交后的值。這種隔離級別下,會引發不可重復讀和幻讀,但避免了臟讀。
Repeatable Reads
可重復讀
這種隔離級別下,會引發幻讀,但避免了臟讀、不可重復讀。
Serializable
串行化
是最嚴格的隔離級別。在Serializable隔離級別下,所有事務按照次序依次執行。臟讀、不可重復讀、幻讀都不會出現。
三、操作
3.1 查看事務隔離級別
SHOW VARIABLES LIKE 'tx_isolation';
查看全局的事務隔離級別
SHOW GLOBAL VARIABLES LIKE 'tx_isolation';
使用系統變量查詢
SELECT @@global.tx_isolation;
SELECT @@session.tx_isolation;
SELECT @@tx_isolation;
3.2 設置MysQL的事務隔離級別
語法
SET [GLOBAL | SESSION] TRANSACTION ISOLATION LEVEL
{
REPEATABLE READ
| READ COMMITTED
| READ UNCOMMITTED
| SERIALIZABLE
}
GLOBAL
:設置全局的事務隔離級別
SESSION
:設置當前session的事務隔離級別,如果語句沒有指定GLOBAL或SESSION,默認值為SESSION
使用系統變量設置事務隔離級別
SET GLOBAL tx_isolation='REPEATABLE-READ';
SET SESSION tx_isolation='SERIALIZABLE';
四、案例分析
下面實際操作中使用到的一些並發控制語句,可看上面的操作介紹
作為演示:product表
productId | productName | productPrice | productCount |
---|---|---|---|
1 | xiaomi | 1999 | 100 |
帶着上面的我們來看一下,事務在沒有隔離性的情況下,會引發哪些問題?
同時打開兩個窗口模擬2個用戶並發訪問數據庫
4.1 事務隔離級別設置為read uncommitted
查詢事務隔離級別
SELECT @@tx_isolation;
設置隔離級別為未提交讀:
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
注意:需要同時修改兩個窗口的事務隔離級別
以下我們以兩位用戶搶小米手機為例
時間軸 | 事務A | 事務B |
---|---|---|
T1 | start transaction; | |
T2 | select p.productName,p.productCount from product p where p.productId=1;(productCount =100) | |
T3 | start transaction; | |
T4 | select p.productName,p.productCount from product p where p.productId=1;(productCount =100) | |
T5 | update product set productCount = 99 where productId = 1; | |
T6 | select p.productName,p.productCount from product p where p.productId=1;(productCount =99) | |
T7 | ROLLBACK; | |
T8 | select p.productName,p.productCount from product p where p.productId=1;(productCount =100) |
T1—— A用戶開啟事務,start transaction;
T2—— A用戶查詢當前小米手機剩余數量,select p.productName,p.productCount from product p where p.productId=1;此時數量顯示為100。
T3——B用戶開啟事務,start transaction;
T4——B用戶查詢當前小米手機剩余數量,select p.productName,p.productCount from product p where p.productId=1;此時數量顯示為100。
T5—— B用戶購買了一台小米手機,update product set productCount = 99 where productId = 1; 此時只修改數據並未提交事務。
T6—— A用戶刷新頁面,select p.productName,p.productCount from product p where p.productId=1;此時數量顯示為99。
T7—— B用戶購買失敗,回滾事務。
T8—— A用戶查詢當前小米手機剩余數量,select p.productName,p.productCount from product p where p.productId=1;此時數量顯示為100。
小結:
事務A讀取了未提交的數據,事務B的回滾,導致了事務A的數據不一致,導致了事務A的臟讀
!
4.2 事務隔離級別設置為Read Committed
查詢事務隔離級別
SELECT @@tx_isolation;
更改數據庫隔離級別,設置隔離級別為提交讀:
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
注意:需要同時修改兩個窗口的事務隔離級別
時間軸 | 事務A | 事務B |
---|---|---|
T1 | start transaction; | |
T2 | select p.productName,p.productCount from product p where p.productId=1;(productCount =100) | |
T3 | start transaction; | |
T4 | select p.productName,p.productCount from product p where p.productId=1;(productCount =100) | |
T5 | update product set productCount = 99 where productId = 1; | |
T7 | select p.productName,p.productCount from product p where p.productId=1;(productCount =100) | |
T6 | commit; | |
T8 | select p.productName,p.productCount from product p where p.productId=1;(productCount =99) |
這里就不再對流程做過多贅述。
小結:
可以看到避免了臟讀
現象,但是卻出現了,一個事務還沒有結束,就發生了不可重復讀問題,即事務A來說 productCount從 100->100->99。但這個過程中事務並未提交結束。
4.3 事務隔離級別設置為Repeatable Read(mysql默認級別)
查詢事務隔離級別
SELECT @@tx_isolation;
更改數據庫隔離級別,設置隔離級別為可重復讀:
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
注意:需要同時修改兩個窗口的事務隔離級別
時間軸 | 事務A | 事務B |
---|---|---|
T1 | start transaction; | |
T2 | select p.productName,p.productCount from product p where p.productId=1;(productCount =100) | |
T3 | start transaction; | |
T4 | select p.productName,p.productCount from product p where p.productId=1;(productCount =100) | |
T5 | update product set productCount = 99 where productId = 1; | |
T7 | select p.productName,p.productCount from product p where p.productId=1;(productCount =100) | |
T6 | commit; | |
T8 | select p.productName,p.productCount from product p where p.productId=1;(productCount =100) |
這里就不再對流程做過多贅述。
小結:
可以看到可重復讀
隔離級別避免了臟讀
,不可重復讀
的問題,但是出現了幻讀
現象。事務A查詢到的小米數量等於100,但是事務B修改了數量為99,但是事務A讀取到的值還是100。當事務A去減1等於99時,是錯誤的,此時應該是99-1=98才對。接下來我們再提高一個事務隔離級別。
4.4 事務隔離級別設置為Serializable
查詢事務隔離級別
SELECT @@tx_isolation;
更改數據庫隔離級別,設置隔離級別為串行化:
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
時間軸 | 事務A | 事務B |
---|---|---|
--- | --- | --- |
T1 | start transaction; | |
T2 | start transaction; | |
T2 | select p.productName,p.productCount from product p where p.productId=1;(productCount =100); | |
T4 | update product set productCount = 99 where productId = 1;(等待中..) |
這里就不再對流程做過多贅述。
小結:
在我們Serializable隔離級別中,我們可以看到事務B去做修改動作時卡主了,不能向下執行。這是因為:給事務A的select操作上了鎖,所以事務B去修改值的話,就會被卡主。只有當事務A操作執行完畢,才會執行事務B的操作。這樣就避免了上述三個問題了。
問題本身
-
回到問題的本身,其實我們並不需要將事務提到這么高。
-
問題的本身就是,當我們讀完了的時候,就要在上面加鎖。我們不希望別人能夠去讀它。因為別人讀到了count,就會修改count的值,並寫進去。所以我們在select 操作的時候,加上for update。這時候就會把這行操作給鎖掉了。那么另外一個人也進行相同的操作,也表示select 出來的count需要進行update,需要鎖住。
select p.productName,p.productCount from product p where p.productId=1 for update;
PS: 在實際開發過程中,這樣的加鎖行為,是非常的耗系統性能的。下一章節我們將來介紹
悲觀鎖與樂觀鎖
文末
本章節主要介紹了數據庫中事務的ADID特性中的
隔離性
,在沒有隔離的情況下會發生什么問題,相信大家通過本章,對數據庫事務中的隔離性
有了一定的了解,下篇文章我們將介紹數據庫中的悲觀鎖與樂觀鎖
。
歡迎關注個人微信公眾號:Coder編程
獲取最新原創技術文章和免費學習資料,更有大量精品思維導圖、面試資料、PMP備考資料等你來領,方便你隨時隨地學習技術知識!
新建了一個qq群:315211365,歡迎大家進群交流一起學習。謝謝了!也可以介紹給身邊有需要的朋友。
文章收錄至
Github: https://github.com/CoderMerlin/coder-programming
Gitee: https://gitee.com/573059382/coder-programming
歡迎關注並star~