意思是讀取到了事務正在修改的數據,若是事務回滾,那麼拿到的數據就是錯誤的sql
時間 | 事務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 | 提交事務 |
在同一個事務內,兩次讀取同一個數據產生不一致session
時間 | 事務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 |
能夠看到,回滾的事務把正常事務的數據覆蓋了,正常事務的數據丟失了code
第二種狀況:blog
時間 | 事務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,能夠經過如下命令查看it
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自增的版本字段,查詢的時候拿到版本字段和庫存,當須要去更新的時候,若是版本不一致,那麼須要從新查詢,重複上述步驟,知道拿到的版本和數據庫中的版本一致時,才進行更新,這樣就不須要等待,效率更高