什么是鎖
在對共享資源并發(fā)訪問時翅溺,鎖用來保障數據的準確。通俗點理解,鎖就類似于排隊飞蚓,Java中synchronized鎖是在對象頭上進行排隊,分布式鎖是在一個公用的存儲服務上排隊拴鸵,而數據庫中的鎖是在所操作記錄的對象上排隊玷坠。
MySQL中鎖的類型
各大主流數據庫都會有鎖的實現,而鎖正是數據庫區(qū)別于文件系統(tǒng)的特性之一劲藐。不僅不同數據庫之間鎖的實現不同八堡,MySQL中不同存儲引擎對于鎖的實現也各不相同。
大體而言MySQL數據庫中既有表鎖聘芜,又有行鎖兄渺。在Innodb存儲引擎中會使用到下列這些鎖:
- Shared and Exclusive Lock
- Intention Lock
- Record Lock
- Gap Lock
- Next-Key Lock
- Insert Intention Lock
- AUTO-INC Locks
MyISAM中實現的鎖是表鎖,并發(fā)情況下的讀沒有問題汰现,但是并發(fā)插入的性能很差挂谍。InnoDB實現的是行鎖,鎖定粒度小并發(fā)度高瞎饲。MySQL默認的存儲引擎是InnoDB口叙,且官方目前打算不再繼續(xù)對其他存儲進行開發(fā)維護,所以這里我們討論的鎖都是基于InnoDB存儲引擎嗅战。
鎖都是鎖在索引上的妄田,無論是聚集索引(主鍵)還是二級索引
在InnoDB中行鎖的模式有兩種:
- 共享鎖(S Lock),允許事務讀取一行數據驮捍。
- 排他鎖(X Lock)疟呐,允許事務刪除或者更新一行數據。
意向鎖
除了行級別的鎖东且,InnoDB中還支持表級別的鎖启具。為了實現多粒度的鎖(多粒度的鎖表示在數據庫中不但能實現行級別的鎖,還可以實現頁級別的鎖珊泳,表級別的鎖甚至數據庫級別的鎖)鲁冯,InnoDB還支持另外一種鎖的模式,意向鎖色查。意向鎖主要是為了在一個事務中揭示下一行將被請求的鎖類型晓褪。InnoDB同樣支持兩種意向鎖:
- 意向共享鎖(IS Lock),事務想要獲取表中某幾行的共享鎖综慎。
- 意向排他鎖(IX Lock),事務想要獲取表中某幾行的排他鎖勤庐。
意向鎖的工作方式
MySQL加鎖的方式是從上往下一層層加的示惊。如果事務A要在記錄1上加一把X鎖好港,則步驟如下:
- 在記錄1所在的數據庫上加一把意向鎖IX;
- 在記錄1所在的表上加一把意向鎖IX;
- 在記錄1所在的頁上加一把意向鎖IX米罚;
- 在記錄1上加一把X鎖钧汹。
InnoDB沒有數據庫級別的鎖,也沒有頁級別的鎖录择,InnoDB只能在表和記錄上加鎖拔莱,所以InnoDB的意向鎖只能加在表上,即InnoDB中的意向鎖都是表鎖隘竭。
下面來看看共享鎖塘秦、排他鎖、意向共享鎖动看、意向排他鎖的兼容性
加鎖以及查看
加鎖
針對某條記錄的修改和刪除會隱式的加一把X鎖尊剔,如果正對查詢也想要加鎖就需要在SQL語句中顯示的指定。
查詢操作通過在SQL語句的末尾處增加for update
來加X鎖
select * from t1 where a=1 for update;
查詢操作通過在SQL語句的末尾處增加lock in share mode
來加S鎖
select * from t1 where a=1 lock in share mode;
鎖超時
當產生鎖競爭時菱皆,如果持有鎖的一方遲遲不釋放鎖须误,這時請求鎖的事務(或session)會一直等待鎖的釋放。但是這個等待鎖的過程是有等待超時的仇轻,通過innodb_lock_wait_timeout變量進行設置京痢,默認50s。也就是說等待50秒后還沒有獲取到鎖就放棄等待篷店,同時拋出錯誤ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
查看
可以通過show engine innodb status\G;指令來查看加鎖的情況祭椰,該指令會顯示很多和innodb存儲引擎相關的運行信息,其中TRANSACTIONS表示和鎖相關的信息船庇。默認顯示的情況如下:如果想要顯示更詳細的信息吭产,可以將innodb_status_output_locks參數打開,在MySQL5.7中其默認是關閉的鸭轮。
打開之后我們可以看到關于鎖更詳細的信息
-- Record lock : 表示是鎖住的記錄
-- heap no 2 PHYSICAL RECORD: n_fields 5 : 表示鎖住記錄的heap no 為2的物理記錄臣淤,由5個列組成
-- compact format : 表示這條記錄存儲的格式(Dynamic其實也是compact的格式)
-- info bits : 0 -- 表示這條記錄沒有被刪除; 非0 -- 表示被修改或者被刪除(32)
在MySQL5.7之后的版本中,我們可以通過sys庫下的innodb_lock_waits表來查看更詳細的信息窃爷,注意只有當產生了鎖等待時該表中才會有記錄邑蒋。
select * from innodb_lock_waits\G;
*************************** 1. row ***************************
wait_started: 2018-07-15 12:06:27 -- 開始的時間
wait_age: 00:00:09 -- 等待的時間
wait_age_secs: 9 -- 等待的秒數
locked_table: `test`.`t1` -- 鎖主的表(意向鎖)
locked_index: GEN_CLUST_INDEX -- 鎖住的是系統(tǒng)生成的聚集索引,鎖都是在索引上的
locked_type: RECORD -- 鎖的類型按厘,記錄鎖
waiting_trx_id: 421992807332576 -- 等待鎖的事務ID
waiting_trx_started: 2018-07-15 12:06:27
waiting_trx_age: 00:00:09
waiting_trx_rows_locked: 1
waiting_trx_rows_modified: 0
waiting_pid: 13
waiting_query: select * from t1 where a=2 lock in share mode -- 等待鎖的SQL語句
waiting_lock_id: 421992807332576:25:3:2 -- 事務ID:space:page_no:heap_no
waiting_lock_mode: S -- 等待的鎖的模式
blocking_trx_id: 3338
blocking_pid: 12
blocking_query: NULL
blocking_lock_id: 3338:25:3:2
blocking_lock_mode: X -- 阻塞的鎖的類型
blocking_trx_started: 2018-07-15 12:04:41
blocking_trx_age: 00:01:55
blocking_trx_rows_locked: 2
blocking_trx_rows_modified: 0
sql_kill_blocking_query: KILL QUERY 12
sql_kill_blocking_connection: KILL 12 -- 給出了建議
1 row in set, 3 warnings (0.00 sec)
下面列舉一下通過show engine innodb status\G
指令看到的各種鎖的樣子医吊。
1. 意向鎖
TABLE LOCK table `test`.`t3` trx id 3962 lock mode IX
2. Record Locks
RECORD LOCKS space id 39 page no 3 n bits 72 index PRIMARY of table `test`.`t3` trx id 3962 lock_mode X locks rec but not gap
2.1 從index PRIMARY可以看出該鎖加在主鍵上
2.2 記錄鎖除了lock_mode X locks rec but not gap還有l(wèi)ock_mode S locks rec but not gap
3. Gap Locks
RECORD LOCKS space id 39 page no 5 n bits 72 index c of table `test`.`t3` trx id 3962 lock_mode X locks gap before rec
4. Next-Key Locks
RECORD LOCKS space id 39 page no 5 n bits 72 index c of table `test`.`t3` trx id 3962 lock_mode X
5. Insert Intention Locks
RECORD LOCKS space id 279 page no 3 n bits 72 index PRIMARY of table `test`.`t3` trx id 133587907 lock_mode X insert intention waiting
6. AUTO-INC Locks
TABLE LOCK table xx trx id 7498948 lock mode AUTO-INC waiting
讀與MVCC
我們知道如果某行數據加了X鎖, 就沒法加S鎖逮京,即沒法讀了卿堂,不過在實際情況中一般數據庫中都允許并發(fā)讀的,即使要讀取的記錄上存在X鎖。普通的讀操作(非for update
和lock in share mode
)不會加鎖草描。那么在MySQL中如何保障存在DDL的并發(fā)操作中讀的一致性呢览绿?Innodb使用了MVCC技術來保證讀的一致性。
MVCC (Multiversion Concurrency Control)穗慕,即多版本并發(fā)控制技術,它使得大部分支持行鎖的事務引擎饿敲,不再單純的使用行鎖來進行數據庫的并發(fā)控制,取而代之的是把數據庫的行鎖與行的多個版本結合起來逛绵,只需要很小的開銷,就可以實現非鎖定讀怀各,從而大大提高數據庫系統(tǒng)的并發(fā)性能
如果要讀取的行上存在X鎖,這時讀操作不會等行上X鎖的釋放术浪,而是去讀取行的一個快照版本瓢对。由于沒有事務會對快照數據進行修改,所以對于快照的讀取是不用上鎖的添吗。
在read committed
和repeatable read
的事務隔離級別中沥曹,都是使用MVCC進行讀操作。不過這兩種隔離級別在選擇哪個快照版本進行讀取問題上存在區(qū)別碟联,區(qū)別如下:
-
read committed
選擇最新的快照版本進行讀(事務A)妓美,如果其他事務(事務B)對要讀取的行進行了更新并提交。則在事務A再進行讀時讀取到的數據版本是事務B提交修改后形成的最新快照版本鲤孵。 -
repeatable read
讀取的是事務(事務A)一開始時記錄的快照版本壶栋,即使后面事務B對數據進行了修改并提交,只要事務A沒有提交普监,那么在事務A中讀取的版本依然是一開始的快照版本贵试。
這也就能說明為啥在read committed
中存在不可重復讀現象,而repeatable read
不存在凯正。
MVCC實現讀是不需要加鎖的毙玻,MySQL當中除了MVCC的方式之外,我們還可以通過顯示的加鎖來保證并發(fā)過程中數據讀取的一致性廊散∩L玻可以通過下面兩種方式顯式的加鎖:
- select ... for update; 加X鎖
- select ... lock in share mode; 加S鎖
這兩種形式的查詢語句要放在事務當中,一旦事務提交了允睹,鎖也就釋放了运准。