當前讀
諸如select ... lock in share mode
、select ... for update
女器、update
酸役、delete
、insert
均為當前讀驾胆;當前讀本質(zhì)上是加了鎖的增刪該查語句涣澡,無論上的是共享鎖還是排他鎖均為當前讀.
這些語句被稱為當前讀的根本原因是因為它讀取的是記錄的最新版本,并且在讀取之后丧诺,還需保證其他事務(wù)不能修改當前記錄入桂,對讀取的記錄加鎖;上面的除 select 語句加的是共享鎖外驳阎,其他的都是排他鎖抗愁,那為什么 update
馁蒂、delete
、insert
也被稱為當前讀呢蜘腌?
我們都知道 RSBMS 主要由兩大部分組成沫屡,一部分是程序?qū)嵗?MySQL 實例),另一部分是存儲(即 InnoDB)撮珠,
我們在執(zhí)行 update
語句更新某幾行數(shù)據(jù)時沮脖,每次都需要先讀取相應(yīng)數(shù)據(jù)行,然后在更新芯急,這個時候在讀取的時候需要讀取最新行勺届,所以需要使用當前讀
快照讀
快照讀也叫非阻塞讀,即所謂快照讀就是不加鎖的非阻塞讀志于,就是我們最簡單的 select
操作
當然了涮因,這里不加鎖的非阻塞讀是以事務(wù)隔離級別不為最高級別的前提下成立废睦,因為在最高隔離級別下伺绽,快照讀也會變成當前讀,在其后自動加lock in share mode
快照讀的實現(xiàn)原理
之所以出現(xiàn)快照讀嗜湃,是基于提升并發(fā)訪問性能考慮的奈应;快照讀的實現(xiàn)是基于多版本的并發(fā)控制,即 MVCC购披,可以認為 MVCC 是行級鎖的一個變種杖挣,但是它在很多情況下避免了加鎖的操作,因此開銷更低.
既然基于多版本實現(xiàn)刚陡,那么快照讀有可能讀到的并不是數(shù)據(jù)的最新版本惩妇,可能是之前的歷史版本。
在 RC【Read Committed】 隔離級別下筐乳,快照讀和當前讀讀到讀數(shù)據(jù)版本是一樣的歌殃;
演示.
1.開啟兩個會話,并設(shè)置相應(yīng)的事務(wù)隔離級別為 RC
【下面的命令是基于 MySQL 8.0的蝙云,8以下的版本可以自行查找相應(yīng)的命令進行替換】
通過show variables like 'transaction%';
查看會話的隔離級別氓皱,然后通過set session transaction isolation level read committed;
設(shè)置會話的隔離級別為 RC
2.在一個會話中查詢數(shù)據(jù),另一個會話中修改相應(yīng)數(shù)據(jù)
1)會話 1 中查詢 deptno = 10 的 loc 為 NEW YORK
2)會話 2 中修改 deptno = 10 的 loc 為紐約
3)在會話1中分別使用當前讀和快照讀讀取 deptno = 10 的數(shù)據(jù)
發(fā)現(xiàn)使用快照讀和當前讀讀取到到數(shù)據(jù)是一樣的勃刨,即在 RR 隔離級別下波材,使用快照讀讀取到到也是最新數(shù)據(jù)
而在 RR【Repeatable Read】隔離級別下,當前讀返回的是數(shù)據(jù)的最新版本身隐,而快照讀在該隔離級別下可能讀到數(shù)據(jù)的歷史版本.在 RR 隔離級別下廷区,事務(wù)首次調(diào)用快照讀的時機很關(guān)鍵,即創(chuàng)造快照的時機決定了快照的版本
演示
1. 第一種情況贾铝,會話 1 先快照讀讀取 deptno = 40 的數(shù)據(jù)行躲因,會話2修改 deptno = 40的數(shù)據(jù)行的 loc = 奧地利早敬,并提交會話,然后比較會話 1 通過快照讀和當前讀讀取讀數(shù)據(jù)情況
1)會話 1 先快照讀讀取 deptno = 40 的數(shù)據(jù)行
2)會話2修改 deptno = 40的數(shù)據(jù)行的 loc = 奧地利大脉,并提交搞监,并查詢結(jié)果顯示已修改成功
3)比較會話 1 通過快照讀和當前讀讀取讀數(shù)據(jù)情況
結(jié)果顯示,快照讀讀取到的是舊數(shù)據(jù)镰矿,而當前讀讀到讀是最新數(shù)據(jù)
1. 第二種情況琐驴,會話2修改 deptno = 40的數(shù)據(jù)行的 loc = 烏克蘭,并提交會話秤标,然后比較會話 1 通過快照讀和當前讀讀取讀數(shù)據(jù)情況
1)會話2修改 deptno = 40的數(shù)據(jù)行的 loc = 烏克蘭绝淡,并提交會話
2)比較會話 1 通過快照讀和當前讀讀取讀數(shù)據(jù)情況
可以看到當前讀和快照讀讀取到的數(shù)據(jù)是一致的
RC|RR 隔離級別下的 InnoDB 的非阻塞讀【即快照讀】如何實現(xiàn)
快照讀的實現(xiàn)依賴三個因素;每行數(shù)據(jù)記錄除了存儲數(shù)據(jù)以外苍姜,還有一些額外的字段牢酵,其中最關(guān)鍵的是三個:DB_TRX_ID、DB_ROLL_PTR衙猪、DB_ROW_ID字段
DB_TRX_ID
該字段用來標識最近一次對本行記錄做修改馍乙,無論是insert,還是update垫释,它都是事務(wù)的標識符丝格,即最后一次修改本行記錄的事務(wù)ID,delete對于innodb來說也是一個update操作棵譬,更新行中的一個特殊位显蝌,將行標識為deleted,并非做真正的刪除【每次開啟一個事務(wù)的時候订咸,該事務(wù)ID就會遞增曼尊,即越新開啟的事務(wù),它的 事務(wù) ID 越大】
DB_ROLL_PTR
回滾指針脏嚷,只寫入回滾段( roll back segment)的undo 日志記錄骆撇。如果一行記錄被更新,則undo log report 包含重建該行記錄被更新之前內(nèi)容所必須的信息然眼。
DB_ROW_ID
即行號包含一個隨著新行插入而單調(diào)遞增的行ID艾船,當innodb自動產(chǎn)生聚集索引時,聚集索引會包括這個行ID的值高每,否則這個行ID不會出現(xiàn)在任何索引中屿岂。 (以前提到的,在innodb存儲引擎中鲸匿,如果表中沒有設(shè)置主鍵并且無唯一鍵時爷怀,Innodb會為我們創(chuàng)建一個隱藏主鍵字段,即我們這里的DB_ROW_ID)
光有上面這三個字段带欢,并不足以實現(xiàn)快照讀运授,還需要依托undo日志烤惊。
undo 日志
當我們對記錄做了變更操作時,就會產(chǎn)生undo記錄吁朦,undo記錄中存儲的是老版數(shù)據(jù)柒室,當一個舊的事務(wù)需要讀取數(shù)據(jù)時,為了能夠讀取到老版本的數(shù)據(jù)逗宜,需要順著undo列找到滿足其可見性的記錄雄右,這個找滿足可見行的記錄依賴 read view
undo 日志主要分為兩種:即insert undo log 和 update undo log.
insert undo log
表示的是事務(wù)對insert新記錄產(chǎn)生的undo log,只在事務(wù)回滾時需要纺讲,并且在事務(wù)提交后就可以立即丟棄
update undo log
事務(wù)對記錄進行delete或者update操作時產(chǎn)生的undo log擂仍,不僅在事務(wù)回滾時需要,快照讀也需要熬甚,所以不能隨便刪除逢渔,只有當數(shù)據(jù)庫所使用的快照中不涉及該日志記錄,對應(yīng)的回滾日志才會被線程刪除乡括。
read view
read view主要是用來做可見性判斷的肃廓,即當我們?nèi)?zhí)行快照讀 select 的時候,會針對我們查詢的數(shù)據(jù)創(chuàng)建出一個 read view粟判,來決定當前事務(wù)能看到的是哪個版本的數(shù)據(jù)亿昏,有可能是當前最新版本的數(shù)據(jù)峦剔,也可能是 undo log 中某個版本的數(shù)據(jù)档礁,read view 遵循一個可見性算法。主要是將要修改的數(shù)據(jù)的 DB_TRX_ID 取出來吝沫,與系統(tǒng)其他活躍事務(wù)ID【DB_TRX_ID】做對比呻澜,如果大于或者等于這些 ID 的話,就通過 DB_ROW_PTR 指針去取出 un do log惨险,上一層的 DB_TRX_ID 直到小于這些活躍事務(wù) ID 為止羹幸,這樣就保證了我們獲取到的數(shù)據(jù)版本是當前可見的最穩(wěn)定的版本
總結(jié)
正是以上的三個因子才使得 InnoDB 在 RR、RC 隔離級別下支持非阻塞讀辫愉,而讀取數(shù)據(jù)時的非阻塞就是所謂的 MVCC【Multiversion concurrency control】 栅受,而 InnDB 非阻塞讀機制實現(xiàn)了仿照版的 MVCC,MVCC 代表多版本并發(fā)控制恭朗,讀不加鎖屏镊,讀寫不沖突,極大的增加了系統(tǒng)的并發(fā)性能痰腮,那為什么是偽 MVCC 機制呢而芥,因為并沒有實現(xiàn)核心的多版本并存,而undo log 中的內(nèi)容只是串行化的結(jié)果膀值,記錄了多個事務(wù)的過程棍丐,不屬于多版本共存误辑。