1. 程式人生 > 其它 >MySQL的鎖與鎖機制----鎖分類

MySQL的鎖與鎖機制----鎖分類

技術標籤:MySQL資料庫後端

根據加鎖的範圍,MySQL 裡面的鎖大致可以分成全域性鎖、表級鎖和行鎖三類。

全域性鎖

全域性鎖指的是對整個資料庫例項加鎖,MySQL提供了FLUSH TABLE WITH READ LOCK,用於給全整個資料庫例項加讀鎖。這個命令執行後,整個例項就變成只讀了,增刪改的DML語句與建表增加索引等DDL語句都會被堵塞。可以使用UNLOCK TABLES解鎖。
整個資料庫例項都變成自讀了,想想就多麼可怕,但是在MyISAM時代,由於MyISAM不支援事務當我們需要做全庫備份的時候,只能使用這個語句,使得備份期間整個庫只能是隻讀的,從而使得備份出來的庫是一致性的。

但是對於INNODB由於支援事務,在可重複讀級別下,事務開始後,會建立一個一致性檢視,那麼備份出來的資料,就是一致的。我們可以使用mysqldump工具新增–single-transaction 的時候,導資料之前就會啟動一個事務,來確保拿到一致性檢視。而由於 MVCC 的支援,這個過程中資料是可以正常更新的。

表級鎖

MySQL 裡面表級別的鎖有兩種:一種是表鎖,一種是元資料鎖(meta data lock,MDL)。

表鎖

可以通過如下的語句新增表鎖:

LOCK TABLES tbl_name [[AS] alias] lock_type [, tbl_name [[AS] alias] lock_type] ... 
lock_type: { READ [LOCAL] | [LOW_PRIORITY] WRITE } 
UNLOCK TABLES

讀鎖與讀鎖直接可以併發,讀鎖與寫鎖、寫鎖之間只能互斥。對錶加讀鎖後,自己也不能對其進行修改;自己和其他執行緒只能讀取該表。 當對某個表執加上寫鎖後(lock table t2 write),該執行緒可以對這個表進行讀寫,其他執行緒對該表的讀和寫都受到阻塞。現今MySQL都使用了INNODB作為預設的儲存引擎了,MyISAM已經很少使用了,更多細節就不在贅述。

元資料鎖(meta data lock,MDL)

MDL不需要手動加鎖,當訪問資料庫表的時候(增刪改查的DML語句),會自動加上讀鎖;當修改資料庫表的時候(DDL語句),會給資料庫加上寫鎖。MDL鎖是為了保證資料的一致性,想想如果我們在查資料庫表的時候,如果通過DML語句刪除了表的一列,那麼資料庫應該返回什麼呢?與表鎖類似,讀鎖與讀鎖之間可以併發,讀鎖與寫鎖、寫鎖之間只能互斥。但是如果在DML期間都要加寫鎖,那麼在持有寫鎖期間整個庫不可讀寫,如果DDL是一個大表,那麼是多麼可怕的,為此MySQL引入了線上DDL。

更多online DDL的細節參見MySQL官方文件:online-ddl-index-operations
線上DDL只能解決DDL期間堵塞增刪改查的問題,但是DDL在總是需要短暫的獲取MDL寫鎖的,那麼對於熱點表,在獲取到寫鎖前,需要等待其他事務提交或者回滾。那麼對於熱點表,在這之後的DDL語句都會被堵塞這也是沒法接受的。此時有如下選擇:
1、如果熱點表有明顯的業務高峰期與低峰期,可以選擇在低峰期執行online DDL;
2、只能使用先在備庫執行DDL(需臨時關閉binlog),執行完後進行主備切換,然後在舊的主庫執行DDL(同樣需臨時關閉binlog);
3、InfoQ上的gh-ost或者github/gh-ost
另外需要說明的是,雖然online DDL在DDL期間可以執行DML語句,但是DDL本身是一個重IO與CPU的操作,還是要選擇業務低峰期執行。

InnoDB行鎖

MySQL的行鎖是由儲存引擎層實現的,MySQL本身並不支援。並不是所有的儲存引擎都會提供行鎖,MyISAM就沒有提供行鎖功能,對於不支援行數的儲存引擎,當我們需要修改表資料的時候,只能通過新增表鎖來實現,從而嚴重影響併發。這也是INNODB替換MyISAM的原因之一。

兩階段鎖協議

兩階段鎖協議是指,在事務的執行過程中,當需要給行加鎖的時候,才自動加上鎖,但是直到事務提交了,鎖才釋放。這個告訴我們,對於併發衝突越嚴重的語句應該越放到事務的後面來執行。同時這也是避免大事務的原因之一,大事務會導致MySQL長時間佔有鎖,從而影響系統的併發。

死鎖與死鎖檢測

正如如下的語句,當我們的SQL語句存在交叉鎖的時候,就會死鎖。

在這裡插入圖片描述

如上的2個事務的鎖在互相等待,從而發生了死鎖。INNODB提供瞭如下的2種策略:
1、直接進入等待,直到超時。這個超時時間可以通過引數 innodb_lock_wait_timeout 來設定。
2、發起死鎖檢測,發現死鎖後,主動回滾死鎖鏈條中的某一個事務,讓其他事務得以繼續執行。將引數 innodb_deadlock_detect 設定為 on,表示開啟這個邏輯。

通過超時時間來設定,設定長了,如果真發生了死鎖,語句等半天了才退出;設定短了,又存在誤刪的情況。
為此我們一般開啟死鎖檢測。但是開啟死鎖檢測後,在嚴重併發場景下又會導致CPU佔用高,如果併發是1000,當發生鎖資源衝突的時候,每次執行SQL語句都要掃描全部等待同一行鎖的語句,1000的併發就需要掃描1000乘以1000次=100W,時間複雜度是O(n*n)。

鎖衝突策略

當出現鎖衝突的時候,我們可以通過如下的2種策略:
1、控制併發,我們可以使用令牌桶等限流演算法(更多限流演算法參加我的部落格《限流–高併發系統中的流量控制》),在資料庫中介軟體控制併發,也可以在業務層控制併發;
2、提高併發度,比如某個熱點商品的庫存,我們可以通過將庫存拆分成多行,每次扣減庫存的時候,隨機選擇一行就行扣減,當選取的庫存不足時,可以拒絕下單或者再次重新選擇或者重新分配總行數與每一行的數量等策略。

InnoDB間隙鎖

考慮如下的表

CREATE TABLE t
(
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`n` INT UNSIGNED NOT NULL,
`m` INT UNSIGNED NOT NULL,
PRIMARY KEY (`id`),
KEY `n` (`n`)
)ENGINE=InnoDB;
INSERT INTO t(`id`,`n`,`m`) VALUES(10,10,10),(20,20,20),(30,30,30),(40,40,40);
事務1事務2
BEGINBEGIN
SQL1:select * from t where n>0 and n<=10;
INSERT INTO t(id,n,m) VALUES(5,5,5);
COMMIT
SQL2:select * from t where n>0 and n<=10;
COMMIT

如果SQL2讀取到新插入的值(5,5,5),那就有幻讀問題。InnoDB為了再可重複讀隔離級別下,解決幻讀問題,引入了間隙鎖,阻止再(0,10)的間隙中插入新資料。間隙鎖與間隙鎖之間沒有併發衝突,間隙鎖與行鎖也沒有併發衝突,但是間隙鎖會堵塞住往這個間隙中插入元素。
關於幻讀的危害參加我的部落格《事務的ACID特性與隔離性分析》