InnoDB 存儲引擎中的鎖.


一、鎖的類型

InnoDB 存儲引擎 lock 的對象是事務,用來鎖定的是數據庫中的對象,如表、頁、行,並且一般 lock 的對象僅在事務 commit 或 rollback 后進行釋放(不同事務隔離級別釋放的時間可能不同)。

InnoDB 存儲引擎實現了如下兩種標准的行級鎖,其中,X 鎖與任何的鎖都不兼容,而 S 鎖僅和 S 鎖兼容(兼容指對同一記錄行的兼容性情況)

  • 共享鎖(S Lock),允許事務讀一行數據;
  • 排他鎖(X Lock),允許事務刪除或更新一行數據;

InnoDB 存儲引擎除了行鎖以外,還有表鎖,通常也稱為意向鎖,其設計目的主要是為了在一個事務中揭示下一行將被請求的鎖類型。其支持兩種意向鎖:

  • 意向共享鎖(IS Lock),事務想要獲得一張表中某幾行的共享鎖
  • 意向排他鎖(IX Lock),事務想要獲得一張表中某幾行的排他鎖

通過 information_schema 架構下的 INNODB_TRX、INNODB_LOCKS、INNODB_LOCK_WAITS 三張表,用戶可以更簡單的監控當前事務並分析可能存在的鎖問題。

SELECT * FROM information_schema.INNODB_TRX;
SELECT * FROM information_schema.INNODB_LOCKS;
SELECT * FROM information_schema.INNODB_LOCK_WAITS;

二、鎖的算法

InnoDB 存儲引擎有三種行鎖的算法,其分別是:

  • Record Lock:單個行記錄上的鎖
  • Gap Lock:間隙鎖,鎖定一個范圍,但不包含記錄本身
  • Next-Key Lock:Record Lock + Gap Lock,鎖定一個范圍,並且鎖定記錄本身

Record Lock 總是會去鎖住索引記錄,InnoDB 存儲引擎會使用聚簇索引來進行鎖定。

Gap Lock 的作用是為了阻止多個事務將記錄插入到同一個范圍內,因為這會導致幻讀問題(phantom Problem)的產生,用戶可以通過以下兩種方式來顯式地關閉 Gap Lock:

  • 將事務的隔離級別設置為 READ COMMITTED
  • 將參數 innodb_locks_unsafe_for_binlog 設置為 1

Next-Key Lock 是結合了 Gap Lock 和 Record Lock 的一種索引算法,這種鎖定技術,不止鎖定記錄本身,還鎖定一個范圍。

InnoDB 對於行的查詢默認是采用 Next-Key Lock 算法,當查詢的索引含有唯一屬性時(主鍵索引、唯一索引),InnoDB 存儲引擎會對 Next-Key Lock 進行優化,將其降級為 Record Lock;而對於輔助索引,不僅會對索引列加 Record Lock ,還會對索引列前后的鍵值范圍加上 Gap Lock。

Phantom Problem:幻讀問題,指在同一事務下,連續執行兩次同樣的 SQL 語句可能導致不同的結果,第二次的 SQL 語句可能會返回之前不存在的行。

1. 聚簇索引的加鎖情況

select * from students where id = 20  for update;

2. 唯一索引的加鎖情況

select * from students where num = 135 for update;

3. 二級索引的加鎖情況

select * from students where score= 91  for update; 

4. 無索引的加鎖情況

select * from students where age = 22  for update;

三、鎖的問題

  1. Dirty Read 臟讀:一個事務讀到了另一個未提交的事務寫的數據,這顯然違反了數據庫的隔離性。

  2. Non-Repeatable Read 不可重復讀:一個事務內多次讀取同一數據集合,可能兩次讀到的數據是不一樣的。不可重復讀和臟讀的區別是:臟讀是讀到未提交的數據,而不可重復讀讀到的卻是已經提交的數據,這顯然違反了數據庫的一致性。

  3. Phantom Problem 幻讀 :幻讀問題,指在同一事務下,連續執行兩次同樣的 SQL 語句可能導致不同的結果,第二次的 SQL 語句可能會返回之前不存在的行。幻讀是比不可重復讀高一個級別的錯誤,讀取同一條數據發現跟剛才是一樣的,只有讀取一堆數據發現忽然多了一個,或者少了一個,像是產生了幻覺。

  4. Lost Update 更新丟失
    a. 第一類更新丟失,回滾覆蓋:撤消一個事務時,在該事務內的寫操作要回滾,把其它已提交的事務寫入的數據覆蓋了。
    b. 第二類更新丟失,提交覆蓋:提交一個事務時,寫操作依賴於事務內讀到的數據,讀發生在其他事務提交前,寫發生在其他事務提交后,把其他已提交的事務寫入的數據覆蓋了。這是不可重復讀的特例。

為了解決多個事務並發會引發的鎖問題,數據庫系統提供了四種事務隔離級別供用戶選擇。

  • Read Uncommitted 讀未提交:不允許第一類更新丟失。允許臟讀,不隔離事務。
  • Read Committed 讀已提交:不允許臟讀,允許不可重復讀(即允許第二類更新丟失)。
  • Repeatable Read 可重復讀:不允許不可重復讀(即不允許第二類更新丟失),但可能出現幻讀。
  • Serializable 串行化:所有的增刪改查串行執行。

為什么 MYSQL 默認使用 Repeatable Read 隔離級別?這跟數據庫的主從復制有關,MYSQL 的主從復制是基於 binlog 復制的,而 binlog 有三種格式,分別為:

  • statement:記錄的是修改 SQL 語句
  • row:記錄的是每行實際數據的變更
  • mixed:statement 和 row 模式的混合

那 MYSQL 在 5.0 這個版本以前,binlog 只支持 statement 這種格式!而這種格式在讀已提交(Read Commited)這個隔離級別下主從復制是有 bug 的,因此 Mysql 將可重復讀(Repeatable Read)作為默認的隔離級別。

怎么解決 Read Committed 隔離級別下,主從復制有問題的 bug?首先得解釋下這個 bug,在 master 上執行的順序為先刪后插,若此時 binlog 為 statement 格式,它記錄的順序為先插后刪,slave 同步的是 binglog,因此 slave 執行的順序和主機不一致,就會出現主從不一致,怎么解決這個 bug 呢?

  1. 隔離級別設為可重復讀(Repeatable Read),在該隔離級別下引入間隙鎖(GAP LOCK),在執行 DELETE 語句時,會鎖住間隙,那么執行 INSERT 語句就會阻塞住。
  2. 將 binglog 的格式修改為 row 格式,此時是基於行的復制,自然就不會出現 sql 執行順序不一樣的問題(這個格式在 MYSQL 5.1 版本才開始引入)。

四、其它

  1. 在 InnoDB 存儲引擎中,參數 innodb_lock_wait_timeout 用來控制鎖等待的時間(默認是 50 秒),innodb_rollback_on_timeout 用來設定是否在等待超時時對進行中的事務進行回滾操作(默認是 OFF,代表不回滾)

  2. InnoDB 存儲引擎在大部分情況下都不會對異常進行回滾(死鎖除外),因此用戶必須判斷是否需要 COMMIT 還是 ROLLBACK,之后再進行下一步的操作。

  3. InnoDB 存儲引擎通過 wait-for graph(等待圖)的方式來進行死鎖檢測,wait-for graph 是一種較為主動的死鎖檢測機制,在每個事務請求鎖並發生等待時都會判斷是否存在回路,若存在則有死鎖,通常來說 InnoDB 存儲引擎選擇回滾 undo 量最小的事務。

  4. InnoDB 存儲引擎不存在鎖升級的問題,因為其不是根據每個記錄來產生行鎖的,相反,其是根據每個事務訪問的每個頁對鎖進行管理的,采用的是位圖的方式。因此不管一個事務鎖住頁中的一個記錄還是多個記錄,其開銷通常是一致的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM