您好,登錄后才能下訂單哦!
原文鏈接:blog.ouyangsihai.cn >> MySQL的又一神器-鎖,MySQL面試必備
在生活中鎖的例子多的不能再多了,從古老的簡單的門鎖,到密碼鎖,再到現在的指紋解鎖,人臉識別鎖,這都是鎖的鮮明的例子,所以,我們理解鎖應該是非常簡單的。
再到MySQL中的鎖,對于MySQL來說,鎖是一個很重要的特性,數據庫的鎖是為了支持對共享資源進行并發訪問,提供數據的完整性和一致性,這樣才能保證在高并發的情況下,訪問數據庫的時候,數據不會出現問題。
在數據庫中,lock和latch都可以稱為鎖,但是意義卻不同。
Latch一般稱為閂鎖
(輕量級的鎖),因為其要求鎖定的時間必須非常短。若持續的時間長,則應用的性能會非常差,在InnoDB引擎中,Latch又可以分為mutex
(互斥量)和rwlock
(讀寫鎖)。其目的是用來保證并發線程操作臨界資源的正確性,并且通常沒有死鎖檢測的機制。
Lock的對象是事務
,用來鎖定的是數據庫中的對象,如表、頁、行。并且一般lock的對象僅在事務commit或rollback后進行釋放(不同事務隔離級別釋放的時間可能不同)。
在數據庫中,鎖的粒度的不同可以分為表鎖、頁鎖、行鎖,這些鎖的粒度之間也是會發生升級的,鎖升級的意思就是講當前鎖的粒度降低,數據庫可以把一個表的1000個行鎖升級為一個頁鎖,或者將頁鎖升級為表鎖,下面分別介紹一下這三種鎖的粒度(參考自博客:https://blog.csdn.net/baolingye/article/details/102506072)。
表級別的鎖定是MySQL各存儲引擎中最大顆粒度的鎖定機制。該鎖定機制最大的特點是實現邏輯非常簡單,帶來的系統負面影響最小。所以獲取鎖和釋放鎖的速度很快。由于表級鎖一次會將整個表鎖定,所以可以很好的避免困擾我們的死鎖問題。
當然,鎖定顆粒度大所帶來最大的負面影響就是出現鎖定資源爭用的概率也會最高,致使并大度大打折扣。
使用表級鎖定的主要是MyISAM,MEMORY,CSV等一些非事務性存儲引擎。
特點: 開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖沖突的概率最高,并發度最低。
頁級鎖定是MySQL中比較獨特的一種鎖定級別,在其他數據庫管理軟件中也并不是太常見。頁級鎖定的特點是鎖定顆粒度介于行級鎖定與表級鎖之間,所以獲取鎖定所需要的資源開銷,以及所能提供的并發處理能力也同樣是介于上面二者之間。另外,頁級鎖定和行級鎖定一樣,會發生死鎖。
在數據庫實現資源鎖定的過程中,隨著鎖定資源顆粒度的減小,鎖定相同數據量的數據所需要消耗的內存數量是越來越多的,實現算法也會越來越復雜。不過,隨著鎖定資源 顆粒度的減小,應用程序的訪問請求遇到鎖等待的可能性也會隨之降低,系統整體并發度也隨之提升。
使用頁級鎖定的主要是BerkeleyDB存儲引擎。
特點: 開銷和加鎖時間界于表鎖和行鎖之間;會出現死鎖;鎖定粒度界于表鎖和行鎖之間,并發度一般。
行級鎖定最大的特點就是鎖定對象的粒度很小,也是目前各大數據庫管理軟件所實現的鎖定顆粒度最小的。由于鎖定顆粒度很小,所以發生鎖定資源爭用的概率也最小,能夠給予應用程序盡可能大的并發處理能力而提高一些需要高并發應用系統的整體性能。
雖然能夠在并發處理能力上面有較大的優勢,但是行級鎖定也因此帶來了不少弊端。由于鎖定資源的顆粒度很小,所以每次獲取鎖和釋放鎖需要做的事情也更多,帶來的消耗自然也就更大了。此外,行級鎖定也最容易發生死鎖。
特點: 開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖沖突的概率最低,并發度也最高。
比較表鎖我們可以發現,這兩種鎖的特點基本都是相反的,而從鎖的角度來說,表級鎖更適合于以查詢為主,只有少量按索引條件更新數據的應用,如Web應用;而行級鎖則更適合于有大量按索引條件并發更新少量不同數據,同時又有并發查詢的應用,如一些在線事務處理(OLTP)系統。
InnoDB存儲引擎中存在著不同類型的鎖,下面一一介紹一下。
S or X (共享鎖、排他鎖)
數據的操作其實只有兩種,也就是讀和寫,而數據庫在實現鎖時,也會對這兩種操作使用不同的鎖;InnoDB 實現了標準的行級鎖,也就是共享鎖(Shared Lock)和互斥鎖(Exclusive Lock)。
IS or IX (共享、排他)意向鎖
為了允許行鎖和表鎖共存,實現多粒度鎖機制,InnoDB存儲引擎支持一種額外的鎖方式,就稱為意向鎖,意向鎖在 InnoDB 中是表級鎖,意向鎖分為:
另外,這些鎖之間的并不是一定可以共存的,有些鎖之間是不兼容的,所謂兼容性就是指事務 A 獲得一個某行某種鎖之后,事務 B 同樣的在這個行上嘗試獲取某種鎖,如果能立即獲取,則稱鎖兼容,反之叫沖突。
下面我們再看一下這兩種鎖的兼容性。
這里用一個思維導圖把前面的概念做一個小結。
在一個事務中查詢數據時,普通的SELECT語句不會對查詢的數據進行加鎖,其他事務仍可以對查詢的數據執行更新和刪除操作。因此,InnoDB提供了兩種類型的鎖定讀來保證額外的安全性:
SELECT ... LOCK IN SHARE MODE
SELECT ... FOR UPDATE
SELECT ... LOCK IN SHARE MODE
: 對讀取的行添加S鎖,其他事物可以對這些行添加S鎖,若添加X鎖,則會被阻塞。
SELECT ... FOR UPDATE
: 會對查詢的行及相關聯的索引記錄加X鎖,其他事務請求的S鎖或X鎖都會被阻塞。 當事務提交或回滾后,通過這兩個語句添加的鎖都會被釋放。 注意:只有在自動提交被禁用時,SELECT FOR UPDATE才可以鎖定行,若開啟自動提交,則匹配的行不會被鎖定。
一致性非鎖定讀(consistent nonlocking read) 是指InnoDB存儲引擎通過多版本控制(MVVC)讀取當前數據庫中行數據的方式。如果讀取的行正在執行DELETE或UPDATE操作,這時讀取操作不會因此去等待行上鎖的釋放。相反地,InnoDB會去讀取行的一個快照。所以,非鎖定讀機制大大提高了數據庫的并發性。
一致性非鎖定讀是InnoDB默認的讀取方式,即讀取不會占用和等待行上的鎖。在事務隔離級別READ COMMITTED
和REPEATABLE READ
下,InnoDB使用一致性非鎖定讀。
然而,對于快照數據的定義卻不同。在READ COMMITTED
事務隔離級別下,一致性非鎖定讀總是讀取被鎖定行的最新一份快照數據。而在REPEATABLE READ
事務隔離級別下,則讀取事務開始時的行數據版本。
下面我們通過一個簡單的例子來說明一下這兩種方式的區別。
首先創建一張表;
插入一條數據;
insert into lock_test values(1);
查看隔離級別;
select @@tx_isolation;
下面分為兩種事務進行操作。
在REPEATABLE READ
事務隔離級別下;
在REPEATABLE READ
事務隔離級別下,讀取事務開始時的行數據,所以當會話B修改了數據之后,通過以前的查詢,還是可以查詢到數據的。
在READ COMMITTED
事務隔離級別下;
在READ COMMITTED
事務隔離級別下,讀取該行版本最新的一個快照數據,所以,由于B會話修改了數據,并且提交了事務,所以,A讀取不到數據了。
InnoDB存儲引擎有3種行鎖的算法,其分別是:
Record Lock:總是會去鎖住索引記錄,如果InnoDB存儲引擎表在建立的時候沒有設置任何一個索引,那么這時InnoDB存儲引擎會使用隱式的主鍵來進行鎖定。
Next-Key Lock:結合了Gap Lock和Record Lock的一種鎖定算法,在Next-Key Lock算法下,InnoDB對于行的查詢都是采用這種鎖定算法。舉個例子10,20,30,那么該索引可能被Next-Key Locking的區間為:
除了Next-Key Locking,還有Previous-Key Locking技術,這種技術跟Next-Key Lock正好相反,鎖定的區間是區間范圍和前一個值。同樣上述的值,使用Previous-Key Locking技術,那么可鎖定的區間為:
不是所有索引都會加上Next-key Lock的,這里有一種特殊的情況,在查詢的列是唯一索引(包含主鍵索引)的情況下,Next-key Lock
會降級為Record Lock
。
接下來,我們來通過一個例子解釋一下。
CREATE TABLE test (
x INT,
y INT,
PRIMARY KEY(x), // x是主鍵索引
KEY(y) // y是普通索引
);
INSERT INTO test select 3, 2;
INSERT INTO test select 5, 3;
INSERT INTO test select 7, 6;
INSERT INTO test select 10, 8;
我們現在會話A中執行如下語句;
SELECT * FROM test WHERE y = 3 FOR UPDATE
我們分析一下這時候的加鎖情況。
用戶可以通過以下兩種方式來顯示的關閉Gap Lock:
Gap Lock的作用:是為了阻止多個事務將記錄插入到同一個范圍內,設計它的目的是用來解決Phontom Problem(幻讀問題)。在MySQL默認的隔離級別(Repeatable Read)下,InnoDB就是使用它來解決幻讀問題。
幻讀:是指在同一事務下,連續執行兩次同樣的SQL語句可能導致不同的結果,第二次的SQL可能會返回之前不存在的行,也就是第一次執行和第二次執行期間有其他事務往里插入了新的行。
臟讀: 在不同的事務下,當前事務可以讀到另外事務未提交的數據。另外我們需要注意的是默認的MySQL隔離級別是REPEATABLE READ
是不會發生臟讀的,臟讀發生的條件是需要事務的隔離級別為READ UNCOMMITTED
,所以如果出現臟讀,可能就是這種隔離級別導致的。
下面我們通過一個例子看一下。
從上面這個例子可以看出,當我們的事務的隔離級別為READ UNCOMMITTED
的時候,在會話A還沒有提交時,會話B就能夠查詢到會話A沒有提交的數據。
不可重復讀: 是指在一個事務內多次讀取同一集合的數據,但是多次讀到的數據是不一樣的,這就違反了數據庫事務的一致性的原則。但是,這跟臟讀還是有區別的,臟讀的數據是沒有提交的,但是不可重復讀的數據是已經提交的數據。
我們通過下面的例子來看一下這種問題的發生。
從上面的例子可以看出,在A的一次會話中,由于會話B插入了數據,導致兩次查詢的結果不一致,所以就出現了不可重復讀的問題。
我們需要注意的是不可重復讀讀取的數據是已經提交的數據,事務的隔離級別為READ COMMITTED
,這種問題我們是可以接受的。
如果我們需要避免不可重復讀的問題的發生,那么我們可以使用Next-Key Lock算法(設置事務的隔離級別為READ REPEATABLE
)來避免,在MySQL中,不可重復讀問題就是Phantom Problem,也就是幻像問題。
丟失更新:指的是一個事務的更新操作會被另外一個事務的更新操作所覆蓋,從而導致數據的不一致。在當前數據庫的任何隔離級別下都不會導致丟失更新問題,要出現這個問題,在多用戶計算機系統環境下有可能出現這種問題。
如何避免丟失更新的問題呢,我們只需要讓事務的操作變成串行化,不要并行執行就可以。
我們一般使用SELECT ... FOR UPDATE
語句,給操作加上一個排他X鎖。
這里我們做一個小結,主要是在不同的事務的隔離級別下出現的問題的對照,這樣就更加清晰了。
文章有不當之處,歡迎指正,如果喜歡微信閱讀,你也可×××學java`,獲取優質學習資源。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。