InnoDB引擎事務(wù)總結(jié)

什么是局部性原理?

數(shù)據(jù)預(yù)讀的思路是:磁盤讀寫并不是按需讀取,而是按頁預(yù)讀,一次會讀一頁的數(shù)據(jù),每次加載更多的數(shù)據(jù),以便未來減少磁盤IO

局部性原理:軟件設(shè)計要盡量遵循“數(shù)據(jù)讀取集中”與“使用到一個數(shù)據(jù),大概率會使用其附近的數(shù)據(jù)”,這樣磁盤預(yù)讀能充分提高磁盤IO

B樹

B樹,如上圖,它的特點是:
(1)不再是二叉搜索,而是m叉搜索;
(2)葉子節(jié)點,非葉子節(jié)點,都存儲數(shù)據(jù);
(3)中序遍歷,可以獲得所有節(jié)點;
非根節(jié)點包含的關(guān)鍵字個數(shù)j滿足,(┌m/2┐)-1 <= j <= m-1,節(jié)點分裂時要滿足這個條件

B+樹

(1)非葉子節(jié)點不再存儲數(shù)據(jù),數(shù)據(jù)只存儲在同一層的葉子節(jié)點上;
(2)葉子之間,增加了鏈表,獲取所有節(jié)點,不再需要中序遍歷;
數(shù)據(jù)庫的索引最常用B+樹:
(1)很適合磁盤存儲,能夠充分利用局部性原理,磁盤預(yù)讀。
比如:每個節(jié)點是1頁的大小,4k,key占8個字節(jié),j = 512, 根據(jù)(┌m/2┐)1 <= j <= m-1, m=j + 1,即513
(2)很低的樹高度,能夠存儲大量數(shù)據(jù);
(3)索引本身占用的內(nèi)存很小;
(4)能夠很好的支持單點查詢,范圍查詢,有序性查詢;

InnoDB的索引

InnoDB的索引有兩類索引,聚集索引(Clustered Index)與普通索引(Secondary Index)。
InnoDB的每一個表都會有聚集索引:
(1)如果表定義了PK,則PK就是聚集索引;
(2)如果表沒有定義PK,則第一個非空unique列是聚集索引;
(3)否則,InnoDB會創(chuàng)建一個隱藏的row-id作為聚集索引;
索引的結(jié)構(gòu)是B+樹,這里不展開B+樹的細節(jié),說幾個結(jié)論:
(1)在索引結(jié)構(gòu)中,非葉子節(jié)點存儲key,葉子節(jié)點存儲value;
(2)聚集索引,葉子節(jié)點存儲行記錄(row);
(3)普通索引,葉子節(jié)點存儲了PK的值;
InnoDB的普通索引,實際上會掃描兩遍:
第一遍,由普通索引找到PK;
第二遍,由PK找到行記錄;

隔離性

事務(wù)ACID特性,其中I代表隔離性(Isolation)
隔離性是指多個并發(fā)事務(wù)之間要相互隔離。
并發(fā)事務(wù)之間相互干擾,可能導(dǎo)致事務(wù)出現(xiàn)讀臟,不可重復(fù)度,幻讀等問題

不隔離可能導(dǎo)致的問題

假設(shè)有InnoDB表:
t(id PK, name);
表中有三條記錄:
1, shenjian
2, zhangsan
3, lisi

case 1
事務(wù)A,先執(zhí)行,處于未提交的狀態(tài):
insert into t values(4, wangwu);

事務(wù)B,后執(zhí)行,也未提交:
select * from t;

如果事務(wù)B能夠讀取到(4, wangwu)這條記錄,事務(wù)A就對事務(wù)B產(chǎn)生了影響,這個影響叫做“讀臟”,讀到了未提交事務(wù)操作的記錄。

case 2
事務(wù)A,先執(zhí)行:
select * from t where id=1;

結(jié)果集為:
1, shenjian

事務(wù)B,后執(zhí)行,并且提交:
update t set name=xxoo where id=1;
commit;

事務(wù)A,再次執(zhí)行相同的查詢:
select * from t where id=1;

結(jié)果集為:
1, xxoo
這次是已提交事務(wù)B對事務(wù)A產(chǎn)生的影響,這個影響叫做“不可重復(fù)讀”,一個事務(wù)內(nèi)相同的查詢,得到了不同的結(jié)果。

case 3
事務(wù)A,先執(zhí)行:
select * from t where id>3;
結(jié)果集為:
NULL

事務(wù)B,后執(zhí)行,并且提交:
insert into t values(4, wangwu);
commit;

事務(wù)A,首次查詢了id>3的結(jié)果為NULL,于是想插入一條為4的記錄:
insert into t values(4, xxoo);

結(jié)果集為:
Error : duplicate key!

事務(wù)A的內(nèi)心OS是:你TM在逗我,查了id>3為空集,insert id=4告訴我PK沖突?

這次是已提交事務(wù)B對事務(wù)A產(chǎn)生的影響,這個影響叫做“幻讀”。
可以看到,并發(fā)的事務(wù)可能導(dǎo)致其他事務(wù):

  • 讀臟

  • 不可重復(fù)讀

  • 幻讀

事務(wù)的隔離級別:

按照SQL92標準,InnoDB實現(xiàn)了四種不同事務(wù)的隔離級別:

  • 讀未提交(Read Uncommitted)
  • 讀提交(Read Committed, RC)
  • 可重復(fù)讀(Repeated Read, RR)
  • 串行化(Serializable)
    不同事務(wù)的隔離級別,實際上是一致性與并發(fā)性的一個權(quán)衡與折衷。

隔離級別實現(xiàn)

InnoDB使用不同的鎖策略(Locking Strategy)來實現(xiàn)不同的隔離級別。

(1)讀未提交:select不加鎖,可能出現(xiàn)讀臟;

(2)讀提交(RC):普通select快照讀,鎖select /update /delete 會使用記錄鎖,可能出現(xiàn)不可重復(fù)讀;

(3)可重復(fù)讀(RR):普通select快照讀,鎖select /update /delete 根據(jù)查詢條件情況,會選擇記錄鎖,或者間隙鎖/臨鍵鎖,以防止讀取到幻影記錄;

(4)串行化:select隱式轉(zhuǎn)化為select ... in share mode,會被update與delete互斥;

InnoDB默認的隔離級別是RR,用得最多的隔離級別是RC

mysql的鎖

默認的事務(wù)隔離級別為可重復(fù)讀(Repeated Read, RR)

記錄鎖(Record Locks)

記錄鎖,它封鎖索引記錄,例如:
select * from t where id=1 for update;
它會在id=1的索引記錄上加鎖,以阻止其他事務(wù)插入,更新,刪除id=1的這一行。
需要說明的是:
select * from t where id=1;
則是快照讀(SnapShot Read),它并不加鎖

間隙鎖(Gap Locks)

間隙鎖,它封鎖索引記錄中的間隔,或者第一條索引記錄之前的范圍,又或者最后一條索引記錄之后的范圍。

依然是上面的例子,InnoDB,RR:
t(id PK, name KEY, sex, flag);
表中有四條記錄:
1, shenjian, m, A
3, zhangsan, m, A
5, lisi, m, A
9, wangwu, f, B

這個SQL語句

select * from t 
    where id between 8 and 15 
    for update;

會封鎖區(qū)間,以阻止其他事務(wù)id=10的記錄插入。
為什么要阻止id=10的記錄插入?
如果能夠插入成功,頭一個事務(wù)執(zhí)行相同的SQL語句,會發(fā)現(xiàn)結(jié)果集多出了一條記錄,即幻影數(shù)據(jù)。
間隙鎖的主要目的,就是為了防止其他事務(wù)在間隔中插入數(shù)據(jù),以導(dǎo)致“不可重復(fù)讀”。
如果把事務(wù)的隔離級別降級為讀提交(Read Committed, RC),間隙鎖則會自動失效。

臨鍵鎖(Next-Key Locks)

臨鍵鎖,是記錄鎖與間隙鎖的組合,它的封鎖范圍,既包含索引記錄,又包含索引區(qū)間。
更具體的,臨鍵鎖會封鎖索引記錄本身,以及索引記錄之前的區(qū)間。
如果一個會話占有了索引記錄R的共享/排他鎖,其他會話不能立刻在R之前的區(qū)間插入新的索引記錄。
依然是上面的例子,InnoDB,RR:
t(id PK, name KEY, sex, flag);
表中有四條記錄:
1, shenjian, m, A
3, zhangsan, m, A
5, lisi, m, A
9, wangwu, f, B

PK上潛在的臨鍵鎖為:
(-infinity, 1]
(1, 3]
(3, 5]
(5, 9]
(9, +infinity]

臨鍵鎖的主要目的,也是為了避免幻讀(Phantom Read)。如果把事務(wù)的隔離級別降級為RC,臨鍵鎖則也會失效。

共享/排它鎖(Shared and Exclusive Locks)

(1)事務(wù)拿到某一行記錄的共享S鎖,才可以讀取這一行;
(2)事務(wù)拿到某一行記錄的排它X鎖,才可以修改或者刪除這一行;

意向鎖(Intention Locks)

InnoDB支持多粒度鎖(multiple granularity locking),它允許行級鎖與表級鎖共存,實際應(yīng)用中,InnoDB使用的是意向鎖。
意向鎖是指,未來的某個時刻,事務(wù)可能要加共享/排它鎖了,先提前聲明一個意向。
意向鎖有這樣一些特點:
(1)首先,意向鎖,是一個表級別的鎖(table-level locking);
(2)意向鎖分為:
意向共享鎖(intention shared lock, IS),它預(yù)示著,事務(wù)有意向?qū)Ρ碇械哪承┬屑庸蚕鞸鎖
意向排它鎖(intention exclusive lock, IX),它預(yù)示著,事務(wù)有意向?qū)Ρ碇械哪承┬屑优潘黊鎖
舉個例子:
select ... lock in share mode,要設(shè)置IS鎖;
select ... for update,要設(shè)置IX鎖;

(3)意向鎖協(xié)議(intention locking protocol)并不復(fù)雜:
事務(wù)要獲得某些行的S鎖,必須先獲得表的IS鎖
事務(wù)要獲得某些行的X鎖,必須先獲得表的IX鎖
(4)由于意向鎖僅僅表明意向,它其實是比較弱的鎖,意向鎖之間并不相互互斥,而是可以并行

插入意向鎖(Insert Intention Locks)

對已有數(shù)據(jù)行的修改與刪除,必須加強互斥鎖X鎖,那對于數(shù)據(jù)的插入,是否還需要加這么強的鎖,來實施互斥呢?插入意向鎖,孕育而生。
插入意向鎖,是間隙鎖(Gap Locks)的一種(所以,也是實施在索引上的),它是專門針對insert操作的。
多個事務(wù),在同一個索引,同一個范圍區(qū)間插入記錄時,如果插入的位置不沖突,不會阻塞彼此
在MySQL,InnoDB,RR下:
t(id unique PK, name);

數(shù)據(jù)表中有數(shù)據(jù):
10, shenjian
20, zhangsan
30, lisi

事務(wù)A先執(zhí)行,在10與20兩條記錄中插入了一行,還未提交:
insert into t values(11, xxx);
事務(wù)B后執(zhí)行,也在10與20兩條記錄中插入了一行:
insert into t values(12, ooo);
(1)會使用什么鎖?
(2)事務(wù)B會不會被阻塞呢?

回答:雖然事務(wù)隔離級別是RR,雖然是同一個索引,雖然是同一個區(qū)間,但插入的記錄并不沖突,故這里:
使用的是插入意向鎖
并不會阻塞事務(wù)B

自增鎖(Auto-inc Locks)

自增鎖是一種特殊的表級別鎖(table-level lock),專門針對事務(wù)插入AUTO_INCREMENT類型的列。最簡單的情況,如果一個事務(wù)正在往表中插入記錄,所有其他事務(wù)的插入必須等待,以便第一個事務(wù)插入的行,是連續(xù)的主鍵值。
與此同時,InnoDB提供了innodb_autoinc_lock_mode配置,可以調(diào)節(jié)與改變該鎖的模式與行為。
一,案例說明

MySQL,InnoDB,默認的隔離級別(RR),假設(shè)有數(shù)據(jù)表:

t(id AUTO_INCREMENT, name);

數(shù)據(jù)表中有數(shù)據(jù):

1, shenjian

2, zhangsan

3, lisi

事務(wù)A先執(zhí)行,還未提交:

insert into t(name) values(xxx);

事務(wù)B后執(zhí)行:

insert into t(name) values(ooo);

問:事務(wù)B會不會被阻塞?

二,案例分析

InnoDB在RR隔離級別下,能解決幻讀問題,上面這個案例中:

(1)事務(wù)A先執(zhí)行insert,會得到一條(4, xxx)的記錄,由于是自增列,故不用顯示指定id為4,InnoDB會自動增長,注意此時事務(wù)并未提交;

(2)事務(wù)B后執(zhí)行insert,假設(shè)不會被阻塞,那會得到一條(5, ooo)的記錄;

此時,并未有什么不妥,但如果,

(3)事務(wù)A繼續(xù)insert:

insert into t(name) values(xxoo);

會得到一條(6, xxoo)的記錄。

(4)事務(wù)A再select:

select * from t where id>3;

得到的結(jié)果是:

4, xxx

6, xxoo

畫外音:不可能查詢到5的記錄,再RR的隔離級別下,不可能讀取到還未提交事務(wù)生成的數(shù)據(jù)。

這對于事務(wù)A來說,就很奇怪了,對于AUTO_INCREMENT的列,連續(xù)插入了兩條記錄,一條是4,接下來一條變成了6,就像莫名其妙的幻影。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 酒過三巡,燈火通明,伴著音樂,往事歷歷在目,看著眼前的人,突然覺得那么陌生,莫名的恐慌如龍卷風(fēng)般席卷而來,傾襲你的...
    見素_藍色鳶尾閱讀 291評論 0 0
  • 啊啊啊……說到淘寶我不自覺地就想來回答這個問題。自從認識馬爸爸以后,我酷愛逛淘寶,特別是衣服類~就算不買看著那些漂...
    是張姑娘啊閱讀 9,401評論 4 83
  • 星期六下午,我和媽媽和阿姨一家一起去山上采楊梅。 今天,艷陽高照,太陽火辣辣的照射著大地。我們一行人沿著一條陡峭的...
    秋風(fēng)靜美閱讀 393評論 0 1

友情鏈接更多精彩內(nèi)容