您好,登錄后才能下訂單哦!
鎖,在現實生活中是為我們想要隱藏于外界所使用的一種工具。在計算機中,是協調多個進程或縣城并發訪問某一資源的一種機制。在數據庫當中,除了傳統的計算資源(CPU、RAM、I/O等等)的爭用之外,數據也是一種供許多用戶共享訪問的資源。如何保證數據并發訪問的一致性、有效性,是所有數據庫必須解決的一個問題,鎖的沖突也是影響數據庫并發訪問性能的一個重要因素。從這一角度來說,鎖對于數據庫而言就顯得尤為重要。
1、MySQL中的鎖
MySQL中有著Lock和Latch的概念,在數據庫中,這兩者都可以被稱為“鎖”,但是兩者有著截然不同的含義。
Latch一般稱為閂鎖(輕量級的鎖),因為其要求鎖定的時間必須非常短。若持續的時間長,則應用的性能會非常差,在InnoDB引擎中,Latch又可以分為mutex(互斥量)和rwlock(讀寫鎖)。其目的是用來保證并發線程操作臨界資源的正確性,并且通常沒有死鎖檢測的機制。
Lock的對象是事務,用來鎖定的是數據庫中的對象,如表、頁、行。并且一般lock的對象僅在事務commit或rollback后進行釋放(不同事務隔離級別釋放的時間可能不同)。
關于Latch更詳細的講解可以參考:關于MySQL latch爭用深入分析與判斷,本文主要關注的是Lock鎖。
鎖的類型
對數據的操作其實只有兩種,也就是讀和寫,而數據庫在實現鎖時,也會對這兩種操作使用不同的鎖;InnoDB 實現了標準的行級鎖,也就是共享鎖(Shared Lock)和互斥鎖(Exclusive Lock)。
共享鎖(讀鎖),允許事務讀一行數據。
排他鎖(寫鎖),允許事務刪除或更新一行數據。
而它們的名字也暗示著各自的另外一個特性,共享鎖之間是兼容的,而互斥鎖與其他任意鎖都不兼容:
稍微對它們的使用進行思考就能想明白它們為什么要這么設計,因為共享鎖代表了讀操作、互斥鎖代表了寫操作,所以我們可以在數據庫中并行讀,但是只能串行寫,只有這樣才能保證不會發生線程競爭,實現線程安全。
鎖的粒度
Lock鎖根據粒度主要分為表鎖、頁鎖和行鎖。不同的存儲引擎擁有的鎖粒度都不同。
表鎖
表級別的鎖定是MySQL各存儲引擎中最大顆粒度的鎖定機制。該鎖定機制最大的特點是實現邏輯非常簡單,帶來的系統負面影響最小。所以獲取鎖和釋放鎖的速度很快。由于表級鎖一次會將整個表鎖定,所以可以很好的避免困擾我們的死鎖問題。
當然,鎖定顆粒度大所帶來最大的負面影響就是出現鎖定資源爭用的概率也會最高,致使并發度大打折扣。
使用表級鎖定的主要是MyISAM,MEMORY,CSV等一些非事務性存儲引擎。
表鎖的語法很簡單:
# 獲取表鎖 LOCK TABLES tbl_name [[AS] alias] lock_type [, tbl_name [[AS] alias] lock_type] ... lock_type: READ [LOCAL] | [LOW_PRIORITY] WRITE # 釋放表鎖 UNLOCK TABLES
MyISAM在執行查詢前,會自動執行表的加鎖、解鎖操作,一般情況下不需要用戶手動加、解鎖,但是有的時候也需要顯示加鎖。比如:檢索某一個時刻t1,t2表中數據數量。
LOCK TABLE t1 read, t2 read; select count(t1.id1) as 'sum' from t1; select count(t2.id1) as 'sum' from t2; UNLOCK TABLES;
頁鎖
頁級鎖定是MySQL中比較獨特的一種鎖定級別,在其他數據庫管理軟件中也并不是太常見。頁級鎖定的特點是鎖定顆粒度介于行級鎖定與表級鎖之間,所以獲取鎖定所需要的資源開銷,以及所能提供的并發處理能力也同樣是介于上面二者之間。另外,頁級鎖定和行級鎖定一樣,會發生死鎖。
在數據庫實現資源鎖定的過程中,隨著鎖定資源顆粒度的減小,鎖定相同數據量的數據所需要消耗的內存數量是越來越多的,實現算法也會越來越復雜。不過,隨著鎖定資源顆粒度的減小,應用程序的訪問請求遇到鎖等待的可能性也會隨之降低,系統整體并發度也隨之提升。
使用頁級鎖定的主要是BerkeleyDB存儲引擎。
行鎖
行級鎖定最大的特點就是鎖定對象的粒度很小,也是目前各大數據庫管理軟件所實現的鎖定顆粒度最小的。由于鎖定顆粒度很小,所以發生鎖定資源爭用的概率也最小,能夠給予應用程序盡可能大的并發處理能力而提高一些需要高并發應用系統的整體性能。
雖然能夠在并發處理能力上面有較大的優勢,但是行級鎖定也因此帶來了不少弊端。由于鎖定資源的顆粒度很小,所以每次獲取鎖和釋放鎖需要做的事情也更多,帶來的消耗自然也就更大了。此外,行級鎖定也最容易發生死鎖。
使用行級鎖定的主要是InnoDB存儲引擎。
總結
表級鎖:開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖沖突的概率最高,并發度最低。
行級鎖:開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖沖突的概率最低,并發度也最高。
頁面鎖:開銷和加鎖時間界于表鎖和行鎖之間;會出現死鎖;鎖定粒度界于表鎖和行鎖之間,并發度一般。
從鎖的角度來說,表級鎖更適合于以查詢為主,只有少量按索引條件更新數據的應用,如Web應用;而行級鎖則更適合于有大量按索引條件并發更新少量不同數據,同時又有并發查詢的應用,如一些在線事務處理(OLTP)系統。
2、InnoDB中的鎖
意向鎖
上節提到InnoDB 支持多種粒度的鎖,也就是行鎖和表鎖。為了支持多粒度鎖定,InnoDB 存儲引擎引入了意向鎖(Intention Lock)。
那什么是意向鎖呢?我們在這里可以舉一個例子:如果沒有意向鎖,當已經有人使用行鎖對表中的某一行進行修改時,如果另外一個請求要對全表進行修改,那么就需要對所有的行是否被鎖定進行掃描,在這種情況下,效率是非常低的;不過,在引入意向鎖之后,當有人使用行鎖對表中的某一行進行修改之前,會先為表添加意向互斥鎖(IX),再為行記錄添加互斥鎖(X),在這時如果有人嘗試對全表進行修改就不需要判斷表中的每一行數據是否被加鎖了,只需要通過等待意向互斥鎖被釋放就可以了。
與上一節中提到的兩種鎖的種類相似的是,意向鎖也分為兩種:
意向共享鎖(IS):事務想要在獲得表中某些記錄的共享鎖,需要在表上先加意向共享鎖。
意向互斥鎖(IX):事務想要在獲得表中某些記錄的互斥鎖,需要在表上先加意向互斥鎖。
隨著意向鎖的加入,鎖類型之間的兼容矩陣也變得愈加復雜:
意向鎖其實不會阻塞全表掃描之外的任何請求,它們的主要目的是為了表示是否有人請求鎖定表中的某一行數據。
行鎖的算法
InnoDB存儲引擎有3種行鎖的算法,其分別是:
Record Lock:單個行記錄上的鎖。
Gap Lock:間隙鎖,鎖定一個范圍,但不包含記錄本身。
Next-Key Lock:Gap Lock+Record Lock,鎖定一個范圍,并且鎖定記錄本身。
Record Lock總是會去鎖住索引記錄,如果InnoDB存儲引擎表在建立的時候沒有設置任何一個索引,那么這時InnoDB存儲引擎會使用隱式的主鍵來進行鎖定。
Next-Key Lock是結合了Gap Lock和Record Lock的一種鎖定算法,在Next-Key Lock算法下,InnoDB對于行的查詢都是采用這種鎖定算法。例如有一個索引有10,11,13和20這4個值,那么該索引可能被Next-Key Locking的區間為:
除了Next-Key Locking,還有Previous-Key Locking技術。同樣上述的值,使用Previous-Key Locking技術,那么可鎖定的區間為:
但是不是所有索引都會加上Next-key Lock的,在查詢的列是唯一索引(包含主鍵索引)的情況下,Next-key Lock會降級為Record Lock。
接下來,我們來通過一個例子解釋一下。
CREATE TABLE z ( a INT, b INT, PRIMARY KEY(a), // a是主鍵索引 KEY(b) // b是普通索引 ); INSERT INTO z select 1, 1; INSERT INTO z select 3, 1; INSERT INTO z select 5, 3; INSERT INTO z select 7, 6; INSERT INTO z select 10, 8;
這時候在會話A中執行 SELECT * FROM z WHERE b = 3 FOR UPDATE ,索引鎖定如下:
這時候會話B執行的語句落在鎖定范圍內的都會進行waiting
SELECT * FROM z WHERE a = 5 LOCK IN SHARE MODE; INSERT INTO z SELECT 4, 2; INSERT INTO z SELECT 6, 5;
用戶可以通過以下兩種方式來顯示的關閉Gap Lock:
將事務的隔離級別設為 READ COMMITED。
將參數innodb_locks_unsafe_for_binlog設置為1。
從上面的例子可以看出來,Gap Lock的作用是為了阻止多個事務將記錄插入到同一個范圍內,設計它的目的是用來解決Phontom Problem(幻讀問題)。在MySQL默認的隔離級別(Repeatable Read)下,InnoDB就是使用它來解決幻讀問題。
幻讀是指在同一事務下,連續執行兩次同樣的SQL語句可能導致不同的結果,第二次的SQL可能會返回之前不存在的行,也就是第一次執行和第二次執行期間有其他事務往里插入了新的行。
一致性非鎖定讀
一致性非鎖定讀(consistent nonlocking read)是指InnoDB存儲引擎通過多版本控制(MVCC)的方式來讀取當前執行時間數據庫中行的數據。如果讀取的這行正在執行DELETE或UPDATE操作,這時讀取操作不會向XS鎖一樣去等待鎖釋放,而是會去讀一個快照數據。MVCC相關的知識我已經在另外一篇文章中闡述了,這里就不做過多原理的分析了。地址:談談MySQL InnoDB存儲引擎事務的ACID特性
在事務隔離級別RC和RR下,InnoDB存儲引擎使用非鎖定的一致性讀。然而對于快照數據的定義卻不同,在RC級別下,對于快照數據,非一致性讀總是讀取被鎖定行的最新一份快照數據。而在RR級別下,對于快照數據,非一致性讀總是讀取事務開始時的行數據版本。
下面我們通過一個例子來看看大家是否對MVCC理解了。
可以看到,第1步和第2步是非常容易理解的,而在第3步事務B插入一條新的數據后,在第4步事務A還是查不到,也就是利用了MVCC的特性來實現。當事務B提交后,第5步的查詢在RC和RR隔離級別下的輸出是不同的,這個的原因在另一篇博客中也說到了,是因為他們創建ReadView的時機不同。
但是很詭異的是在第6步的時候,事務A更新了一條它看不見的記錄,然后查詢就能夠查詢出來了。這里很多人容易迷惑,不可見不代表記錄不存在,它只是利用了可見性判斷忽略了而已。更新成功之后,事務A順其自然的記錄了這條記錄的Undo log,在隨后的查詢中,因為它能夠看見自己的改動這一個可見性的判斷,自然就能夠查詢出來了。這里很多名詞需要去深入讀一下此文:談談MySQL InnoDB存儲引擎事務的ACID特性
一致性鎖定讀
前面說到,在默認隔離級別RR下,InnoDB存儲引擎的SELECT操作使用一致性非鎖定讀。但是在某些情況下,用戶需要顯式地對數據庫讀取操作進行加鎖以保證數據邏輯的一致性。InnoDB存儲引擎對于SELECT語句支持兩種一致性的鎖定讀(locking read)操作。
SELECT … FOR UPDATE (X鎖)
SELECT … LOCK IN SHARE MODE (S鎖)
3、鎖帶來的問題
通過鎖定機制可以實現事務隔離性要求,使得事務可以并發的工作。鎖提高了并發,但是卻會帶來潛在的問題。不過好在有事務隔離性的要求,不同的隔離級別解決的鎖的問題也不同,這里只進行簡單的介紹,不進行舉例分析了。
InnoDB存儲引擎在RR級別就已經解決了所有問題,但是它和Serializable的區別在哪里呢?區別就在于RR級別還存在一個丟失更新問題,而SERIALIZABLE無論對于查詢還是更新都會進行鎖定操作。
如圖所示,用戶原始金額為100,如果程序中對于轉賬和存款的判斷是先查詢再更新的話就會出現丟失更新的問題,也就是后面的更新覆蓋了前面的更新。如果想避免這種問題,只能每次更新的時候金額基于表里最新的值來做。如果必須要先查詢再更新,可以在更新的條件里判斷金額(樂觀鎖),也可以使用隔離級別最高的SERIALIZABLE。
4、死鎖
死鎖是指兩個或兩個以上的事務在執行過程中,因爭奪鎖資源而造成的一種互相等待的現象,這里直接放上之前項目中遇到的一個死鎖問題以及深入的分析:由一次線上問題帶來的MySQL死鎖問題分析,這里就不再贅述了。
以上就是MySQL的鎖詳解的詳細內容,更多請關注億速云其它相關文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。