limit实现原理 mysql_解读数据库:深入分析MySQL中事务以及MVCC的实现原理
什么是事務(wù)
事務(wù)(Transaction)是由一系列對數(shù)據(jù)庫中的數(shù)據(jù)進行訪問與更新的操作所組成的一個程序執(zhí)行單元。
在同一個事務(wù)中所進行的操作,要么都成功,要么就什么都不做。理想中的事務(wù)必須滿足四大特性,這就是大名鼎鼎的ACID。
事務(wù)的ACID特性
并不是所有的事務(wù)都滿足ACID特性,比如:對于Oracle和SQL Server數(shù)據(jù)庫,其默認隔離級別是Read COMMITTED,就不滿足I(隔離性)的要求;對于MySQL的NDB Cluster引擎來說,不滿足D(持久性)的要求。
A(Atomicity)-原子性
原子性指的是數(shù)據(jù)庫事務(wù)是不可分割的一部分,只有一個事務(wù)中的所有操作都成功,這個事務(wù)才算執(zhí)行成功,一旦有一個操作失敗,那么其他成功的操作也必須回滾。
以轉(zhuǎn)賬1000元場景為例,一個轉(zhuǎn)賬過程就是一個事務(wù),這個事務(wù)主要包括以下兩步:
1、從A賬戶扣除1000元
2、將B賬戶中增加1000元
試想,如果第一步成功了,那么第二步失敗了,那就等于A的1000元錢直接消失了,相信這是任何人都不能接受的事項,所以數(shù)據(jù)庫事務(wù)才需要保證原子性。
C(Consistent)-一致性
指的是在事務(wù)開始之前和事務(wù)結(jié)束之后,數(shù)據(jù)庫的完整性約束都沒有被破壞,事務(wù)執(zhí)行的前后都是合法的數(shù)據(jù)狀態(tài)。
比如我們有一張表中有一個字段name建立了一個唯一約束,那么當(dāng)我們進行事務(wù)提交或者事務(wù)回滾之后,這個name必須依然保證唯一。
I(Isolation)-隔離性
隔離性就是說每個事務(wù)之間的操作應(yīng)該相互隔離,互不干擾。比如說一個事務(wù)提交之前對另一個事務(wù)不可見。
隔離是一個相對抽象而復(fù)雜的概念,比如說事務(wù)之間的隔離性我們到底要隔離到哪種程度呢?所以,針對隔離,SQL92標(biāo)準(zhǔn)定義了4種隔離級別,這個放在后面事務(wù)的隔離級別中介紹。
D(Durable)-持久性
持久性這個概念就比較容易理解了,就是說事務(wù)一旦提交成功了,那么就應(yīng)該是持久的,即使是數(shù)據(jù)庫重啟,服務(wù)器宕機等情況發(fā)生,數(shù)據(jù)都不會丟失(當(dāng)然這個不能包括因為地震等自然災(zāi)害導(dǎo)致的存儲數(shù)據(jù)的硬盤損發(fā)生不可逆的損壞)。
事務(wù)的管理
可能很多人會說自己都感知不到MySQL的事務(wù),其實這是因為MySQL事務(wù)是默認開啟了自動提交的,因此,如果要感知到事務(wù),我們需要關(guān)閉自動提交或者顯示開啟事務(wù)。
事務(wù)的自動提交
查看自動提交語句:
SHOW VARIABLES LIKE 'autocommit';-- ON表示開啟了自動提交 SELECT @@autocommit;-- 1表示開啟了自動提交執(zhí)行如下語句關(guān)閉自動提交:
SET autocommit='OFF'; SET @@autocommit = 0;不過需要注意的是,這種修改方式只是在當(dāng)前會話窗口生效,對其他會話窗口是不生效的,MySQL幾乎所有變量設(shè)置都會分成兩個級別,session(會話)和global(全局)級別,默認就是session級別。
常用的事務(wù)控制語句
- START TRANSACTION或者BEGIN:顯示的開啟事務(wù)。需要注意的是在存儲過程中只能用START TRANSACTION開啟事務(wù),因為存儲過程本來有BEGIN…END語法,兩者會沖突。
- COMMIT:提交事務(wù)。也可以寫成COMMIT WORK。
- ROLLBACK:回滾事務(wù)。也可以寫成ROLLBACK WORK。
- SAVEPOINT identifier:自定義保存點,適用于長事務(wù),可以回滾到我們自定義的位置。
- RELEASE SAVEPOINT identifier:刪除一定保存點,如果沒有保存點的時候,會報錯
- ROLLBACK TO[SAVEPOINT] identifier:回滾到指定保存點。
COMMIT和COMMIT WORK的區(qū)別
這兩個都能提交一個事務(wù),區(qū)別就在于提交事務(wù)之后的操作,同樣的還有ROLLBACK和ROLLBACK WORK,主要是通過一個變量來控制:completion_type,可以執(zhí)行下面的sql來查看結(jié)果:
SHOW VARIABLES LIKE '%completion_type%';completion_type有如下三種結(jié)果:
舉個栗子1:
SET completion_type=1; --1 begin;--2 INSERT test2 VALUES(1,'張1');--3 commit work;--4 INSERT test2 VALUES(2,'張1');--5 select * from test2;--6 rollback;--7 select * from test2;--8第4條語句中,我們提交了一個事務(wù),第5條語句中我們又插入了一條數(shù)據(jù),此時第六條語句可以查詢出2條數(shù)據(jù),接下來我們回滾,語句8再去查詢就會發(fā)現(xiàn)只剩一條數(shù)據(jù)了,因為語句6倍回滾了,我們在語句4之后并沒有顯示的開啟一個事務(wù),這就說明語句4自動開啟了一個新的事務(wù)。
舉個栗子2:
SET completion_type=2; begin; INSERT test2 VALUES(3,'張1'); commit work; select * from test2;最后一條語句返回如下結(jié)果:
先提示的斷開連接,然后自動重連。測試這個例子的時候用工具比如sqlyog可能會不是很明顯,因為工具會自動幫忙重連,看起來就好像沒斷開一樣,建議用命令窗口的形式測試
事務(wù)的分類
從事務(wù)的理論角度來說,我們可以把事務(wù)分為以下五大類:
扁平事務(wù)
這種是最簡單也是最常用的一種事務(wù),這種事務(wù)中的所有操作都是原子的,要么全部成功,要么什么都不做。
帶有保存點的扁平事務(wù)
這種一般比較適合于長事務(wù),事務(wù)處理到后面報錯的時候,我們可以選擇不全部回滾事務(wù),而是回滾到我們自定義好的某一個保存點。如下例子:
BEGIN; INSERT test VALUES(1,'張1'); SAVEPOINT A INSERT test VALUES(2,'張2'); ROLLBACK TO A COMMIT;上面示例語句中,我么你定義了一個保存點A,然后在后面又回滾到A,這時候提交事務(wù),那么第二條插入語句是失敗的,而第一條語句是成功的。
注意:回滾到指定保存點之后,事務(wù)仍然還在活動狀態(tài),我們依然需要執(zhí)行COMMIT或者ROLLBACK語句才算結(jié)束了事務(wù)
鏈?zhǔn)聞?wù)
在提交一個事務(wù)之后,釋放掉我們不需要的數(shù)據(jù),將必要的數(shù)據(jù)隱式的傳給下一個事務(wù)。(注意:提交事務(wù)操作和開始下一個事務(wù)操作是一個原子操作)這就意味著下一個事務(wù)能看到上一個事務(wù)的結(jié)果。
鏈?zhǔn)聞?wù)可以看成帶有保存點的特殊事務(wù),他們的區(qū)別就是帶有保存點的事務(wù)可以回滾到任意保存點,但是回滾之后事務(wù)仍然活躍,需要執(zhí)行COMMIT或者ROLLBACK之后才結(jié)束事務(wù),而鏈?zhǔn)聞?wù)中只能回滾到最近的一個保存點(即開始事務(wù)的點)。
鏈?zhǔn)聞?wù)可以通過上面的completion_type參數(shù)來實現(xiàn)。上文中有舉例使用方法,這里就不重復(fù)舉例了。
嵌套事務(wù)
嵌套事務(wù)就是說一個事務(wù)之中嵌套另一個事務(wù),事務(wù)之間存在父子關(guān)系,子事務(wù)的提交之后并不生效,需要等到父事務(wù)提交之后才會生效。
需要注意的是MySQL原生并不支持嵌套事務(wù),但是可以通過保存點模擬嵌套事務(wù),只是說這么模擬的話就沒有真正的嵌套事務(wù)這么靈活。
分布式事務(wù)
分布式事務(wù)通常就是在分布式環(huán)境下,多個數(shù)據(jù)庫下運行不同的扁平事務(wù)。多個數(shù)據(jù)庫環(huán)境下運行的扁平事務(wù)就合成了一個分布式事務(wù)。
事務(wù)的隔離級別
Read Uncommitted(未提交讀)
簡稱RU。這種是最低的隔離級別,等于沒有隔離,基本上沒有數(shù)據(jù)庫會使用這個級別。一個事務(wù)可以讀取到其他事務(wù)未提交的數(shù)據(jù),這種也叫做臟讀。
什么是臟讀?請看下面這個例子:
左邊是事務(wù)1,先查一次,查到id為1的數(shù)據(jù)name為張三,這時候事務(wù)2又來了,把張三改成了李四,然后事務(wù)1又進行了一次查詢,查出來了name為李四,那么假如這時候事務(wù)2發(fā)生了回滾,也就是name還是張三,但是事務(wù)1卻讀到了李四,這就是臟讀。
Read Committed(已提交讀)
簡稱RC。一個事務(wù)只能讀取到其他事務(wù)已提交的數(shù)據(jù),就是說在一個事務(wù)里面,執(zhí)行同樣的查詢,會出現(xiàn)兩次不一樣的結(jié)果。Oracle和SQL Server數(shù)據(jù)庫默認的數(shù)據(jù)庫隔離級別。這種隔離級別解決了臟讀問題,但是會出現(xiàn)不可重復(fù)讀的問題。
什么是不可重復(fù)讀?還是看上面那個例子,假設(shè)事務(wù)2更新之后馬上就提交,然后事務(wù)1第二次查詢查出來的結(jié)果還是李四,只是這次就不算是臟讀了,因為事務(wù)2提交了,這種就叫不可重復(fù)讀,因為事務(wù)1中兩次查詢同一條數(shù)據(jù)結(jié)果不一樣。
Repeatable Read(可重復(fù)讀)
簡稱RR。這種隔離級別解決了不可重復(fù)讀問題,就是說在同一個事務(wù)中,執(zhí)行相同的查詢,結(jié)果都是一樣的,但是這種級別會出現(xiàn)幻讀問題(InnoDB引擎例外,InnoDB引擎通過間隙鎖解決了幻讀問題)。
什么是幻讀?請看下面這個例子:
上面圖形中,事務(wù)1進行了一個范圍查詢,第一次只能查出一條記錄,這時候事務(wù)2來插入了一條數(shù)據(jù),然后事務(wù)1再次執(zhí)行同一個查詢,這時候就能查出來兩條記錄,也就是多了一條,給人一種幻覺,所以稱之為幻讀。
說到這里,可能有人就有疑問了,因為感覺不可重復(fù)讀和幻讀都是讀取到已提交事務(wù)的結(jié)果,好像沒什么區(qū)別?確實如此,不可重復(fù)讀和幻讀本質(zhì)上是一樣的,但是不可重復(fù)讀針對的是更新和刪除操作,而幻讀僅針對插入操作。
Serializable(串行化)
這種是隔離的最高級別,也就是說所有的事務(wù)都是串行執(zhí)行的,也就不存在并發(fā)事務(wù),臟讀,可重復(fù)讀和幻讀問題自然也就沒有了。
不同隔離級別對比
不同的隔離級別可以解決不同的問題,大致如下圖:
對于未提交讀和已提交讀大家可能都很好理解,只要控制一個事務(wù)提交之后才能對另一個事務(wù)可見,但是對于可重復(fù)讀,MySQL到底是如何實現(xiàn)即使一個事務(wù)已經(jīng)提交了,還能對另一個事務(wù)不可見呢?這就是接下來我們要講解的MVCC了。
事務(wù)隔離的實現(xiàn)方案
事務(wù)隔離的實現(xiàn)方案有兩種,LBCC和MVCC
LBCC
LBCC,基于鎖的并發(fā)控制,英文全稱Based Concurrency Control。這種方案比較簡單粗暴,就是一個事務(wù)去讀取一條數(shù)據(jù)的時候,就上鎖,不允許其他事務(wù)來操作(當(dāng)然這個鎖的實現(xiàn)也比較重要,如果我們只鎖定當(dāng)前一條數(shù)據(jù)依然無法解決幻讀問題)。
當(dāng)前讀
這個概念其實很好理解,MySQL加鎖之后就是當(dāng)前讀。假如當(dāng)前事務(wù)只是加共享鎖,那么其他事務(wù)就不能有排他鎖,也就是不能修改數(shù)據(jù);而假如當(dāng)前事務(wù)需要加排他鎖,那么其他事務(wù)就不能持有任何鎖。總而言之,能加鎖成功,就確保了除了當(dāng)前事務(wù)之外,其他事務(wù)不會對當(dāng)前數(shù)據(jù)產(chǎn)生影響,所以自然而然的,當(dāng)前事務(wù)讀取到的數(shù)據(jù)就只能是最新的,而不會是快照數(shù)據(jù)(后文MVCC會解釋快照讀概念)。
LBCC方案中,如果我們的業(yè)務(wù)系統(tǒng)是讀多寫少的話,這種方案就會極大影響了效率,所以我們就有了另一種解決方案:MVCC。
MVCC
MVCC,多版本的并發(fā)控制,英文全稱:Multi Version Concurrency Control。就是當(dāng)我們在修改數(shù)據(jù)的時候,可以為這條數(shù)據(jù)創(chuàng)建一個快照,后面就可以直接讀取這個快照。
那么MVCC具體到底是如何實現(xiàn)的呢?
為了實現(xiàn)MVCC機制,InnoDB內(nèi)部為每一行添加了兩個隱藏列:DB_TRX_ID和DB_ROLL_PTR(MySQL另外還有一個隱藏列DB_ROW_ID,這是在InnoDB表沒有主鍵的時候會用來作為主鍵,想詳細了解可以點擊這里)。
DB_TRX_ID
長度為6字節(jié),存儲了插入或更新語句的最后一個事務(wù)的事務(wù)ID。
DB_ROLL_PTR
長度為7字節(jié),稱之為:回滾指針。回滾指針指向?qū)懭牖貪L段的undo log記錄,讀取記錄的時候會根據(jù)指針去讀取undo log中的記錄。
正因為MySQL中undo log中會維護一個歷史數(shù)據(jù)記錄,所以我們應(yīng)該養(yǎng)成定期提交事務(wù)的習(xí)慣,否則回滾段會越來越大,甚至占滿了表空間。
快照讀
快照讀是針對上文的當(dāng)前讀而言,指的是在RR隔離級別下,在不加鎖的情況下MySQL會根據(jù)回滾指針選擇從undo log記錄中獲取快照數(shù)據(jù),而不總是獲取最新的數(shù)據(jù),這也就是為什么另一個事務(wù)提交了數(shù)據(jù),在當(dāng)前事務(wù)中看到的依然是另一個事務(wù)提交之前的數(shù)據(jù)。
MySQL什么時候開始讀取快照
我們先看看MySQL默認隔離級別RR下的一個例子(注意,test和test2兩張表一開始都是空表,均只有id和name兩個字段)。
- 場景1(事務(wù)1操作數(shù)據(jù)之后再進行第一次查詢):
- 場景2(事務(wù)1不進行任何操作,事務(wù)2先開始第一次查詢)
通過上面兩個場景中我們可以得出結(jié)論:RR隔離級別快照并不是在BEGIN就開始產(chǎn)生了,而是要等到事務(wù)當(dāng)中的第一次查詢之后才會產(chǎn)生快照,之后的查詢就只讀取這個快照數(shù)據(jù)
- 場景3(事務(wù)2先進行一次t1表查詢之后,事務(wù)1再去操作其他表t2)
從場景3我們可以得出結(jié)論:RR隔離級別快照并不只是針對當(dāng)前所查詢的數(shù)據(jù),而是針對當(dāng)前MySQL中的所有數(shù)據(jù)(跨庫也一樣,只要在同一個MySQL)
MVCC查詢機制
MVCC機制到底如何查詢的呢?假設(shè)由很多個事務(wù)同時進行,那么就會產(chǎn)生很多快照,查詢的時候又到底是怎么做的呢?
接下來我們把抽象的概念具體化,假定DB_TRX_ID和DB_ROLL_PTR均為整型,接下來我們進行查詢演示:
1、清空原先的test表,事務(wù)A插入兩條數(shù)據(jù),此時DB_TRX_ID(事務(wù)id)為1,DB_ROLL_PTR(回滾指針為null)
2、這時候事務(wù)B進行了一次查詢,會得到上面的結(jié)果,事務(wù)2還沒提交的時候又來了事務(wù)C,事務(wù)C插入了id=3的數(shù)據(jù),此時表中的數(shù)據(jù)如下:
注意,這時候第3條數(shù)據(jù)的事務(wù)id為3,因為事務(wù)2也會產(chǎn)生一個事務(wù)id
3、這時候事務(wù)B再次進行查詢,根據(jù)上面了解的,我們知道,這時候應(yīng)該是查詢不出王五的,所以實際上二次查詢可能是這么查的:
4、假如這時候事務(wù)D又來了,把id=1的數(shù)據(jù)給刪除了,這時候會把原數(shù)據(jù)的回滾指針記錄為當(dāng)前的事務(wù)id:4,所以此時數(shù)據(jù)如下:
5、回到事務(wù)B,繼續(xù)查詢,應(yīng)該還是只有1和2兩條數(shù)據(jù),那么他可能是這么查詢的:
select * from test where 事務(wù)id<=2 and (回滾指針 is null or 回滾指針 >2)6、假如這時候又來了事務(wù)E,對第2條數(shù)據(jù)進行了更新,這時候會生產(chǎn)一條事務(wù)id為5的數(shù)據(jù),并把原數(shù)據(jù)的回滾指針也同時標(biāo)記為當(dāng)前的事務(wù)id:5,那么會得到如下數(shù)據(jù):
根據(jù)上面猜測,執(zhí)行下面的查詢:
select * from test where 事務(wù)id<=2 and (回滾指針 is null or 回滾指針 >2)這時候發(fā)現(xiàn),查出來的數(shù)據(jù)還是只有1和2兩條。
MVCC查詢兩大規(guī)則
綜上,MVCC大致查詢規(guī)則如下:
1、只查詢事務(wù)id小于等于當(dāng)前事務(wù)id的數(shù)據(jù)。(這里要等于是因為假如自己的事務(wù)插入了一條數(shù)據(jù),會生成一條當(dāng)前事務(wù)id的數(shù)據(jù),所以必須包含本事務(wù)自己插入的數(shù)據(jù))
2、只查詢未刪除(回滾指針為空)或者回滾指針大于當(dāng)前事務(wù)id的數(shù)據(jù)。(這里不能等于是因為假如自己的事務(wù)刪除了一條數(shù)據(jù),會生成數(shù)據(jù)的回滾指針為當(dāng)前事務(wù)id,所以必須排除掉自己刪除的數(shù)據(jù))
當(dāng)然,上面規(guī)則只是簡化了,實際查詢遠比這里復(fù)雜,只是希望借助這種簡單化的概念可以幫助大家更好的理解MVCC工作機制。
作者:雙子孤狼原文鏈接:https://blog.csdn.net/zwx900102/article/details/106544843
總結(jié)
以上是生活随笔為你收集整理的limit实现原理 mysql_解读数据库:深入分析MySQL中事务以及MVCC的实现原理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux系统atom安装教程,Ubun
- 下一篇: 药学专业报计算机一级有用吗,全网友泣泪劝