1. 程式人生 > 資料庫 >MySQL-鎖的學習

MySQL-鎖的學習

SQL標準規定不同隔離級別

READ UNCOMMITTED :髒讀、不可重複讀、幻讀都可能發生。
READ COMMITTED:隔離級別下,不可重複讀、幻讀可能發生,髒讀不可以發生。
REPEATABLE READ:隔離級別下,幻讀可能發生,髒讀和不可重複讀不可以發生。
SERIALIZABLE:隔離級別下,上述問題都不可以發生。
MySQL在REPEATABLE READ隔離級別實際上就已經解決了幻讀問題。

  • 幻讀

    幻讀問題的產生是因為某個事務讀了一個範圍的記錄,之後別的事務在該範圍內插入了新記錄,該事務再次讀取該範圍的記錄時,可以讀到新插入的記錄,所以幻讀問題準確的說並不是因為讀取和寫入一條相同記錄而產生的,這一點要注意一下。

解決髒讀、不可重複讀、幻讀

我們說過普通的SELECT語句在READ COMMITTED和REPEATABLE READ隔離級別下會使用到MVCC讀取記錄。在READ COMMITTED隔離級別下,一個事務在執行過程中每次執行SELECT操作時都會生成一個ReadView,ReadView的存在本身就保證了事務不可以讀取到未提交的事務所做的更改,也就是避免了髒讀現象;REPEATABLE READ隔離級別下,一個事務在執行過程中只有第一次執行SELECT操作才會生成一個ReadView,之後的SELECT操作都複用這個ReadView,這樣也就避免了不可重複讀和幻讀的問題。

加鎖

表級別的鎖

LOCK TABLES xxx READ:這是加表級共享鎖 
LOCK TABLES xxx WRITE:這是加表級獨佔鎖
系統預設加的MDL,當對一個表做增刪改查操作的時候,加MDL讀鎖;當要對錶做結構變更操作的時候,加MDL寫鎖。
所以改表結構要注意點,會阻塞這個表的所有操作
其實走mvcc基本都夠了沒必要加鎖。除非特定的業務需要獨佔某條資料。

鎖定語句

對讀取的記錄加S鎖: 共享鎖相互不互斥
SELECT ... LOCK IN SHARE MODE;
對讀取的記錄加X鎖: 排它鎖與鎖都互斥
SELECT ... FOR UPDATE;

增刪改的語句或多或少在獲取資料時或者在更新記錄時都是會獲取X鎖的,具體細化看後面
降低鎖時間

在InnoDB事務中,行鎖是在需要的時候才加上的,但並不是不需要了就立刻釋放,而是要等到事務結束時才釋放。這個就是兩階段鎖協議。

假設你負責實現一個電影票線上交易業務,顧客A要在影院B購買電影票。我們簡化一點,這個業務需要涉及到以下操作:

  1. 從顧客A賬戶餘額中扣除電影票價;
  2. 給影院B的賬戶餘額增加這張電影票價;
  3. 記錄一條交易日誌。

根據兩階段鎖協議,不論你怎樣安排語句順序,所有的操作需要的行鎖都是在事務提交的時候才釋放的。所以,如果你把語句2安排在最後,比如按照3、1、2這樣的順序,那麼影院賬戶餘額這一行的鎖時間就最少。這就最大程度地減少了事務之間的鎖等待,提升了併發度。

死鎖檢測策略
  • 一種策略是,直接進入等待,直到超時。這個超時時間可以通過引數innodb_lock_wait_timeout來設定。
  • 另一種策略是,發起死鎖檢測,發現死鎖後,主動回滾死鎖鏈條中的某一個事務,讓其他事務得以繼續執行。將引數innodb_deadlock_detect設定為on,表示開啟這個邏輯。

間隙鎖與臨鍵鎖(next-key lock)

MVCC不能完美的解決幻讀問題

我們都知道 幻讀是資料條數的改變,那意味著表裡面增加記錄的鎖時未知的。即使把所有的記錄都加上鎖,還是阻止不了新插入的記錄

這樣,當你執行 select * from t where d=5 for update的時候,就不止是給資料庫中已有的6個記錄加上了行鎖,還同時加了7個間隙鎖。這樣就確保了無法再插入新的記錄。

**跟間隙鎖存在衝突關係的,是“往這個間隙中插入一個記錄”這個操作。**間隙鎖之間都不存在衝突關係

間隙鎖是在可重複讀隔離級別下才會生效的,不可重複讀那沒必要加鎖了

間隙鎖和行鎖合稱next-key lock:每個next-key lock是前開後閉區間。

也就是說,我們的表t初始化以後。如果用select * from t for update要把整個表所有記錄鎖起來,

就形成了7個next-key lock,分別是 (-∞,0]、(0,5]、(5,10]、(10,15]、(15,20]、(20, 25]、(25, +suprenum]。