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