深入剖析 MySQL 自增鎖

其實自增鎖(AUTO-INC Locks)這塊還是有很多值得討論的細節,例如在併發的場景下,InnoDB 是如何保證該值正確的進行自增的,本章就專門來簡單討論一下 InnoDB 中的自增鎖。

什麼是自增鎖

之前我們提到過,自增鎖是一種比較特殊的表級鎖。並且在事務向包含了 AUTO_INCREMENT 列的表中新增數據時就會去持有自增鎖,假設事務 A 正在做這個操作,如果另一個事務 B 嘗試執行 INSERT語句,事務 B 會被阻塞住,直到事務 A 釋放自增鎖。

這怎麼說呢,說他對,但是他也不完全對。

行爲與限制

其實上面說的那種阻塞情況只是自增鎖行爲的其中一種,可以理解爲自增鎖就是一個接口,其具體的實現有多種。具體的配置項爲 innodb_autoinc_lock_mode ,通過這個配置項我們可以改變自增鎖中運行的一些細節。

並且,自增鎖還有一個限制,那就是被設置爲 AUTO_INCREMENT  的列必須是索引,或者該列是索引的一部分(聯合索引),不過這個限制對於大部分開發場景下並沒有什麼影響。

畢竟我們的基操不就是把 id 設置爲 AUTO_INCREMENT 嗎。

鎖模式

其實在 InnoDB 中,把鎖的行爲叫做鎖模式可能更加準確,那具體有哪些鎖模式呢,如下:

分別對應配置項 innodb_autoinc_lock_mode  的值 0、1、2.

看到這就已經知道爲啥上面說不準確了,因爲三種模式下,InnoDB 對併發的處理是不一樣的,而且具體選擇哪種鎖模式跟你當前使用的 MySQL 版本還有關係。

在 MySQL 8.0 之前,InnoDB 鎖模式默認爲連續模式,值爲 1,而在 MySQL 8.0 之後,默認模式變成了交叉模式。至於爲啥會改變默認模式,後面會講。

傳統模式

傳統模式(Traditional),說白了就是還沒有鎖模式這個概念時,InnoDB 的自增鎖運行的模式。只是後面版本更新,InnoDB 引入了鎖模式的概念,然後 InnoDB 給了這種以前默認的模式一個名字,叫——傳統模式。

傳統模式具體是咋工作的?

我們知道,當我們向包含了 AUTO_INCREMENT 列的表中插入數據時,都會持有這麼一個特殊的表鎖——自增鎖(AUTO-INC),並且當語句執行完之後就會釋放。這樣一來可以保證單個語句內生成的自增值是連續的。

這樣一來,傳統模式的弊端就自然暴露出來了,如果有多個事務併發的執行 INSERT 操作,AUTO-INC的存在會使得 MySQL 的性能略有下降,因爲同時只能執行一條 INSERT 語句。

連續模式

連續模式(Consecutive)是 MySQL 8.0 之前默認的模式,之所以提出這種模式,是因爲傳統模式存在影響性能的弊端,所以纔有了連續模式。

在鎖模式處於連續模式下時,如果 INSERT 語句能夠提前確定插入的數據量,則可以不用獲取自增鎖,舉個例子,像 INSERT INTO 這種簡單的、能提前確認數量的新增語句,就不會使用自增鎖,這個很好理解,在自增值上,我可以直接把這個 INSERT 語句所需要的空間流出來,就可以繼續執行下一個語句了。

但是如果 INSERT 語句不能提前確認數據量,則還是會去獲取自增鎖。例如像 INSERT INTO ... SELECT ... 這種語句,INSERT 的值來源於另一個 SELECT 語句。

連續模式的圖和交叉模式差不多

交叉模式

交叉模式(Interleaved)下,所有的 INSERT 語句,包含 INSERTINSERT INTO ... SELECT ,都不會使用 AUTO-INC 自增鎖,而是使用較爲輕量的 mutex  鎖。這樣一來,多條 INSERT 語句可以併發的執行,這也是三種鎖模式中擴展性最好的一種。

併發執行所帶來的副作用就是單個 INSERT 的自增值並不連續,因爲 AUTO_INCREMENT 的值分配會在多個 INSERT 語句中來回交叉的執行。

優點很明確,缺點是在併發的情況下無法保證數據一致性,這個下面會討論。

交叉模式缺陷

要了解缺陷是什麼,還得先了解一下 MySQL 的 Binlog。Binlog 一般用於 MySQL 的數據複製,通俗一點就是用於主從同步。在 MySQL 中 Binlog 的格式有 3 種,分別是:

如果 MySQL 採用的格式爲 Statement ,那麼 MySQL 的主從同步實際上同步的就是一條一條的 SQL 語句。如果此時我們採用了交叉模式,那麼併發情況下 INSERT 語句的執行順序就無法得到保障。

可能你還沒看出問題在哪兒,INSERT 同時交叉執行,並且 AUTO_INCREMENT 交叉分配將會直接導致主從之間同行的數據主鍵 ID 不同。而這對主從同步來說是災難性的。

換句話說,如果你的 DB 有主從同步,並且 Binlog 存儲格式爲 Statement,那麼不要將 InnoDB 自增鎖模式設置爲交叉模式,會有問題。其實主從同步的過程遠比上圖中的複雜,之前我也寫過詳細的 MySQL 主從同步的文章,感興趣可以先去看看。

而後來,MySQL 將日誌存儲格式從 Statement 變成了 Row,這樣一來,主從之間同步的就是真實的行數據了,而且 主鍵ID 在同步到從庫之前已經確定了,就對同步語句的順序並不敏感,就規避了上面 Statement 的問題。

基於 MySQL 默認 Binlog 格式從 StatementRow 的變更,InnoDB 也將其自增鎖的默認實現從連續模式,更換到了效率更高的交叉模式

魚和熊掌

但是如果你的 MySQL 版本仍然默認使用連續模式,但同時又想要提高性能,該怎麼辦呢?這個其實得做一些取捨。

如果你可以斷定你的系統後續不會使用 Binlog,那麼你可以選擇將自增鎖的鎖模式從連續模式改爲交叉模式,這樣可以提高 MySQL 的併發。並且,沒有了主從同步,INSERT 語句在從庫亂序執行導致的 AUTO_INCREMENT 值不匹配的問題也就自然不會遇到了。

總結

你可能會說,爲啥要了解這麼深?有啥用?

其實還真有,例如在業務中你有一個需要執行 幾十秒 的腳本,腳本中不停的調用多次 INSERT,這時就問你這個問題,在這幾十秒裏,會阻塞其他的用戶使用對應的功能嗎?

如果你對自增鎖有足夠的瞭解,那麼這個問題將會迎刃而解

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/Q6DPjzAIFQWIYHfq29sfeQ