数据库系列:MySQL InnoDB锁机制介绍
數(shù)據(jù)庫(kù)系列:MySQL慢查詢(xún)分析和性能優(yōu)化
數(shù)據(jù)庫(kù)系列:MySQL索引優(yōu)化總結(jié)(綜合版)
數(shù)據(jù)庫(kù)系列:高并發(fā)下的數(shù)據(jù)字段變更
數(shù)據(jù)庫(kù)系列:覆蓋索引和規(guī)避回表
數(shù)據(jù)庫(kù)系列:數(shù)據(jù)庫(kù)高可用及無(wú)損擴(kuò)容
數(shù)據(jù)庫(kù)系列:使用高區(qū)分度索引列提升性能
數(shù)據(jù)庫(kù)系列:前綴索引和索引長(zhǎng)度的取舍
數(shù)據(jù)庫(kù)系列:MySQL引擎MyISAM和InnoDB的比較
數(shù)據(jù)庫(kù)系列:InnoDB下實(shí)現(xiàn)高并發(fā)控制
數(shù)據(jù)庫(kù)系列:事務(wù)的4種隔離級(jí)別
數(shù)據(jù)庫(kù)系列:RR和RC下,快照讀的區(qū)別
1 背景
隨著互聯(lián)網(wǎng)的發(fā)展,高并發(fā)業(yè)務(wù)的盛行,MySQL InnoDB引擎的細(xì)粒度行鎖,變成很核心的特性之一。
在并發(fā)高的情況下,如果使用不當(dāng),會(huì)導(dǎo)致嚴(yán)重的性能問(wèn)題。比如細(xì)粒度行鎖,是實(shí)現(xiàn)在索引記錄上的,但如果沒(méi)有命中索引,就回退化成表鎖,那對(duì)性能是災(zāi)難的。
下面我們從索引角度出發(fā), 介紹下MySQL InnoDB的鎖機(jī)制。
2 InnoDB的索引回顧
Innodb中有2種索引:主鍵索引(也叫聚集索引 Clustered Index)、輔助索引(也叫非聚集索引 Secondary Index)。
主鍵索引: 每個(gè)表只有一個(gè)主鍵索引,b+樹(shù)結(jié)構(gòu),葉子節(jié)點(diǎn)存儲(chǔ)主鍵的值以及對(duì)應(yīng)整條記錄的數(shù)據(jù),非葉子節(jié)點(diǎn)不存儲(chǔ)記錄的數(shù)據(jù),只存儲(chǔ)主鍵的值。
當(dāng)表中未指定主鍵時(shí),MySQL內(nèi)部會(huì)自動(dòng)給每條記錄添加一個(gè)隱藏的rowid字段(默認(rèn)4個(gè)字節(jié))作為主鍵,用rowid構(gòu)建聚集索引。聚集索引在MySQL中即主鍵索引。
輔助索引: 每個(gè)表可以有多個(gè)輔助索引,b+樹(shù)結(jié)構(gòu),非聚集索引葉子節(jié)點(diǎn)存儲(chǔ)字段(索引字段)的值以及對(duì)應(yīng)記錄主鍵的值,其他節(jié)點(diǎn)只存儲(chǔ)字段的值(索引字段),這就是與聚集索引不同的地方。每個(gè)表可以有多個(gè)非聚集索引。
InnoDB的每一個(gè)表都會(huì)有聚集索引:
- 假設(shè)表定義了PK,則PK就是聚集索引
- 如果未定義PK,則第一個(gè)非空unique列即是聚集索引
- 如果沒(méi)有PK也沒(méi)有非空unique列,InnoDB會(huì)創(chuàng)建一個(gè)隱含的row_id作為聚集索引使用
下圖更形象說(shuō)明這兩種索引的區(qū)別,這邊假設(shè)了一個(gè)存儲(chǔ)4行數(shù)據(jù)的表。Id為主鍵索引,Name作為輔助索引,圖中清晰的體現(xiàn)了聚簇索引和非聚簇索引的差異。
表中有四條記錄:
5, Gates, Microsoft
7, Bezos, Amazon
11, Jobs, Apple
14, Elison, Oracle
InnoDB數(shù)據(jù)檢索過(guò)程
上面的表中有2個(gè)索引:id作為主鍵索引,name作為輔助索引。
如果需要查詢(xún)id=14的數(shù)據(jù),只需要在左邊的主鍵索引中檢索就可以了。
如果需要搜索name='Ellison'的數(shù)據(jù),需要2步:
- 先在輔助索引中檢索到name='Ellison'的數(shù)據(jù),獲取id為14
- 再到主鍵索引中檢索id為14的記錄
輔助索引這個(gè)查詢(xún)過(guò)程在mysql中叫做回表,相對(duì)于主鍵索引多了第二步操作。
MyISAM數(shù)據(jù)檢索過(guò)程
- 在索引中找到對(duì)應(yīng)的關(guān)鍵字,獲取關(guān)鍵字對(duì)應(yīng)的記錄的地址
- 通過(guò)記錄的地址查找到對(duì)應(yīng)的數(shù)據(jù)記錄
對(duì)比發(fā)現(xiàn):Innodb中最好是采用主鍵索引查詢(xún),這樣只需要一次索引,如果使用輔助索引檢索,涉及多一步的回表操作,比主鍵查詢(xún)要耗時(shí)一些。
所以,InnoDB的普通索引,實(shí)際上會(huì)掃描兩遍:
第1遍,由普通索引找到PK:檢索到name='Ellison'的數(shù)據(jù),獲取id為14
第2遍,由PK找到行記錄:即到主鍵索引中檢索id為14的記錄
對(duì)索引有興趣的,可以參考作者的這幾篇文章:
MySQL全面瓦解22:索引的介紹和原理分析
MySQL全面瓦解23:MySQL索引實(shí)現(xiàn)和使用
MySQL全面瓦解24:構(gòu)建高性能索引(策略篇)
3 InnoDB 幾種常見(jiàn)鎖
★InnoDB默認(rèn)的事務(wù)隔離級(jí)別為可重復(fù)讀(Repeated Read, RR),我們當(dāng)下的所有介紹都是基于這個(gè)隔離級(jí)別為前提的。
- 記錄鎖(Record Locks):鎖定單一行記錄,InnoDB 使用記錄鎖來(lái)實(shí)現(xiàn)行級(jí)鎖,這樣允許多個(gè)事務(wù)并發(fā)訪問(wèn)不同的行。
- 間隙鎖(Gap Locks):InnoDB 的特性,用于鎖定一個(gè)范圍,但不包括實(shí)際的記錄。這主要用于防止幻讀(Phantom Reads)。
- 臨鍵鎖(Next-Key Locks):InnoDB 存儲(chǔ)引擎的一種鎖定機(jī)制,在執(zhí)行查詢(xún)語(yǔ)句時(shí),根據(jù)查詢(xún)條件所鎖定的一個(gè)范圍。這個(gè)范圍中包含有間隙鎖和記錄鎖。它的設(shè)計(jì)目的是為了解決幻讀(Phantom Reads)。
3.1 記錄鎖(Record Locks)
記錄鎖,它*索引記錄,例如:
select * from table where id=5 for update;
它會(huì)在id=1的索引記錄上加鎖,以阻止其他事務(wù)插入,更新,刪除id=1的這一行。
需要說(shuō)明的是:
select * from table where id=5;
則是快照讀(SnapShot Read),它并不加鎖,快照讀可以參考作者這篇文章:數(shù)據(jù)庫(kù)系列:RR和RC下,快照讀的區(qū)別
3.2 間隙鎖(Gap Locks)
間隙鎖,它*索引記錄中的間隔,或者第一條索引記錄之前的范圍,又或者最后一條索引記錄之后的范圍。
延續(xù)上面的那個(gè)例子繼續(xù)演示:
# 表結(jié)構(gòu)
table (Id PK, Name , Company);
# 表中包含四條記錄
5, Gates, Microsoft
7, Bezos, Amazon
11, Jobs, Apple
14, Elison, Oracle
執(zhí)行SQL語(yǔ)句如下:
select * from table
where id between 7 and 13
for update;
這樣的話,會(huì)*數(shù)據(jù)的區(qū)間,以防止其他事務(wù)插入id=8的記錄。
假設(shè)沒(méi)有間隙鎖,則可能夠插入成功,而之前的select事務(wù),會(huì)發(fā)現(xiàn)檢索的結(jié)果集莫名多了一條記錄,即幻影數(shù)據(jù)。
所以間隙鎖主要目的用于防止幻讀(Phantom Reads),避免其他事務(wù)在間隔中插入數(shù)據(jù),導(dǎo)致 『不可重復(fù)讀』。
如果把事務(wù)的隔離級(jí)別降級(jí)為讀提交(Read Committed, RC),對(duì),就是互聯(lián)網(wǎng)最常用的隔離級(jí)別,間隙鎖則會(huì)自動(dòng)失效。
3.3 臨鍵鎖(Next-Key Locks)
臨鍵鎖(Next-Key Locks)是數(shù)據(jù)庫(kù)管理系統(tǒng)InnoDB中的一種重要鎖定機(jī)制。這種鎖是查詢(xún)時(shí)根據(jù)查詢(xún)條件鎖定的一個(gè)范圍,這個(gè)范圍包括間隙鎖和記錄鎖,左開(kāi)右閉,即不鎖住左邊界,但會(huì)鎖住右邊界。臨鍵鎖的主要設(shè)計(jì)目的是為了解決所謂的“幻讀”問(wèn)題。
# 左開(kāi)右閉 示例
(-infinity, 1]
(1, 7]
(7, 9]
(9, +infinity]
依然沿用上面的例子,InnoDB引擎,RR隔離級(jí)別:
-- 創(chuàng)建一個(gè)示例表
CREATE TABLE users (
Id INT PRIMARY KEY,
Name VARCHAR(255) NOT NULL,
Company VARCHAR(255) NOT NULL,
);
-- 插入一些示例數(shù)據(jù)
INSERT INTO users (id, name, company) VALUES (1, 'Alice', 'ali');
INSERT INTO users (id, name, company) VALUES (2, 'Brand', 'tencent');
INSERT INTO users (id, name, company) VALUES (3, 'Charlie', 'baidu');
-- 開(kāi)始一個(gè)事務(wù),并使用臨鍵鎖查詢(xún)數(shù)據(jù)
START TRANSACTION;
SELECT * FROM users WHERE id > 1 FOR UPDATE;
-- 在另一個(gè)事務(wù)中嘗試插入新數(shù)據(jù),將會(huì)被阻塞直到第一個(gè)事務(wù)釋放鎖
START TRANSACTION;
INSERT INTO users (id, name, age) VALUES (4, 'David', 30);
COMMIT;
-- 第一個(gè)事務(wù)提交后,第二個(gè)事務(wù)可以繼續(xù)執(zhí)行插入操作
COMMIT;
臨鍵鎖的主要目的,也是為了避免幻讀(Phantom Read),在事務(wù)隔離級(jí)別為可重復(fù)讀的情況下,InnoDB存儲(chǔ)引擎默認(rèn)使用臨鍵鎖。這種鎖提供了一種有效的機(jī)制來(lái)保證在并發(fā)環(huán)境中數(shù)據(jù)的完整性和一致性。
如果把事務(wù)的隔離級(jí)別降級(jí)為RC,臨鍵鎖則也會(huì)失效。
4 總結(jié)
- InnoDB的索引與行記錄存儲(chǔ)在一起,MyISAM則是通過(guò)索引的地址查找到對(duì)應(yīng)的數(shù)據(jù)記錄,效率低一些
- InnoDB的聚集索引存儲(chǔ)行記錄,普通索引存儲(chǔ)PK,所以普通索引要查詢(xún)兩次
- 記錄鎖鎖定索引關(guān)聯(lián)的具體記錄
- 間隙鎖鎖定間隔,防止間隔中被其他事務(wù)插入
- 臨鍵鎖鎖定索引記錄+間隔,防止幻讀
- elect...for update加鎖的幾種情況:
- 主鍵字段:加行鎖。
- 唯一索引字段:加行鎖。
- 普通索引字段:加行鎖。
- 主鍵范圍:加多個(gè)行鎖。
- 普通字段:加表鎖。
- 查詢(xún)空數(shù)據(jù):不加鎖。
- 行鎖與表鎖的區(qū)別
- 如果事務(wù)1加了行鎖,一直未釋放鎖,事務(wù)2操作相同記錄,會(huì)一直等待直至超時(shí)。
- 如果事務(wù)1加了表鎖,一直未釋放鎖,事務(wù)2無(wú)論操作哪一行記錄,都會(huì)一直等待直到超時(shí)
總結(jié)
以上是生活随笔為你收集整理的数据库系列:MySQL InnoDB锁机制介绍的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 自学 --day9---js中的数学操作
- 下一篇: mysql 的相关操作_MySQL(记录