回顧
1.標(biāo)準(zhǔn)事務(wù)四個(gè)特性ACID
- 原子性(atomicity):最小工作單元纽匙,要么都成功,要么都失敗回滾击奶。
- 一致性(consistency):數(shù)據(jù)庫(kù)總是從一個(gè)一致性狀態(tài)轉(zhuǎn)換到另外一個(gè)一致性的狀態(tài)辈双,失敗事務(wù)中的改動(dòng)不會(huì)保存到數(shù)據(jù)庫(kù)中
- 隔離性(isolation):一個(gè)事務(wù)的修改在最終提交前,對(duì)其他事務(wù)是不可見(jiàn)的柜砾。
- 持久性(durability):一旦事務(wù)成功提交湃望,其修改就會(huì)永久保存到數(shù)據(jù)庫(kù)中。
2.隔離級(jí)別
- read uncommitted
- read committed
- repeatable read
- serializable
3.多版本控制(Multiversion Concurrency Control)
- 指的是一種提高并發(fā)的技術(shù)痰驱。最早的數(shù)據(jù)庫(kù)系統(tǒng)证芭,只有讀讀之間可以并發(fā),讀寫担映,寫讀废士,寫寫都要阻塞。引入多版本之后蝇完,只有寫寫之間相互阻塞官硝,其他三種操作都可以并行,這樣大幅度提高了InnoDB的并發(fā)度短蜕。在內(nèi)部實(shí)現(xiàn)中氢架,InnoDB通過(guò)undo log保存每條數(shù)據(jù)的多個(gè)版本,并且能夠找回?cái)?shù)據(jù)歷史版本提供給用戶讀朋魔,每個(gè)事務(wù)讀到的數(shù)據(jù)版本可能是不一樣的达箍。在同一個(gè)事務(wù)中,用戶只能看到該事務(wù)創(chuàng)建快照之前已經(jīng)提交的修改和該事務(wù)本身做的修改铺厨。
- MVCC在 Read Committed 和 Repeatable Read兩個(gè)隔離級(jí)別下適配起作用
MVCC具體細(xì)節(jié)
1.隱藏字段
1.DB_TRX_ID(6字節(jié)):表示最近一次對(duì)本記錄行作修改(insert | update)的事務(wù)ID。至于delete操作硬纤,InnoDB認(rèn)為是一個(gè)update操作解滓,不過(guò)會(huì)更新一個(gè)另外的刪除位,將行表示為deleted筝家。并非真正刪除洼裤。
2.DB_ROLL_PTR(7字節(jié)):回滾指針,指向當(dāng)前記錄行的undo log信息
3.DB_ROW_ID(6字節(jié)):隨著新行插入而單調(diào)遞增的行ID溪王。理解:當(dāng)表沒(méi)有主鍵或唯一非空索引時(shí)腮鞍,innodb就會(huì)使用這個(gè)行ID自動(dòng)產(chǎn)生聚簇索引。如果表有主鍵或唯一非空索引莹菱,聚簇索引就不會(huì)包含這個(gè)行ID了移国。
4.deleted_bit:刪除標(biāo)記位
2.快照read view(snapshot)
里面保存了【對(duì)本事務(wù)不可見(jiàn)的其他活躍事務(wù)】,主要用來(lái)做可見(jiàn)性判斷的(即判斷事務(wù)能select哪些行)
read view中的幾個(gè)關(guān)鍵字段:
- createor_trx_id:當(dāng)前事務(wù)的id(分配給事務(wù)的id是遞增的編號(hào))
- trx_ids:readView創(chuàng)建時(shí)其他未提交的活躍事務(wù)id列表(逆序排列)
- low_limit_id:目前出現(xiàn)過(guò)的最大的事務(wù)ID + 1道伟,即下一個(gè)即將被分配的事務(wù)ID
- up_limit_id:活躍事務(wù)列表trx_ids中最小的事務(wù)id迹缀,如果trx_ids為空的話使碾,則up_limit_id= low_limit_id
3.undo log
- 事務(wù)的回滾日志,存儲(chǔ)的是老版本數(shù)據(jù)祝懂,當(dāng)一個(gè)事務(wù)需要讀取記錄行時(shí)票摇,如果當(dāng)前記錄行不可見(jiàn),可以順著undo log鏈找到滿足其可見(jiàn)性條件的記錄行版本砚蓬。
- 可見(jiàn)性算法主要就是靠undo log來(lái)實(shí)現(xiàn)的
- undo log分類
1.insert undo log : 事務(wù)對(duì)insert新記錄時(shí)產(chǎn)生的undo log, 只在事務(wù)回滾時(shí)需要, 并且在事務(wù)提交后就可以立即丟棄矢门。
2.update undo log : 事務(wù)對(duì)記錄進(jìn)行delete和update操作時(shí)產(chǎn)生的undo log,不僅在事務(wù)回滾時(shí)需要灰蛙,快照讀也需要祟剔。提交后仍然留著需要,有個(gè)purge線程專門處理這類undo log的清理工作缕允。 - purge線程:
為了實(shí)現(xiàn)InnoDB的MVCC機(jī)制峡扩,更新或者刪除操作都只是設(shè)置一下舊記錄的deleted_bit,并不真正將舊記錄刪除障本。為了節(jié)省磁盤空間教届,InnoDB有專門的purge線程來(lái)清理deleted_bit為true的記錄。purge線程自己也維護(hù)了一個(gè)read view驾霜,如果某個(gè)記錄的deleted_bit為true案训,并且DB_TRX_ID相對(duì)于purge線程的read view可見(jiàn),那么這條記錄一定是可以被安全清除的粪糙。(只有當(dāng)系統(tǒng)沒(méi)有比這個(gè)log更早的read-view了的時(shí)候才能刪除) - update分為兩種情況:update的列是否是主鍵列强霎。
1.如果不是主鍵列,在undo log中直接反向記錄是如何update的蓉冈。即update是直接進(jìn)行的城舞。
2.如果是主鍵列,update分兩部執(zhí)行:先刪除該行寞酿,再插入一行目標(biāo)行家夺。 -
每次對(duì)記錄進(jìn)行改動(dòng),都會(huì)記錄一條undo日志伐弹,每條undo日志也都有一個(gè)DB_ROLL_PTR屬性(INSERT操作對(duì)應(yīng)的undo日志沒(méi)有該屬性拉馋,因?yàn)樵撚涗洸](méi)有更早的版本),可以將這些undo日志都連起來(lái)惨好,串成一個(gè)鏈表煌茴,所以現(xiàn)在的情況就像下圖一樣:
如圖,事務(wù)T2對(duì)字段做修改的步驟:
1.事務(wù)T2對(duì)該行加排他鎖
2.然后把該行數(shù)據(jù)拷貝到undo log中日川,作為舊版本
3.拷貝完畢后蔓腐,修改該行的字段,并且修改DB_TRX_ID為T2, 回滾指針指向拷貝到undo log的舊版本逗鸣。(然后還會(huì)將修改后的最新數(shù)據(jù)寫入redo log)
4.事務(wù)提交合住,釋放排他鎖
從上面可以看出绰精,不同事務(wù)或者相同事務(wù)的對(duì)同一記錄行的修改,會(huì)使該記錄行的undo log成為一條鏈表透葛,undo log的鏈?zhǔn)拙褪亲钚碌呐f記錄笨使,鏈尾就是最早的舊記錄。
4.可見(jiàn)性算法
在innodb中RR事務(wù)隔離級(jí)別情況下僚害,創(chuàng)建一個(gè)新事務(wù)后硫椰,執(zhí)行第一個(gè)select語(yǔ)句的時(shí)候,innodb會(huì)創(chuàng)建一個(gè)快照(read view)萨蚕,快照中會(huì)保存系統(tǒng)當(dāng)前不應(yīng)該被本事務(wù)看到的其他活躍事務(wù)id列表(即trx_ids)靶草。當(dāng)用戶在這個(gè)事務(wù)中要讀取某個(gè)記錄行的時(shí)候,innodb會(huì)將該記錄行的DB_TRX_ID與該Read View中的一些變量進(jìn)行比較岳遥,判斷是否滿足可見(jiàn)性條件奕翔。
假設(shè)當(dāng)前事務(wù)要讀取某一個(gè)記錄行,該記錄行的DB_TRX_ID(即最新修改該行的事務(wù)ID)為trx_id浩蓉,可見(jiàn)性算法流程圖(low_limit_id=max_trx_id+1=current_system_trx_id):
5.Read Committed 與 Repeatable Read的快照區(qū)別
- 在innodb中的Repeatable Read級(jí)別, 只有事務(wù)在begin之后派继,執(zhí)行第一條select(讀操作)時(shí), 才會(huì)創(chuàng)建一個(gè)快照(read view),將當(dāng)前系統(tǒng)中活躍的其他事務(wù)記錄起來(lái)捻艳;并且事務(wù)之后都是使用的這個(gè)快照驾窟,不會(huì)重新創(chuàng)建,直到事務(wù)結(jié)束认轨。
- 在innodb中的Read Committed級(jí)別, 事務(wù)在begin之后绅络,執(zhí)行每條select(讀操作)語(yǔ)句時(shí),快照會(huì)被重置嘁字,即會(huì)重新創(chuàng)建一個(gè)快照(read view)恩急。
- 這個(gè)區(qū)別從而也就導(dǎo)致了RR情況下能保證一致性讀,因?yàn)榭煺帐冀K是用固定初始select創(chuàng)建的快照
6.當(dāng)前讀和快照讀
- 快照讀(snapshot read):普通的 select 語(yǔ)句(不包括 select ... lock in share mode, select ... for update)
- 當(dāng)前讀(current read) :select ... lock in share mode纪蜒,select ... for update假栓,insert,delete 語(yǔ)句(這些語(yǔ)句獲取的是數(shù)據(jù)庫(kù)中的最新數(shù)據(jù)霍掺,官方文檔:14.7.2.4 Locking Reads )
只靠 MVCC 實(shí)現(xiàn)RR隔離級(jí)別,能防止快照讀的幻讀拌蜘,不能防止當(dāng)前讀的幻讀
1.比如事務(wù)A開(kāi)始后杆烁,執(zhí)行普通select語(yǔ)句,創(chuàng)建了快照简卧;之后事務(wù)B執(zhí)行insert語(yǔ)句兔魂;然后事務(wù)A再執(zhí)行普通select語(yǔ)句,得到的還是之前B沒(méi)有insert過(guò)的數(shù)據(jù)举娩,因?yàn)檫@時(shí)候A讀的數(shù)據(jù)是符合快照可見(jiàn)性條件的數(shù)據(jù)析校。這就防止了部分幻讀构罗,此時(shí)事務(wù)A是快照讀。
2.但是智玻,如果事務(wù)A執(zhí)行的不是普通select語(yǔ)句遂唧,而是select ... for update等語(yǔ)句,這時(shí)候吊奢,事務(wù)A是當(dāng)前讀盖彭,每次語(yǔ)句執(zhí)行的時(shí)候都是獲取的最新數(shù)據(jù)。也就是說(shuō)页滚,在只有MVCC時(shí)召边,A先執(zhí)行 select ... where nid between 1 and 10 … for update;然后事務(wù)B再執(zhí)行 insert … nid = 5 …裹驰;然后 A 再執(zhí)行 select ... where nid between 1 and 10 … for update隧熙,就會(huì)發(fā)現(xiàn),多了一條B insert進(jìn)去的記錄幻林。這就產(chǎn)生幻讀了贞盯,所以單獨(dú)靠MVCC并不能完全防止幻讀。
- 所以說(shuō)滋将,RR(read repeatable)隔離級(jí)別下邻悬,僅僅依賴MVCC的話,可以保證可重復(fù)讀随闽,還能防止部分幻讀父丰,但不能完全防止幻讀(因?yàn)槿绻钱?dāng)前讀的情況下,獲取的是最新數(shù)據(jù)掘宪,仍然會(huì)有幻讀現(xiàn)象)蛾扇。
- InnoDB在實(shí)現(xiàn)RR隔離級(jí)別時(shí),是通過(guò)“行級(jí)鎖+MVCC”來(lái)徹底做到完全防止幻讀的:不僅使用了MVCC魏滚,還會(huì)對(duì)“當(dāng)前讀語(yǔ)句”讀取的記錄行加記錄鎖(record lock)和間隙鎖(gap lock)镀首,禁止其他事務(wù)在間隙間插入記錄行,來(lái)防止幻讀鼠次。(鎖的概念后文會(huì)講)
鎖
表級(jí)鎖與行級(jí)鎖
- 表級(jí)鎖:具有開(kāi)銷小更哄、加鎖快的特性;表級(jí)鎖的鎖定粒度較大腥寇,發(fā)生鎖沖突的概率高成翩,支持的并發(fā)度低;
- 行級(jí)鎖:具有開(kāi)銷大赦役,加鎖慢的特性麻敌;行級(jí)鎖的鎖定粒度較小,發(fā)生鎖沖突的概率低掂摔,支持的并發(fā)度高术羔。
共享鎖與排他鎖
- 共享鎖(S):允許獲得該鎖的事務(wù)讀取數(shù)據(jù)行(讀鎖)赢赊,同時(shí)允許其他事務(wù)獲得該數(shù)據(jù)行上的共享鎖,并且阻止其他事務(wù)獲得數(shù)據(jù)行上的排他鎖级历。
- 排他鎖(X):允許獲得該鎖的事務(wù)更新或刪除數(shù)據(jù)行(寫鎖)释移,同時(shí)阻止其他事務(wù)取得該數(shù)據(jù)行上的共享鎖和排他鎖。
意向鎖
demo理解:
1.事務(wù)A鎖住了表中的一行鱼喉,讓這一行只能讀秀鞭,不能寫。
2.之后扛禽,事務(wù)B申請(qǐng)整個(gè)表的寫鎖锋边。如果事務(wù)B申請(qǐng)成功,那么理論上它就能修改表中的任意一行编曼,這與A持有的行鎖是沖突的豆巨。
3.數(shù)據(jù)庫(kù)需要避免這種沖突,要讓B的申請(qǐng)被阻塞掐场,直到A釋放了行鎖往扔。 數(shù)據(jù)庫(kù)要怎么判斷這個(gè)沖突呢?
?step1:判斷表是否已被其他事務(wù)用表鎖鎖表
?step2:判斷表中的每一行是否已被行鎖鎖住熊户。 (這樣的判斷方法效率不高萍膛,因?yàn)樾枰闅v整個(gè)表)
于是就有了意向鎖。在意向鎖存在的情況下嚷堡,事務(wù)A必須先申請(qǐng)表的意向共享鎖蝗罗,成功后再申請(qǐng)一行的行鎖。在意向鎖存在的情況下蝌戒, 上面的判斷可以改成
?step1:不變
?step2:發(fā)現(xiàn)表上有意向共享鎖串塑,說(shuō)明表中有些行被共享行鎖鎖住了,因此北苟,事務(wù)B申請(qǐng)表的寫鎖會(huì)被阻塞桩匪。
?注意:申請(qǐng)意向鎖的動(dòng)作是數(shù)據(jù)庫(kù)完成的,就是說(shuō)友鼻,事務(wù)A申請(qǐng)一行的行鎖的時(shí)候傻昙,數(shù)據(jù)庫(kù)會(huì)自動(dòng)先開(kāi)始申請(qǐng)表的意向鎖,不需要我們程序員使用代碼來(lái)申請(qǐng)彩扔。 總結(jié):為了實(shí)現(xiàn)多粒度鎖機(jī)制(為了表鎖和行鎖都能用)
- “The main purpose of IX and IS locks is to show that someone is locking a row, or going to lock a row in the table.” 意思是說(shuō)加意向鎖的目的是為了表明某個(gè)事務(wù)正在鎖定這個(gè)表的一行或者將要鎖定一行屋匕。
意向鎖屬于表級(jí)鎖,由 InnoDB 自動(dòng)添加借杰,不需要用戶干預(yù)。意向鎖也分為共享和排他兩種方式:
- 意向共享鎖(IS):事務(wù)在給數(shù)據(jù)行加行級(jí)共享鎖之前进泼,必須先取得該表的 IS 鎖蔗衡。
- 意向排他鎖(IX):事務(wù)在給數(shù)據(jù)行加行級(jí)排他鎖之前纤虽,必須先取得該表的 IX 鎖。
表級(jí)鎖和表級(jí)意向鎖的兼容性:
行級(jí)鎖的實(shí)現(xiàn)
- Innodb通過(guò)給索引上的索引記錄加鎖的方式實(shí)現(xiàn)行級(jí)鎖
- 行級(jí)鎖分為三種
1.記錄鎖(Record Lock)
2.間隙鎖(Gap Lock)
3.臨鍵鎖(Next-key Lock)
1. 記錄鎖(Record Lock)
- 記錄鎖(Record Lock)是針對(duì)索引記錄(index record)的鎖定绞惦。
- 記錄鎖永遠(yuǎn)都是鎖定索引記錄逼纸,鎖定非聚集索引會(huì)先鎖定聚集索引,然后再鎖定非聚集索引济蝉。如果表中沒(méi)有定義索引杰刽,InnoDB 默認(rèn)為表創(chuàng)建一個(gè)隱藏的聚簇索引,并且使用該索引鎖定記錄王滤。
1.例如贺嫂,id為唯一主鍵;SELECT * FROM t WHERE id = 1 FOR UPDATE;會(huì)阻止其他事務(wù)對(duì)表 t 中 id = 1 的數(shù)據(jù)執(zhí)行插入雁乡、更新第喳,以及刪除操作:
- 表t上會(huì)有一個(gè)IX鎖
- 主鍵索引id_index上對(duì)應(yīng)記錄會(huì)有一個(gè)X鎖
2.例如,id為唯一主鍵踱稍,name是加了普通索引的字段曲饱;SELECT * FROM t WHERE name = ‘test’ FOR UPDATE;會(huì)阻止其他事務(wù)對(duì)表 t 中 name = ‘test’ 的數(shù)據(jù)執(zhí)行插入、更新珠月,以及刪除操作:
- 表t上會(huì)有一個(gè)IX鎖
- 主鍵索引id_index上對(duì)應(yīng)記錄會(huì)有一個(gè)X鎖
- 索引name_index上對(duì)應(yīng)記錄會(huì)有一個(gè)X鎖
2.間隙鎖(Gap Lock)
- 間隙鎖(Gap Lock)鎖定的是索引記錄之間的間隙扩淀、第一個(gè)索引之前的間隙或者最后一個(gè)索引之后的間隙。嚴(yán)格來(lái)說(shuō)是個(gè)開(kāi)區(qū)間
- 只會(huì)在Repeatable Read下使用
1.例如啤挎,SELECT * FROM t WHERE c1 BETWEEN 1 and 10 FOR UPDATE;會(huì)阻止其他事務(wù)將 1 到 10 之間的任何值插入到 c1 字段中驻谆,即使該列不存在這樣的數(shù)據(jù);(c1=4和c1=1是存在的侵浸,其他都不存在)因?yàn)檫@些值都會(huì)被鎖定:
?表 t 上存在 IX 鎖
?主鍵索引上存在 1 個(gè) X 記錄鎖(id = 1)
?主鍵索引上存在 2個(gè)間隙鎖(1, 4],(4,10] 后面可以得知旺韭,其實(shí)這個(gè)屬于next-key鎖了
3.臨鍵鎖(Next-key Lock)
理解為記錄鎖+間隙鎖即可,間隙鎖的邊緣的索引記錄恰好有個(gè)記錄鎖
SELECT * FROM t WHERE c1 BETWEEN 1 and 10 FOR UPDATE;c1是個(gè)無(wú)索引的字段的話掏觉,那么就會(huì)導(dǎo)致出現(xiàn)( -infinity 区端,1),1澳腹,(1织盼,10),10酱塔,(10沥邻, infinity )的鎖。會(huì)導(dǎo)致任何插入都需要等待鎖釋放羊娃;因?yàn)閺腸1跟主鍵索引構(gòu)建不了管聯(lián)唐全,所以索引一定要加好來(lái)