數據庫隔離級別

數據庫隔離級別

若是沒有隔離級別會出現的問題

髒讀

意思是讀取到了事務正在修改的數據,若是事務回滾,那麼拿到的數據就是錯誤的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自增的版本字段,查詢的時候拿到版本字段和庫存,當須要去更新的時候,若是版本不一致,那麼須要從新查詢,重複上述步驟,知道拿到的版本和數據庫中的版本一致時,才進行更新,這樣就不須要等待,效率更高


相關文章
相關標籤/搜索