文章目錄
- 為什么需要鎖
- 鎖的基本原理
- 鎖的型別
- 行級鎖
- 表級鎖
- 意向鎖
- 自增鎖
- 鎖的演算法
- 記錄鎖
- 間隙鎖
- 臨鍵鎖
- 鎖問題
- 臟讀
- 不可重復讀
- 丟失更新
為什么需要鎖
鎖機制用于管理對共享資源的并發訪問,
鎖不僅是資料庫事務實作不同級別隔離性的手段,由其帶來的所沖突也是影響資料庫并發訪問性能的一個重要因素,
-
MyISAM是表級鎖,并發讀沒有問題,但并發插入性能較差,
-
Microsoft SQL Server資料庫,在2005版本之前都是頁鎖,相對MyISAM并發性能有所提升,但對于熱點資料頁的并發問題依然無能為力,
-
InnoDB存盤引擎鎖的實作與Oracle非常相似,提供一致性的非鎖定讀、行級鎖支持,
鎖的基本原理
當我們說的MySQL鎖住了這一行,其實并不會真正的鎖住對應記錄,而是鎖住相應的索引,具體的鎖哪些索引是根據查詢條件來決定的,
因為MySQL的表的資料,本身通過1個或多個B+樹索引來組織的,其中主鍵索引包含了完整的每一行的資料,非主鍵索引記錄的是到行記錄對應的主鍵索引的位置,所以,通過在索引記錄上進行加鎖操作,可以有效的讀取、插入、修改、洗掉進行沖突判斷,
如果這個表沒有索引,是不是就加不了鎖?這是不可能的,因為MySQL的每一個表至少有一個隱藏的聚簇索引,即主鍵索引,
鎖的型別
行級鎖
InnoDB存盤引擎實作了如下兩種標準的行級鎖:
-
共享鎖(S Lock),允許事務讀一行的資料,
-
排它鎖(X Lock),允許事務洗掉或更新一行的資料,
行鎖即只會鎖住一行,它的原理即是在對應的索引記錄上加鎖;
具有并發度高、鎖沖突的概率低的優勢,相對而言,由于粒度小,行鎖成本高;
舉例來說,當一行記錄加了排它行鎖的時候,其它事務是不能對這行記錄進行修改的,但其他的行則不受此鎖的影響,
但需要注意的是,在RR模型下進行更新一行記錄時,如查詢條件所在的列并無索引時,會退化成在主鍵索引對應的記錄上全部加鎖,即鎖表,
表級鎖
InnoDB同時支持表鎖,表鎖又分為表級讀鎖和表級寫鎖,具體語法是
LOCK TABLES XXX READ|WRITE
如表加了讀鎖時,這個表進入了只讀模式,其它會話不能對此表進行修改;
如表加了寫鎖,則此表進入獨占模式,其它表的讀和寫都會被阻塞,一般用于特殊的場景,如drop table或者truncate table的場景
表鎖實作原理,不是由InnoDB存盤引擎層管理的,而是由其上一層MySQL Server負責的,需要注意以下兩點:
(1)需要設定autocommit=0,innodb_table_lock=1(也是默認設定),InnoDB層才能知道MySQL加的表鎖
(2)在事務結束前,不要用UNLOCAK TABLES釋放表鎖,因為UNLOCK TABLES會隱含地提交事務;同時,COMMIT或ROLLBACK不能釋放由LOCAK TABLES加的表級鎖,必須用UNLOCK TABLES釋放表鎖,
意向鎖
InnoDB存盤引擎支持意向鎖的設計比較簡練,其意向鎖即為表級鎖,設計目的主要是為了在一個事務中揭示下一行將被請求的鎖型別,
其支持兩種意向鎖:
意向共享鎖(IS Lock),事務想要獲得一張表中某幾行的共享鎖,
意向排它鎖(IX Lock),事務想要獲得一張表中某幾行的排它鎖,
由于InnoDB存盤引擎支持的是行級別鎖,因此意向鎖不會阻塞除全表掃描以外的任何請求,
InnoDB存盤引擎中鎖的兼容性
| 鎖 | IS | IX | S | X |
|---|---|---|---|---|
| IS | 兼容 | 兼容 | 兼容 | 不兼容 |
| IX | 兼容 | 兼容 | 不兼容 | 不兼容 |
| S | 兼容 | 兼容 | 兼容 | 不兼容 |
| X | 不兼容 | 不兼容 | 不兼容 | 不兼容 |
簡而言之,排它鎖不兼容任何鎖;意向排它鎖不兼容共享鎖;其他場景均可兼容,
自增鎖
自增長是資料庫一種常見屬性,也是很多DBA或開發人員首選的主鍵方式,
在InnoDB存盤引擎的記憶體結構中,對每個含有自增長值的表都有一個自增長計數器(auto-increment counter),
當對含有自增長的計數器的表進行插入操作時,這個計數器會被初始化,執行如下的陳述句來得到計數器的值,
SELECT MAX(auto_inc_col) FROM t FOR UPDATE;
這個實作方式稱作AUTO-INC Locking,這種鎖其實是采用一種特殊的表鎖機制,為了提高插入的性能,鎖不是在一個事務完成后才釋放,而是在完成對自增長值插入的SQL陳述句后立即釋放,
鎖的演算法
-
Record Lock:單個行記錄上的鎖,
-
Gap Lock:間隙鎖,鎖定一個范圍,但不包含記錄本身,
-
Next-Key Lock:Gap Lock + Record Lock,鎖定一個范圍,并且鎖定記錄本身,
記錄鎖
Record Lock總是會鎖住索引記錄,如果沒有設定索引,會使用隱式的主鍵來進行鎖定,
間隙鎖
間隙鎖(gap lock)是RR模式下,為了防止幻讀而設計出來的,它鎖住的是一個區間(開區間),當一個區間被加了間隙鎖時,是無法執行插入的,
它的實作原理是,在對應的索引記錄范圍進行加鎖,是一個左右均是開區間,
TABLE `test` (
`id` bigint(20) unsigned NOT NULL COMMENT '主鍵',
`key` bigint(20) DEFAULT NULL COMMENT 'k',
PRIMARY KEY (`id`),
KEY `k` (`k`)
)
如果(2,2)和(6,6)之間被加了間隙鎖,則事物2和事物3的插入,將會被阻塞,而事物1和事物4,則不會被阻塞,
需要注意的是,間隙鎖本身之間是不會相互沖突的,它的唯一作用就是阻止在間隙內插入新的行,
臨鍵鎖
臨鍵鎖即next-key lock,是行鎖和它之前的間隙共同構成的鎖,即一個前開后閉的加鎖區間,
然而,當查詢的索引含有唯一屬性時,InnoDB存盤引擎會對Next-Key Lock進行優化,降級為Record Lock,即僅鎖住索引本身,而不是范圍,
需要注意的是,對于唯一鍵值的鎖定,Next-Key Lock降級為Record Lock僅存在于查詢所有的唯一索引列,若唯一索引由多個列組成,而查詢僅是查找多個唯一索引列中的其中一個,那么查詢其實是range型別,而不是point型別,依然會使用Next-Key Lock進行鎖定,
由于間隙鎖和臨鍵鎖的加鎖規則比較復雜,這里參考林曉斌的總結為2個原則,2個優化,1個bug:
-
原則1:RR模式加鎖的基本單位是next-key lock,即前開后閉的區間
-
原則2:查找程序中訪問到的物件才會加鎖
-
優化1:唯一索引上的等值查詢,next-key lock會退化為行鎖
-
優化2:索引上的等值查詢,向右遍歷的時候,最后一個值不滿足等值條件的時候會退化為間隙鎖,
-
一個bug:唯一索引上的范圍查詢,會訪問到不滿足條件的第一個值為止,
這里舉例來說明
TABLE `test` (
`id` int(11) unsigned NOT NULL COMMENT '主鍵',
`c` int(11) DEFAULT NULL,
`d` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `k` (`c`)
)
insert into t values (0,0,0),(5,5,5), (10,10,10),(15,15,15), (20,20,20),(25,25,25)
例子一:唯一索引查詢間隙鎖,sql如下:
update t set d=d+1 where id=7;
根據加鎖規則1,加鎖范圍是(5,10]
同時由于優化2,最終的加鎖范圍是(5,10).
例子二:非唯一索引等值鎖
select id from t where c=5 lock in share mode;
1)根據加鎖原則1,加鎖區間是(0,5],
2)但由于c是普通索引,需要繼續向右遍歷,直到查詢到c=10才會放棄
3)根據優化2,由于10不滿足等值判斷,因此會退化成間隙鎖(5,10)
4)根據原則2,只有訪問到的物件才會加鎖,這個查詢本身是有覆寫索引,并不需要訪問主鍵,因此id主鍵上并沒有鎖,因此不會阻塞如下sql
update t set d=d+1 where id=5
例子三:主鍵索引范圍鎖
考慮如下兩個SQL,語意上等同的,但是對加鎖的效果確是不一樣的,
select * from t where id=10 for update;
select * from t where id>=10 and id < 11 for update;
我們都知道第一條陳述句,會退化成id=10的行鎖,
第二條陳述句,其實是id=10和 10<id<11的兩個查找聯合加鎖結果,對于id=10,會加一個行鎖,對于10<id<11會加一個(10,15]的臨鍵鎖
鎖問題
臟讀
臟頁:在緩沖池中已經被修改的頁,但是還沒重繪到磁盤中,即資料庫實體記憶體中的頁和磁盤中的頁的資料是不一致的,當然在重繪到磁盤之前,日志都已經被寫入到了重做日志檔案中,
而所謂臟資料是指事務對緩沖池中行記錄的修改,并且還沒有被提交(commit),在一個事務中可以讀到另一個事務中未提交的資料(臟資料),顯然違反了資料庫的隔離性,
主從拷貝程序的slave節點,并且slave節點上的查詢不需要特別精確的回傳值的情況下,可以采用臟讀隔離級別,
臟讀本身違反了事務的隔離性,
不可重復讀
不可重復讀是指在一個事務內多次讀取到同一資料集合(范圍查詢),且兩次讀到的資料可能不一樣(其他事務對部分資料進行修改),這種情況稱為不可重復讀,
不可重復讀本身違反了事務的一致性,
丟失更新
丟失更新是另一個鎖導致的問題,簡單來說就是一個事務的更新操作會被另一個事務的更新操作所覆寫,從而導致資料的不一致,
事務T1將行記錄R更新為V1,但不提交,
事務T2將行記錄R更新為V2,但不提交,
事務T1提交,
事務T2提交,
此時事務T1的更新就被事務T2覆寫了,但事實上,在當前資料庫的任何隔離級別下,都不會導致資料庫理論意義上的丟失更新問題,
但可能存在邏輯上的丟失更新,將上述程序想象成,用戶查詢資料到界面,更新資料,但未提交至資料庫,
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/250280.html
標籤:其他
上一篇:JDBC鏈接資料庫
