1:MySQL 有哪些鎖?
全局鎖
flush tables with read lock 整個數據庫就處于只讀狀態了
unlock tables 釋放全局鎖全局鎖主要應用于做全庫邏輯備份,這樣在備份數據庫期間,不會因為數據或表結構的更新,而出現備份文件的數據與預期的不一樣。
如果數據庫的引擎支持的事務支持可重復讀的隔離級別,那么在備份數據庫之前先開啟事務,會先創建 Read View,然后整個事務執行期間都在用這個 Read View,而且由于 MVCC 的支持,備份期間業務依然可以對數據進行更新操作。
備份數據庫的工具是 mysqldump,在使用 mysqldump 時加上 –single-transaction 參數的時候,就會在備份數據庫之前先開啟事務。這種方法只適用于支持「可重復讀隔離級別的事務」的存儲引擎。
InnoDB 存儲引擎默認的事務隔離級別正是可重復讀,因此可以采用這種方式來備份數據庫。
表級鎖
表鎖;
表鎖除了會限制別的線程的讀寫外,也會限制本線程接下來的讀寫操作。盡量避免在使用 InnoDB 引擎的表使用表鎖,因為表鎖的顆粒度太大。元數據鎖(MDL);
不需要顯示的使用 MDL,因為當我們對數據庫表進行操作時,會自動給這個表加上 MDL
對一張表進行 CRUD 操作時,加的是 MDL 讀鎖;
對一張表做結構變更操作的時候,加的是 MDL 寫鎖
MDL 是在事務提交后才會釋放,這意味著事務執行期間,MDL 是一直持有的
意向鎖;
在使用 InnoDB 引擎的表里對某些記錄加上「共享鎖」之前,需要先在表級別加上一個「意向共享鎖」;
在使用 InnoDB 引擎的表里對某些紀錄加上「獨占鎖」之前,需要先在表級別加上一個「意向獨占鎖」;
意向共享鎖和意向獨占鎖是表級鎖,不會和行級的共享鎖和獨占鎖發生沖突,而且意向鎖之間也不會發生沖突,只會和共享表鎖(lock tables ... read)和獨占表鎖(lock tables ... write)發生沖突。
意向鎖的目的是為了快速判斷表里是否有記錄被加鎖。
AUTO-INC 鎖;
表里的主鍵通常都會設置成自增的,這是通過對主鍵字段聲明 AUTO_INCREMENT 屬性實現的。
之后可以在插入數據時,可以不指定主鍵的值,數據庫會自動給主鍵賦值遞增的值,這主要是通過 AUTO-INC 鎖實現的
AUTO-INC 鎖是特殊的表鎖機制,鎖不是再一個事務提交后才釋放,而是再執行完插入語句后就會立即釋放。
AUTO-INC 鎖再對大量數據進行插入的時候,會影響插入性能
在 MySQL 5.1.22 版本開始,InnoDB 存儲引擎提供了一種輕量級的鎖來實現自增。
一樣也是在插入數據的時候,會為被 AUTO_INCREMENT 修飾的字段加上輕量級鎖,然后給該字段賦值一個自增的值,就把這個輕量級鎖釋放了,而不需要等待整個插入語句執行完后才釋放鎖
InnoDB 存儲引擎提供了個 innodb_autoinc_lock_mode 的系統變量,是用來控制選擇用 AUTO-INC 鎖,還是輕量級的鎖
當 innodb_autoinc_lock_mode = 0,就采用 AUTO-INC 鎖,語句執行結束后才釋放鎖;
當 innodb_autoinc_lock_mode = 2,就采用輕量級鎖,申請自增主鍵后就釋放鎖,并不需要等語句執行后才釋放。
當 innodb_autoinc_lock_mode = 1:
普通 insert 語句,自增鎖在申請之后就馬上釋放;
類似 insert … select 這樣的批量插入數據的語句,自增鎖還是要等語句結束后才被釋放
當 innodb_autoinc_lock_mode = 2 是性能最高的方式,但是當搭配 binlog 的日志格式是 statement 一起使用的時候,在「主從復制的場景」中會發生數據不一致的問題
因為binlog_format=statement,記錄的語句就是原始語句。
binlog_format = row時,binlog 里面記錄的是主庫分配的自增值,到備庫執行的時候,主庫的自增值是什么,從庫的自增值就是什么。
所以當 innodb_autoinc_lock_mode = 2 時,并且 binlog_format = row,既能提升并發性,又不會出現數據一致性問題。
行級鎖
//對讀取的記錄加共享鎖
select ... lock in share mode;
//對讀取的記錄加獨占鎖
select ... for update;
共享鎖(S鎖)滿足讀讀共享,讀寫互斥。獨占鎖(X鎖)滿足寫寫互斥、讀寫互斥。
Record Lock
Record Lock 稱為記錄鎖,鎖住的是一條記錄。而且記錄鎖是有 S 鎖和 X 鎖之分的:
當一個事務對一條記錄加了 S 型記錄鎖后,其他事務也可以繼續對該記錄加 S 型記錄鎖(S 型與 S 鎖兼容),但是不可以對該記錄加 X 型記錄鎖(S 型與 X 鎖不兼容);
當一個事務對一條記錄加了 X 型記錄鎖后,其他事務既不可以對該記錄加 S 型記錄鎖(S 型與 X 鎖不兼容),也不可以對該記錄加 X 型記錄鎖(X 型與 X 鎖不兼容)。
Gap Lock
Gap Lock 稱為間隙鎖,只存在于可重復讀隔離級別,目的是為了解決可重復讀隔離級別下幻讀的現象。
假設,表中有一個范圍 id 為(3,5)間隙鎖,那么其他事務就無法插入 id = 4 這條記錄了,這樣就有效的防止幻讀現象的發生。
間隙鎖之間的X型間隙鎖和S型間隙鎖是兼容的,即兩個事務可以同時持有包含共同間隙范圍的間隙鎖,并不存在互斥關系,因為間隙鎖的目的是防止插入幻影記錄而提出的。
Next-Key Lock
Next-Key Lock 稱為臨鍵鎖,是 Record Lock + Gap Lock 的組合,鎖定一個范圍,并且鎖定記錄本身。
假設,表中有一個范圍 id 為(3,5] 的 next-key lock,那么其他事務即不能插入 id = 4 記錄,也不能修改 id = 5 這條記錄。next-key lock 是包含間隙鎖+記錄鎖的,如果一個事務獲取了 X 型的 next-key lock,那么另外一個事務在獲取相同范圍的 X 型的 next-key lock 時,是會被阻塞的。
雖然相同范圍的間隙鎖是多個事務相互兼容的,但對于記錄鎖,我們是要考慮 X 型與 S 型關系,X 型的記錄鎖與 X 型的記錄鎖是沖突的。
插入意向鎖
一個事務在插入一條記錄的時候,需要判斷插入位置是否已被其他事務加了間隙鎖(next-key lock 也包含間隙鎖)。
如果有的話,插入操作就會發生阻塞,直到擁有間隙鎖的那個事務提交為止(釋放間隙鎖的時刻),在此期間會生成一個插入意向鎖,表明有事務想在某個區間插入新記錄,但是現在處于等待狀態。
插入意向鎖名字雖然有意向鎖,但是它并不是意向鎖,它是一種特殊的間隙鎖,屬于行級別鎖。
插入意向鎖與間隙鎖的另一個非常重要的差別是:
盡管「插入意向鎖」也屬于間隙鎖,但兩個事務卻不能在同一時間內,一個擁有間隙鎖,另一個擁有該間隙區間內的插入意向鎖(當然,插入意向鎖如果不在間隙鎖區間內則是可以的)。
2:MySQL 是怎么加鎖的?
什么 SQL 語句會加行級鎖?
要在查詢時對記錄加行級鎖,可以使用下面這兩個方式,這兩種查詢會加鎖的語句稱為鎖定讀
//對讀取的記錄加共享鎖(S型鎖)
select ... lock in share mode;
//對讀取的記錄加獨占鎖(X型鎖)
select ... for update;
除了上面這兩條鎖定讀語句會加行級鎖之外,update 和 delete 操作都會加行級鎖,且鎖的類型都是獨占鎖(X型鎖)。
MySQL 是怎么加行級鎖的?
加鎖的對象是索引,加鎖的基本單位是 next-key lock,它是由記錄鎖和間隙鎖組合而成的,next-key lock 是前開后閉區間,而間隙鎖是前開后開區間。
在能使用記錄鎖或者間隙鎖就能避免幻讀現象的場景下, next-key lock 就會退化成記錄鎖或間隙鎖。
唯一索引等值查詢:
當查詢的記錄是「存在」的,在索引樹上定位到這一條記錄后,將該記錄的索引中的 next-key lock 會退化成「記錄鎖」。
當查詢的記錄是「不存在」的,在索引樹找到第一條大于該查詢記錄的記錄后,將該記錄的索引中的 next-key lock 會退化成「間隙鎖」。
非唯一索引等值查詢:
當查詢的記錄「存在」時,由于不是唯一索引,所以肯定存在索引值相同的記錄,于是非唯一索引等值查詢的過程是一個掃描的過程,直到掃描到第一個不符合條件的二級索引記錄就停止掃描,然后在掃描的過程中,對掃描到的二級索引記錄加的是 next-key 鎖,而對于第一個不符合條件的二級索引記錄,該二級索引的 next-key 鎖會退化成間隙鎖。同時,在符合查詢條件的記錄的主鍵索引上加記錄鎖。
當查詢的記錄「不存在」時,掃描到第一條不符合條件的二級索引記錄,該二級索引的 next-key 鎖會退化成間隙鎖。因為不存在滿足查詢條件的記錄,所以不會對主鍵索引加鎖。
非唯一索引和主鍵索引的范圍查詢的加鎖規則不同之處在于:
唯一索引在滿足一些條件的時候,索引的 next-key lock 退化為間隙鎖或者記錄鎖。
非唯一索引范圍查詢,索引的 next-key lock 不會退化為間隙鎖和記錄鎖。
其實理解 MySQL 為什么要這樣加鎖,主要要以避免幻讀角度去分析,這樣就很容易理解這些加鎖的規則了。
還有一件很重要的事情,在線上在執行 update、delete、select ... for update 等具有加鎖性質的語句,一定要檢查語句是否走了索引,如果是全表掃描的話,會對每一個索引加 next-key 鎖,相當于把整個表鎖住了,這是挺嚴重的問題。
這里我們重點關注行鎖,圖中 LOCK_TYPE 中的 RECORD 表示行級鎖,而不是記錄鎖的意思,通過LOCK_MODE 可以確認是 next-key 鎖,還是間隙鎖,還是記錄鎖:
如果 LOCK_MODE 為 X,說明是 X 型的 next-key 鎖;
如果 LOCK_MODE 為 X, REC_NOT_GAP,說明是 X 型的記錄鎖;
如果 LOCK_MODE 為 X, GAP,說明是 X 型的間隙鎖;
3:如何避免死鎖?
死鎖的四個必要條件:互斥、占有且等待、不可強占用、循環等待。只要系統發生死鎖,這些條件必然成立,但是只要破壞任意一個條件就死鎖就不會成立。
在數據庫層面,有兩種策略通過「打破循環等待條件」來解除死鎖狀態:
? ? ? :?設置事務等待鎖的超時時間。當一個事務的等待時間超過該值后,就對這個事務進行回滾,于是鎖就釋設置事務等待鎖的超時時間。當一個事務的等待時間超過該值后,就對這個事務進行回滾,于是鎖就釋時時間的,默認值時 50 秒。
? ? ? : 開啟主動死鎖檢測。主動死鎖檢測在發現死鎖后,主動回滾死鎖鏈條中的某一個事務,讓其他事務得以開啟主動死鎖檢測。主動死鎖檢測在發現死鎖后,主動回滾死鎖鏈條中的某一個事務,讓其他事務得以
上面這個兩種策略是「當有死鎖發生時」的避免方式。
我們可以回歸業務的角度來預防死鎖,對訂單做冪等性校驗的目的是為了保證不會出現重復的訂單,那我們可以回歸業務的角度來預防死鎖,對訂單做冪等性校驗的目的是為了保證不會出現重復的訂單,不過有一點不好的地方就是在我們插入一個已經存在的訂單記錄時就會拋出異常。
?