本篇主要根據innodb存儲引擎的鎖進行闡述,包括分類,算法,以及鎖的一些問題mysql
1、鎖的概述算法
爲了保證最大程度的利用數據庫的併發訪問,又要確保每一個用戶能以一致的方式讀取和修改數據,爲此鎖就派上了用場,也就是鎖的機制。鎖機制也是用於區別數據庫系統和文件系統的一個關節特性。sql
鎖是爲了支持對共享資源進行訪問,提供數據的一致性和完整性。
innodb存儲引擎支持行級別的鎖,不過也能夠在其餘不少的地方上鎖,好比,LRU列,刪除,添加,移動LRU列中的元素。
innodb存儲引擎提供了一致性的非鎖定讀,行級鎖支撐,行級鎖沒有額外的開銷,並能夠同時保證併發和一致性。數據庫
2、innodb鎖併發
2.一、lock和latch性能
雖然兩個都是「鎖」,可是二者有着大相徑庭的意義。優化
latch爲輕量級鎖:要求鎖定的時間很是短,在innodb中,其又能夠分爲mutex(互斥量)和rwlock(讀寫鎖),其目的是用來保證併發線程操做臨界資源的正確性,沒有死鎖檢查機制。
查看:show ENGINE innodb MUTEXspa
其中os_waits表示操做系統等待的次數,當不能得到latch時,操做系統就會進入等待狀態,等待被喚醒。操作系統
lock爲事務級鎖:用來鎖定數據庫中的對象,好比,表、頁、行。而且通常lock的對象僅在事務commit或者rollback後進行釋放,有死鎖機制。線程
2.二、innodb存儲引擎中的鎖
2.2.一、鎖的類型
共享鎖(S Lock):容許事務讀一行數據,具備鎖兼容性質,容許多個事務同時得到該鎖。
排它鎖(X Lock):容許事務刪除或更新一行數據,具備排它性,某個事務要想得到鎖,必需要等待其餘事務釋放該對象的鎖。
X鎖和其餘鎖都不兼容,S鎖之和S鎖兼容,S鎖和X鎖都是行級別鎖,兼容是指對同一條記錄(row)鎖的兼容性狀況。
此外,innodb支持多粒度鎖定,這種鎖容許事務在行級別和表級別上的鎖同時存在,稱之爲意向鎖(Intention Lock),意向鎖將鎖定的對象分爲多個層次,意味着事務在更細粒度上進行加鎖。意向鎖設計的目的主要是爲了在一個事務中揭示下一行將被請求的鎖類型。
意向共享鎖(IS Lock):事務想要得到一張表中的某幾行的共享鎖
意向排它鎖(IX Lock):事務想要得到一張表中的某幾行的排它鎖
對數據庫中的對象加鎖,相似於這棵樹,以下圖所示:
若將上鎖當作如上的這顆樹,那麼對最下層對象的上鎖,也就是最細粒度的上鎖,首先須要對粗粒度進行上鎖,如上圖所示,若是咱們須要對最底層的記錄進行上X鎖,那麼須要對數據庫,表,頁上意向鎖IX Lock,最後對最底層的記錄上X鎖,若其中的任意一個部分致使等待,則該操做須要等待粗粒度鎖的完成。
因爲innodb的支持行級鎖,因此意向鎖不會阻塞全表掃描之外的任何請求,故表級意向鎖和行級鎖的兼容以下表所示:
查看鎖的方式:
一、show engine innodb status
二、show full processlist
三、在information_schema庫中有三個表能夠查看,分別是innodb_locks, innodb_trx, innodb_lock_waits.
innodb_trx表的結構(該表只用來顯示當前運行innodb事務狀況,不能判斷鎖的狀況):
innodb_locks表的結構(能夠查看鎖的狀況,當事務較小時,用戶能夠認爲的只管判斷,若是事務很大,則認爲判斷就很差判斷了):
innodb_lock_waits表的結構:
實踐部分:
咱們經過兩個會話提交事務,在會話1中開啓事務,修改name,不提交,在會話2中同樣修改name值,會話2中會被阻塞。
一、show engine innodb status\G; 查看
------------ TRANSACTIONS ------------ Trx id counter 10026 Purge done for trx's n:o < 10024 undo n:o < 0 state: running but idle History list length 19 LIST OF TRANSACTIONS FOR EACH SESSION: ---TRANSACTION 421685101706864, not started 0 lock struct(s), heap size 1136, 0 row lock(s) ---TRANSACTION 10025, ACTIVE 238 sec starting index read mysql tables in use 1, locked 1 LOCK WAIT 2 lock struct(s), heap size 1136, 2 row lock(s) MySQL thread id 8, OS thread handle 140209809667840, query id 74 localhost root updating update test2.gxt set name='good boy' where id=1 #表示這個語句被阻塞,在申請鎖 ------- TRX HAS BEEN WAITING 19 SEC FOR THIS LOCK TO BE GRANTED: #表示該事務在申請鎖已經等待了19秒 #表示test2.gxt表上的記錄要申請的行鎖(recode lock)是獨佔鎖而且正在waiting,而且標明瞭該行記錄所在表數據文件中的物理位置:表空間id爲46,頁碼爲3。
RECORD LOCKS space id 46 page no 3 n bits 80 index GEN_CLUST_INDEX of table `test2`.`gxt` trx id 10025 lock_mode X waiting Record lock, heap no 8 PHYSICAL RECORD: n_fields 5; compact format; info bits 0 0: len 6; hex 000000000606; asc ;; 1: len 6; hex 000000002728; asc '(;; 2: len 7; hex 3b00000130036d; asc ; 0 m;; 3: len 4; hex 80000001; asc ;; 4: len 4; hex 676f6f64; asc good;; ------------------ ---TRANSACTION 10024, ACTIVE 262 sec 2 lock struct(s), heap size 1136, 4 row lock(s), undo log entries 1 MySQL thread id 7, OS thread handle 140209809397504, query id 70 localhost root
二、show full processlist\G;
*************************** 2. row *************************** Id: 8 User: root Host: localhost db: NULL Command: Query #正在運行 Time: 3 State: updating Info: update test2.gxt set name='good boy' where id=1 #運行中的語句 *************************** 3. row ***************************
三、select * from information_schema.innodb_trx\G;
mysql> select * from information_schema.innodb_trx\G; *************************** 1. row *************************** trx_id: 10025 trx_state: LOCK WAIT #該事務在等在鎖 trx_started: 2019-09-11 21:23:43 trx_requested_lock_id: 10025:46:3:8 trx_wait_started: 2019-09-11 21:36:09 trx_weight: 2 trx_mysql_thread_id: 8 trx_query: update test2.gxt set name='good boy' where id=1 trx_operation_state: starting index read trx_tables_in_use: 1 trx_tables_locked: 1 trx_lock_structs: 2 trx_lock_memory_bytes: 1136 trx_rows_locked: 4 trx_rows_modified: 0 trx_concurrency_tickets: 0 trx_isolation_level: REPEATABLE READ trx_unique_checks: 1 trx_foreign_key_checks: 1 trx_last_foreign_key_error: NULL trx_adaptive_hash_latched: 0 trx_adaptive_hash_timeout: 0 trx_is_read_only: 0 trx_autocommit_non_locking: 0 *************************** 2. row *************************** trx_id: 10024 trx_state: RUNNING #該事務正在運行 trx_started: 2019-09-11 21:23:19 trx_requested_lock_id: NULL trx_wait_started: NULL trx_weight: 3 trx_mysql_thread_id: 7 trx_query: NULL trx_operation_state: NULL trx_tables_in_use: 0 trx_tables_locked: 1 trx_lock_structs: 2 trx_lock_memory_bytes: 1136 trx_rows_locked: 4 trx_rows_modified: 1 trx_concurrency_tickets: 0 trx_isolation_level: REPEATABLE READ trx_unique_checks: 1 trx_foreign_key_checks: 1 trx_last_foreign_key_error: NULL trx_adaptive_hash_latched: 0 trx_adaptive_hash_timeout: 0 trx_is_read_only: 0 trx_autocommit_non_locking: 0 2 rows in set (0.00 sec)
四、 select * from information_schema.innodb_locks\G;
mysql> select * from information_schema.innodb_locks\G; *************************** 1. row *************************** lock_id: 10025:46:3:8 lock_trx_id: 10025 lock_mode: X #獨佔鎖 lock_type: RECORD lock_table: `test2`.`gxt` lock_index: GEN_CLUST_INDEX lock_space: 46 lock_page: 3 lock_rec: 8 lock_data: 0x000000000606 #鎖住同一份資源,與下面事務相同,因此這裏有一個須要等待,在innodb_trx表中能夠明顯看出來,如上所示 *************************** 2. row *************************** lock_id: 10024:46:3:8 lock_trx_id: 10024 lock_mode: X lock_type: RECORD lock_table: `test2`.`gxt` lock_index: GEN_CLUST_INDEX lock_space: 46 lock_page: 3 lock_rec: 8 lock_data: 0x000000000606 2 rows in set, 1 warning (0.00 sec)
五、select * from information_schema.innodb_lock_waits\G;
mysql> select * from information_schema.innodb_lock_waits\G; *************************** 1. row *************************** requesting_trx_id: 10025 #申請事務的id ,表示在被阻塞,一直在等待資源 requested_lock_id: 10025:46:3:8 #申請鎖的id blocking_trx_id: 10024 #阻塞事務的id ,表示在運行,阻塞了其餘事務 blocking_lock_id: 10024:46:3:8 #阻塞鎖的id 1 row in set, 1 warning (0.00 sec)
2.2.二、一致性非鎖定讀
一致性非鎖定讀是innodb存儲引擎經過多版本控制的方式來讀取當前執行時間數據庫中行的數據,若是讀取到正在刪除或者修改的記錄,這時讀取的操做不會等待行鎖的釋放,而是直接去讀取快照中的數據。以下圖所示:
一致性非鎖定讀,是由於不須要等待訪問行上的X鎖的釋放。
快照數據只的是該行的之前版本的數據,經過undo段來實現,而undo用來事務中的回滾數據,所以快照數據沒有額外的開銷,此外,讀取數據不要上鎖,由於沒有事務對歷史數據修改。一致性非鎖定讀提升數據庫的併發性(這是innodb的默認讀取方式)。可是在不一樣的事務隔離級別下,讀取方式不一樣,並非每一個事務隔離級別下都採用一致性非鎖定讀,就算採用一致性非鎖定讀,那還有可能就是快照數據的定義各不相同,好比快照能夠有多個版本,因此這種技術稱爲多版本技術,由此帶來的併發控制,稱爲多版本併發控制(MVCC)
例如在事務隔離級別
READ COMMITTED中:一致性非鎖定讀老是讀取被鎖定行的最新一份快照數據;
REPEATABLE READ中:一致性非鎖定讀老是讀取事務開始時的行數據版本;
2.2.三、一致性鎖定讀
雖然innodb在默認狀況下會採用一致性非鎖定讀方式進行讀取,可是某些時候用戶也能夠顯示對數據庫讀取操做進行加鎖以保證數據的邏輯的一致性。
innodb存儲引擎對select語句支持兩種一致性鎖定讀的操做:
select ... for update #對讀取行加X鎖,其餘事務不能對已鎖定的行加任何鎖 select ... lock in share mode #對讀取行加S鎖,其餘事務容許加S鎖,可是若是是X鎖,則會阻塞
2.2.四、自增加與鎖
在innodb的存儲結構中,對每一個含有自增加值的表都有一個自增加計數器,對於這樣的表進行插入數據,則這個計數器會被初始化,插入操做會根據這個自增加的計數器值加1賦予自增加列,這種實現方式成爲AUTO-INC Locking,這種鎖爲了提升插入的性能,鎖並非在一個事務完成後釋放,而是在完成自增加值插入的sql語句後當即釋放,可是仍是存在性能上的問題。
msyql5.1.22版本開始,innodb提供了一種輕量級互斥的自增加實現機制,這個機制大大的提升插入的性能。由參數innodb_automic_lock_mode來控制自增加的模式(默認值爲1)。
自增插入的分類如圖所示:
innodb_automic_lock_mode設置以下圖所示:
還有就是innodb的自增加的實現和myisam不一樣,myisam是表鎖設計,自增加不須要考慮併發插入問題。
2.2.五、外鍵與鎖
在innodb表中,建立外鍵的時候若外鍵列上沒有索引,則會在建立過程當中自動在外鍵列上隱式地建立索引。
存在這樣一種狀況,當向子表中插入數據的時候,會向父表查詢該表中是否存在對應的值以判斷將要插入的記錄是否知足外鍵約束,所以此時使用select ... lock share mode,即主動對父表加S鎖,並在表上加意向共享鎖。若是此時父表上對應的記錄正好有X鎖,那麼該操做就會阻塞。同理,從子表中刪除或更新記錄也是同樣的。
3、鎖帶來的三個問題
經過鎖機制能夠實現事務的隔離性,使得事務更好併發的工做,提升了事務的併發性,可是鎖會帶來三個問題,以下:
一、髒讀
二、不可重複
三、丟失更新
髒讀:
先來了解幾個概念:
髒頁:表示緩衝池重的修改的頁尚未刷新到磁盤中,使得緩衝池中的數據和磁盤數據不一致。髒頁時容許讀取的,頁很是的正常,等刷新到磁盤,最終都會保持一致性。
髒數據:是指事務對緩衝池中的數據進行了修改,可是尚未提交事務。
髒讀:指的是一個事務讀取到了另外一個事務沒有提交的數據,違反了數據庫的隔離性。
髒讀只會發生在事務隔離級別爲READ UNCOMMITTED。而如今數據庫基本都設置成了READ COMMITTED和READ REPEATABLE隔離級別。
不可重複讀:
指在一個事務中屢次讀取同一份數據,在這個事務沒有結束過程當中,其餘事務對這份數據進行了修改,這樣就致使這個事務讀取到的數據和以前一次的數據是不同的,這就是不可重複讀。違反是事務的一致性。當設置隔離級別爲READ COMMITTED時,是容許不可重複讀的狀況。
在innodb存儲引擎中,經過使用Next-Key Lock算法,避免不可重複讀問題,在該算法下,對索引的掃描,不只鎖住掃描到的索引,並且鎖住這些索引覆蓋的範圍。由於在這個範圍能不容許修改,更新,插入,這樣就避免了不可重複讀的現象。innodb默認的事務隔離級別爲READ REPEATABLE隔離級別。
丟失更新:
指的就是一個事務更新的數據會被另外一個事務的更新所覆蓋,從而致使數據不一致。
一、事務1更新行r爲v1,未提交
二、事務2更新行r爲v2,未提交
三、事務1提交
四、事務2提交
其實當前的數據庫都不會形成理論上的數據丟失問題,就算是READ UNCOMMITTED隔離級別,事務2在對r行更新時會被阻塞,要麼等到事務1提交要麼等待超時。
可是會出現下面狀況:
一、用戶1查詢一行數據,存放本地內存
二、用戶2查詢一行數據,存放本地內存
三、用戶1修改更新這行數據,並提交
四、用戶2修改更新這行數據,並提交
這樣就致使用戶1的數據被覆蓋了,能夠經過加x鎖使得步驟1,3串行,2,4串行執行,這樣就能夠避免數據丟失
4、鎖的算法
innodb支持行級鎖,可是它還支持範圍鎖。即對範圍內的行記錄加行鎖。
有三種鎖算法:
1.record lock:即行鎖
2.gap lock:範圍鎖,可是不鎖定行記錄自己
3.next-key lock:範圍鎖加行鎖,即範圍鎖並鎖定記錄自己,gap lock + record lock。
record lock是行鎖,可是它的行鎖鎖定的是key,即基於惟一性索引鍵列來鎖定。若是沒有惟一性索引鍵列,則會自動在隱式列上建立索引並完成鎖定。
next-key lock是行鎖和範圍鎖的結合,innodb對行的鎖申請默認都是這種算法。若是有索引,則只鎖定指定範圍內的索引鍵值,若是沒有索引,則自動建立索引並對整個表進行範圍鎖定。之因此鎖定了表還稱爲範圍鎖定,是由於它實際上鎖的不是表,而是把全部可能的區間都鎖定了,從主鍵值的負無窮到正無窮的全部區間都鎖定,等價於鎖定了表。
假設一個索引有10,11,13,20這四個值,那麼索引可能被next-key lock設置的分區爲:[-無窮,10),[10,11),[11,13),[13,20),[20,+無窮)
若是事務已經鎖定範圍[10,11),[11,13),那麼當插入記錄12時,那麼鎖定範圍將會變成[10,11),[11,12),[12,13)
當查詢的索引有惟一屬性時,innodb會對next-key lock進行優化,降級爲Record Lock。即鎖住索引自己,而不是範圍索引。
若惟一索引由多個列組成,而查詢只是查找多個惟一索引列中的一列,那麼查詢仍是範圍查詢。
若是存在輔助索引,須要對聚簇索引(通常以主鍵)和輔助索引分別鎖定,若是對主鍵中的一個索引進行了行索引,那麼與主鍵對應的輔助索引則是範圍索引;
例如:create table z (a int , b int ,primary key(a) ,key(b));
insert into z values(1,1);
insert into z values(2,3);
insert into z values(3,6);
insert into z values(4,9);
insert into z values(5,11);
當咱們對記錄(3,6)進行鎖定,聚簇索引鎖定的知識a=3這行,而輔助索引鎖住的是(3,6)和(6,9)
5、鎖的其餘狀態(阻塞,死鎖)
阻塞:
指的是因爲不一樣鎖之間多是相互不兼容的,有些時候一個事務的鎖須要等待別的事務釋放鎖以後才能得到資源,這就是阻塞。
innodb_lock_wait_timeout #控制鎖的等待時間,默認50s,不然超時
innndb_rollback_on_timeout #控制是否等待超時事務回滾,默認OFF
死鎖:
指的是兩個或者兩個以上的事務在執行的過程當中,因爭奪資源而形成的相互等待的過現象。
解決方法:
一、等待超時
二、wait-for graph(等待圖),主動監測,innodb採用這種方式
等待圖主要保存了兩種信息:
一、鎖的信息鏈表
二、事務等待鏈表
經過上述的鏈表構造出一張圖,若是在圖中存在迴路,則說明存在死鎖。
innodb存儲引擎經過回滾undo量最小的事務進行回滾。
參考:
《mysql技術內幕:innodb存儲引擎》