資料庫隔離級別 如果沒有隔離級別會出現的問題 臟讀 意思是讀取到了事務正在修改的數據,如果事務回滾,那麼拿到的數據就是錯誤的 | 時間 | 事務A | 事務B | | | | | | 1 | 開始事務 | | | 2 | 讀取quantity為5 | | | 3 | 修改quantity為4 | | ...
資料庫隔離級別
如果沒有隔離級別會出現的問題
臟讀
意思是讀取到了事務正在修改的數據,如果事務回滾,那麼拿到的數據就是錯誤的
時間 | 事務A | 事務B |
---|---|---|
1 | 開始事務 | |
2 | 讀取quantity為5 | |
3 | 修改quantity為4 | |
4 | 開始事務 | |
5 | 讀取到quantity為4 | |
6 | 發生錯誤,回滾,quantity為5 | |
7 | 提交事務 |
在按照正常邏輯quantity應該為5
不可重覆讀
時間 | 事務A | 事務B |
---|---|---|
1 | 開始事務 | |
2 | 讀取quantity為5 | |
3 | 開始事務 | |
4 | 修改quantity為4 | |
5 | 提交事務 | |
6 | 讀取quantity為4 | |
7 | 提交事務 |
在同一個事務內,兩次讀取同一個數據產生不一致
幻讀
時間 | 事務A | 事務B |
---|---|---|
1 | 開始事務 | |
2 | 更新所有行的quantity為100 | |
3 | 開始事務 | |
4 | 插入一行quantity為5 | |
5 | 提交事務 | |
6 | 查詢所有行的quantity | |
7 | 提交事務 |
當一個事務內更新所有行後,另一個事務插入了新行,當再次查看記錄時,發現有未更新的記錄,好像幻覺一樣
丟失更新
第一種情況:
時間 | 事務A | 事務B |
---|---|---|
1 | 開始事務 | |
2 | 查詢到quantity為10 | |
3 | 開始事務 | |
4 | 查詢到quantity為10 | |
5 | 更新quantity為11 | |
6 | 提交事務 | |
7 | 更新quantity為9 | |
8 | 事務回滾,quantity為10 |
可以看到,回滾的事務把正常事務的數據覆蓋了,正常事務的數據丟失了
第二種情況:
時間 | 事務A | 事務B |
---|---|---|
1 | 開始事務 | |
2 | 查詢到quantity為10 | |
3 | 開始事務 | |
4 | 查詢到quantity為10 | |
5 | 更新quantity為9 | |
6 | 提交事務 | |
7 | 更新quantity為11 | |
8 | 提交事務 |
這種情況是事務在執行期間,其他事務對數據進行了修改,那麼當前事務拿到的數據就是錯的,對錯的數據進行更新,那也就沒有意義了
解決方法
對於臟讀、不可重覆讀、幻讀
我們可以使用資料庫提供的隔離級別來避免以上情況
隔離級別 | 臟讀 | 不可重覆讀 | 幻讀 |
---|---|---|---|
Read-Uncommitted(讀取未提交的內容) | √ | √ | √ |
Read-Committed(讀取已提交的內容) | × | √ | √ |
Repeatable-Read(可重讀) | × | × | √ |
Serializable(串列化) | × | × | × |
Mysql的預設隔離級別為Repeatable-Read,可以通過以下命令查看
SELECT @@global.tx_isolation;--查看全局隔離級別
SELECT @@session.tx_isolation;--查看當前連接的隔離級別
修改隔離級別
SET @@global.tx_isolation='Read-Committed'
SET @@session.tx_isolation='Read-Committed'
--或
SET GLOBAL TRANSACTION ISOLATION LEVEL SERIALIZABLE
對於丟失更新
使用悲觀鎖
悲觀鎖主要有共用鎖(讀鎖)和排他鎖(寫鎖)
- 共用鎖是指多個事務可以共用一個一把鎖,都可以讀取到數據,但是不能修改
- 排他鎖就是一個事務獲得了排他鎖,那麼其他事務就不能獲得鎖(包括共用鎖和排他鎖),獲取排他鎖的事務可以對數據進行訪問和修改
Mysql預設開啟了自動事務提交,可以使用以下命令關閉
SET autocommit=0 --關閉自動事務提交
這裡必須要強調一下鎖的概念,不管是共用鎖還是排他鎖,都是我們給每一個數據元素加的,如果一個數據元素已經有了排他鎖,那麼久不能再給它加任何鎖,如果一個數據元素有共用鎖,那麼還可以給它加共用鎖,Mysql的InnoDB引擎預設給insert、update、delete都加了排他鎖,而select未加任何鎖
新建一個查詢視窗,開始事務,但是沒有提交,因為update預設給數據元素加排他鎖,所以這個時候我們去更新該數據元素就會出現
上一個事務還沒有提交,數據元素還有排他鎖,這個update語句要給數據元素加排他鎖,所以只有等待,這也驗證了update語句預設會給相關的數據元素加排他鎖
如果使用select語句加共用鎖進行查詢一樣會阻塞
但是使用select語句不加任何鎖是可以查出數據的,但是數據是更新之前的
所以,使用悲觀鎖在高併發情況下,對於減庫存這樣的操作,首先要使用排他鎖的select語句拿到庫存,如果已經有事務對這個數據元素上了鎖,那麼只有等待該事務釋放鎖,只有這樣拿到的庫存才是正確的
BEGIN;
DECLARE @now_quantity INT;
SELECT quantity INTO @now_quantity FROM item WHERE id=1 FOR UPDATE;//一定要加排他鎖
UPDATE item SET quantity=@now_quantity-1 WHERE id=1;
COMMIT;
而且需要註意,MySQL InnoDB預設行級鎖。行級鎖都是基於索引的,如果一條SQL語句用不到索引是不會使用行級鎖的,會使用表級鎖把整張表鎖住
使用悲觀鎖的方式解決丟失更新很簡單,但是也會帶來效率上的問題,如果一個事務上了鎖,那麼其他的都只有等待
使用樂觀鎖
我們可以給表中加上一個version自增的版本欄位,查詢的時候拿到版本欄位和庫存,當需要去更新的時候,如果版本不一致,那麼需要重新查詢,重覆上述步驟,知道拿到的版本和資料庫中的版本一致時,才進行更新,這樣就不需要等待,效率更高