一個(gè)查詢語句的例子
在執(zhí)行下面這個(gè)查詢語句時(shí)的執(zhí)行的流程是怎么樣的?
MySQL> select * from t where id=1;
執(zhí)行器的執(zhí)行流程是這樣的:
1.調(diào)用 InnoDB 引擎接口取這個(gè)表的第一行,判斷 id 值是不是 1,如果不是則跳過,如果是則將這行存在結(jié)果集中;
2.調(diào)用引擎接口取“下一行”,重復(fù)相同的判斷邏輯,直到取到這個(gè)表的最后一行。
3.執(zhí)行器將上述遍歷過程中所有滿足條件的行組成的記錄集作為結(jié)果集返回給客戶端。
一個(gè) SQL 的執(zhí)行過程為:
1.連接 2.查詢緩存 3.詞法分析 4.語法分析 5.語義分析 6.構(gòu)造執(zhí)行樹 7.生成執(zhí)行計(jì)劃 8.執(zhí)行器執(zhí)行計(jì)劃 9.返回執(zhí)行結(jié)果
一個(gè)更新語句的例子
更新語句:
mysql> update table set c = c+1 where id = 2;
執(zhí)行流程:
1.執(zhí)行器先找引擎取 ID=2 這一行。ID 是主鍵,引擎直接用樹搜索找到這一行。如果 ID=2 這一行所在的數(shù)據(jù)頁本來就在內(nèi)存中,就直接返回給執(zhí)行器;否則,需要先從磁盤讀入內(nèi)存,然后再返回。 2.執(zhí)行器拿到引擎給的行數(shù)據(jù),把這個(gè)值加上1,比如原來是N,現(xiàn)在就是N+1,得到新的一行數(shù)據(jù),再調(diào)用引擎接口寫入這行新數(shù)據(jù)。 3.引擎將這行新數(shù)據(jù)更新到內(nèi)存中,同時(shí)將這個(gè)更新操作記錄到 redo log 里面,此時(shí) redo log 處于 prepare 狀態(tài)。然后告知執(zhí)行器執(zhí)行完成了,隨時(shí)可以提交事務(wù)。 4.執(zhí)行器生成這個(gè)操作的 binlog,并把 binlog 寫入磁盤。 5.執(zhí)行器調(diào)用引擎的提交事務(wù)接口,引擎把剛剛寫入的 redo log 改成提交(commit)狀態(tài),更新完成。
query和update執(zhí)行流程不一樣的在于 update涉及了日志模塊,binlog (歸檔日志)和 redo log (重做日志)。
兩階段提交
redo log 的寫入拆成了兩個(gè)步驟:prepare 和 commit,這就是"兩階段提交"。
為什么必須有“兩階段提交”呢?這是為了讓兩份日志之間的邏輯一致。
由于 redo log 和 binlog 是兩個(gè)獨(dú)立的邏輯,如果不用兩階段提交,要么就是先寫完 redo log 再寫 binlog,或者采用反過來的順序。我們看看這兩種方式會(huì)有什么問題。
仍然用前面的 update 語句來做例子。假設(shè)當(dāng)前 ID=2 的行,字段 c 的值是 0,再假設(shè)執(zhí)行 update語句過程中在寫完第一個(gè)日志后,第二個(gè)日志還沒有寫完期間發(fā)生了 crash,會(huì)出現(xiàn)什么情況呢?
情況 1 :先寫 redo log 后寫 binlog。
假設(shè)在 redo log 寫完,binlog 還沒有寫完的時(shí)候,MySQL 進(jìn)程異常重啟。由于我們前面說過的,redo log 寫完之后,系統(tǒng)即使崩潰,仍然能夠把數(shù)據(jù)恢復(fù)回來,所以恢復(fù)后這一行 c 的值是 1。但是由于 binlog 沒寫完就 crash 了,這時(shí)候 binlog 里面就沒有記錄這個(gè)語句。因此,之后備份日志的時(shí)候,存起來的 binlog 里面就沒有這條語句。然后你會(huì)發(fā)現(xiàn),如果需要用這個(gè) binlog 來恢復(fù)臨時(shí)庫的話,由于這個(gè)語句的 binlog 丟失,這個(gè)臨時(shí)庫就會(huì)少了這一次更新,恢復(fù)出來的這一行 c 的值就是 0,與原庫的值不同。
情況 2:先寫 binlog 后寫 redo log。
如果在 binlog 寫完之后 crash,由于 redo log 還沒寫,崩潰恢復(fù)以后這個(gè)事務(wù)無效,所以這一行 c 的值是 0。但是 binlog 里面已經(jīng)記錄了“把 c 從 0 改成 1”這個(gè)日志。所以,在之后用 binlog 來恢復(fù)的時(shí)候就多了一個(gè)事務(wù)出來,恢復(fù)出來的這一行 c 的值就是 1,與原庫的值不同。
可以看到,如果不使用“兩階段提交”,那么數(shù)據(jù)庫的狀態(tài)就有可能和用它的日志恢復(fù)出來的庫的狀態(tài)不一致。
MySQL邏輯架構(gòu)
MySQL是一個(gè)開放源代碼的關(guān)系數(shù)據(jù)庫管理系統(tǒng)。原開發(fā)者為瑞典的MySQL AB公司,最早是在2001年MySQL3.23進(jìn)入到管理員的視野并在之后獲得廣泛的應(yīng)用。
當(dāng)MySQL啟動(dòng)(MySQL服務(wù)器就是一個(gè)進(jìn)程),等待客戶端連接,每一個(gè)客戶端連接請求,服務(wù)器都會(huì)新建一個(gè)線程處理(如果是線程池的話,則是分配一個(gè)空的線程),每個(gè)線程獨(dú)立,擁有各自的內(nèi)存處理空間。

MySQL總體上可分為Server層和存儲(chǔ)引擎層。
Server層包括連接器、查詢器、分析器、優(yōu)化器、執(zhí)行器等,涵蓋 MySQL 的大多數(shù)核心服務(wù)功能,以及所有的內(nèi)置函數(shù)(如日期、時(shí)間、數(shù)學(xué)和加密函數(shù)等),所有跨存儲(chǔ)引擎的功能都在這一層實(shí)現(xiàn),比如存儲(chǔ)過程、觸發(fā)器、視圖等。
存儲(chǔ)引擎層負(fù)責(zé)數(shù)據(jù)的存儲(chǔ)和提取。其架構(gòu)模式是插件式的,支持 InnoDB、MyISAM、Memory 等多個(gè)存儲(chǔ)引擎。
如果能在頭腦中構(gòu)建一幅MySQL各組件之間如何協(xié)同工作的架構(gòu)圖,有助于深入理解MySQL服務(wù)器。下圖展示了MySQL的邏輯架構(gòu)圖。

MySQL 整體上可以分為 Server 層和存儲(chǔ)引擎層兩部分。詳細(xì)的分層如下:
1.客戶端層:連接處理、授權(quán)認(rèn)證、安全等功能均在這一層處理。包含本地sock通信和大多數(shù)基于客戶端/服務(wù)端工具實(shí)現(xiàn)的類似于tcp/ip的通信。主要完成一些類似于連接處理、授權(quán)認(rèn)證、及相關(guān)的安全方案。在該層上引入了線程池的概念,為通過認(rèn)證安全接入的客戶端提供線程。同樣在該層上可以實(shí)現(xiàn)基于SSL的安全鏈接。服務(wù)器也會(huì)為安全接入的每個(gè)客戶端驗(yàn)證它所具有的操作權(quán)限。
2.核心服務(wù)層:查詢解析、分析、優(yōu)化、緩存、內(nèi)置函數(shù)(比如:時(shí)間、數(shù)學(xué)、加密等函數(shù))等。該層架構(gòu)主要完成核心服務(wù)功能,如SQL接口,并完成緩存的查詢,SQL的分析和優(yōu)化及部分內(nèi)置函數(shù)的執(zhí)行。所有跨存儲(chǔ)引擎的功能也在這一層實(shí)現(xiàn),如過程、函數(shù)等。在該層,服務(wù)器會(huì)解析查詢并創(chuàng)建相應(yīng)的內(nèi)部解析樹,并對其完成相應(yīng)的優(yōu)化如確定查詢表的順序,是否利用索引等,最后生成相應(yīng)的執(zhí)行操作。如果是select語句,服務(wù)器還會(huì)查詢內(nèi)部的緩存。如果緩存空間足夠大,這樣在解決大量讀操作的環(huán)境中能夠很好的提升系統(tǒng)的性能。
3.存儲(chǔ)引擎層:存儲(chǔ)過程、觸發(fā)器、視圖等。存儲(chǔ)引擎真正的負(fù)責(zé)了MySQL中數(shù)據(jù)的存儲(chǔ)和提取,服務(wù)器通過API與存儲(chǔ)引擎進(jìn)行通信。不同的存儲(chǔ)引擎具有的功能不同,這樣我們可以根據(jù)自己的實(shí)際需要進(jìn)行選取。
4.數(shù)據(jù)存儲(chǔ)層,主要是將數(shù)據(jù)存儲(chǔ)在運(yùn)行于裸設(shè)備的文件系統(tǒng)之上,并完成與存儲(chǔ)引擎的交互。
最下層為存儲(chǔ)引擎,其負(fù)責(zé)MySQL中的數(shù)據(jù)存儲(chǔ)和提取。和linux下的文件系統(tǒng)類似,每種存儲(chǔ)引擎都有其優(yōu)勢和劣勢。中間的服務(wù)層通過API與存儲(chǔ)引擎通信,這些API接口屏蔽了不同存儲(chǔ)引擎間的差異。
MySQL查詢過程
我們總是希望MySQL能夠獲得更高的查詢性能,最好的辦法是弄清楚MySQL是如何優(yōu)化和執(zhí)行查詢的。一旦理解了這一點(diǎn),就會(huì)發(fā)現(xiàn):很多的查詢優(yōu)化工作實(shí)際上就是遵循一些原則讓MySQL的優(yōu)化器能夠按照預(yù)想的合理方式運(yùn)行而已。
當(dāng)向MySQL發(fā)送一個(gè)請求的時(shí)候,MySQL到底做了些什么呢?

客戶端/服務(wù)端通信協(xié)議
MySQL客戶端/服務(wù)端通信協(xié)議是“半雙工”的:在任一時(shí)刻,要么是服務(wù)器向客戶端發(fā)送數(shù)據(jù),要么是客戶端向服務(wù)器發(fā)送數(shù)據(jù),這兩個(gè)動(dòng)作不能同時(shí)發(fā)生。一旦一端開始發(fā)送消息,另一端要接收完整個(gè)消息才能響應(yīng)它,所以我們無法也無須將一個(gè)消息切成小塊獨(dú)立發(fā)送,也沒有辦法進(jìn)行流量控制。
客戶端用一個(gè)單獨(dú)的數(shù)據(jù)包將查詢請求發(fā)送給服務(wù)器,所以當(dāng)查詢語句很長的時(shí)候,需要設(shè)置max_allowed_packet參數(shù)。但是需要注意的是,如果查詢實(shí)在是太大,服務(wù)端會(huì)拒絕接收更多數(shù)據(jù)并拋出異常。
與之相反的是,服務(wù)器響應(yīng)給用戶的數(shù)據(jù)通常會(huì)很多,由多個(gè)數(shù)據(jù)包組成。但是當(dāng)服務(wù)器響應(yīng)客戶端請求時(shí),客戶端必須完整的接收整個(gè)返回結(jié)果,而不能簡單的只取前面幾條結(jié)果,然后讓服務(wù)器停止發(fā)送。因而在實(shí)際開發(fā)中,盡量保持查詢簡單且只返回必需的數(shù)據(jù),減小通信間數(shù)據(jù)包的大小和數(shù)量是一個(gè)非常好的習(xí)慣,這也是查詢中盡量避免使用SELECT *以及加上LIMIT限制的原因之一。
查詢緩存
在解析一個(gè)查詢語句前,如果查詢緩存是打開的,那么MySQL會(huì)檢查這個(gè)查詢語句是否命中查詢緩存中的數(shù)據(jù)。如果當(dāng)前查詢恰好命中查詢緩存,在檢查一次用戶權(quán)限后直接返回緩存中的結(jié)果。這種情況下,查詢不會(huì)被解析,也不會(huì)生成執(zhí)行計(jì)劃,更不會(huì)執(zhí)行。
MySQL將緩存存放在一個(gè)引用表(不要理解成table,可以認(rèn)為是類似于HashMap的數(shù)據(jù)結(jié)構(gòu)),通過一個(gè)哈希值索引,這個(gè)哈希值通過查詢本身、當(dāng)前要查詢的數(shù)據(jù)庫、客戶端協(xié)議版本號等一些可能影響結(jié)果的信息計(jì)算得來。所以兩個(gè)查詢在任何字符上的不同(例如:空格、注釋),都會(huì)導(dǎo)致緩存不會(huì)命中。
如果查詢中包含任何用戶自定義函數(shù)、存儲(chǔ)函數(shù)、用戶變量、臨時(shí)表、MySQL庫中的系統(tǒng)表,其查詢結(jié)果都不會(huì)被緩存。比如函數(shù)NOW()或者CURRENT_DATE()會(huì)因?yàn)椴煌牟樵儠r(shí)間,返回不同的查詢結(jié)果,再比如包含CURRENT_USER或者CONNECION_ID()的查詢語句會(huì)因?yàn)椴煌挠脩舳祷夭煌慕Y(jié)果,將這樣的查詢結(jié)果緩存起來沒有任何的意義。
既然是緩存,就會(huì)失效,那查詢緩存何時(shí)失效呢?MySQL的查詢緩存系統(tǒng)會(huì)跟蹤查詢中涉及的每個(gè)表,如果這些表(數(shù)據(jù)或結(jié)構(gòu))發(fā)生變化,那么和這張表相關(guān)的所有緩存數(shù)據(jù)都將失效。正因?yàn)槿绱耍谌魏蔚膶懖僮鲿r(shí),MySQL必須將對應(yīng)表的所有緩存都設(shè)置為失效。如果查詢緩存非常大或者碎片很多,這個(gè)操作就可能帶來很大的系統(tǒng)消耗,甚至導(dǎo)致系統(tǒng)僵死一會(huì)兒。而且查詢緩存對系統(tǒng)的額外消耗也不僅僅在寫操作,讀操作也不例外:
- 任何的查詢語句在開始之前都必須經(jīng)過檢查,即使這條SQL語句永遠(yuǎn)不會(huì)命中緩存
- 如果查詢結(jié)果可以被緩存,那么執(zhí)行完成后,會(huì)將結(jié)果存入緩存,也會(huì)帶來額外的系統(tǒng)消耗
基于此,我們要知道并不是什么情況下查詢緩存都會(huì)提高系統(tǒng)性能,緩存和失效都會(huì)帶來額外消耗,只有當(dāng)緩存帶來的資源節(jié)約大于其本身消耗的資源時(shí),才會(huì)給系統(tǒng)帶來性能提升。但要如何評估打開緩存是否能夠帶來性能提升是一件非常困難的事情,也不在本文討論的范疇內(nèi)。如果系統(tǒng)確實(shí)存在一些性能問題,可以嘗試打開查詢緩存,并在數(shù)據(jù)庫設(shè)計(jì)上做一些優(yōu)化,比如:
- 用多個(gè)小表代替一個(gè)大表,注意不要過度設(shè)計(jì)
- 批量插入代替循環(huán)單條插入
- 合理控制緩存空間大小,一般來說其大小設(shè)置為幾十兆比較合適
- 可以通過SQL_CACHE和SQL_NO_CACHE來控制某個(gè)查詢語句是否需要進(jìn)行緩存
最后的忠告是不要輕易打開查詢緩存,特別是寫密集型應(yīng)用。如果你實(shí)在是忍不住,可以將query_cache_type設(shè)置為DEMAND,這時(shí)只有加入SQL_CACHE的查詢才會(huì)走緩存,其他查詢則不會(huì),這樣可以非常自由地控制哪些查詢需要被緩存。

當(dāng)然查詢緩存系統(tǒng)本身是非常復(fù)雜的,這里討論的也只是很小的一部分,其他更深入的話題,比如:緩存是如何使用內(nèi)存的?如何控制內(nèi)存的碎片化?事務(wù)對查詢緩存有何影響等等,讀者可以自行閱讀相關(guān)資料,這里權(quán)當(dāng)拋磚引玉吧。
語法解析和預(yù)處理

MySQL通過關(guān)鍵字將SQL語句進(jìn)行解析,并生成一顆對應(yīng)的解析樹。
這個(gè)過程解析器主要通過語法規(guī)則來驗(yàn)證和解析。比如SQL中是否使用了錯(cuò)誤的關(guān)鍵字或者關(guān)鍵字的順序是否正確等等。預(yù)處理則會(huì)根據(jù)MySQL規(guī)則進(jìn)一步檢查解析樹是否合法。比如檢查要查詢的數(shù)據(jù)表和數(shù)據(jù)列是否存在等。
SQL總體執(zhí)行流程圖

查詢優(yōu)化

經(jīng)過前面的步驟生成的語法樹被認(rèn)為是合法的了,并且由優(yōu)化器將其轉(zhuǎn)化成查詢計(jì)劃。多數(shù)情況下,一條查詢可以有很多種執(zhí)行方式,最后都返回相應(yīng)的結(jié)果。優(yōu)化器的作用就是找到這其中最好的執(zhí)行計(jì)劃。
MySQL使用基于成本的優(yōu)化器,它嘗試預(yù)測一個(gè)查詢使用某種執(zhí)行計(jì)劃時(shí)的成本,并選擇其中成本最小的一個(gè)。在MySQL可以通過查詢當(dāng)前會(huì)話的last_query_cost的值來得到其計(jì)算當(dāng)前查詢的成本。
數(shù)據(jù)庫事務(wù)的概念及其實(shí)現(xiàn)原理
數(shù)據(jù)庫事務(wù)(Database Transaction)概述
什么是事務(wù)?
- 轉(zhuǎn)賬的例子
- 1.轉(zhuǎn)賬操作的第一步執(zhí)行成功,A賬戶上的錢減少了100元,但是第二步執(zhí)行失敗或者未執(zhí)行便發(fā)生系統(tǒng)崩潰,導(dǎo)致B賬戶并沒有相應(yīng)增加100元。
- 2.轉(zhuǎn)賬操作剛完成就發(fā)生系統(tǒng)崩潰,系統(tǒng)重啟恢復(fù)時(shí)丟失了崩潰前的轉(zhuǎn)賬記錄。
- 3.同時(shí)又另一個(gè)用戶轉(zhuǎn)賬給B賬戶,由于同時(shí)對B賬戶進(jìn)行操作,導(dǎo)致B賬戶金額出現(xiàn)異常。
- 1.將A賬戶的金額減少100元
- 2.將B賬戶的金額增加100元。
- 從A賬戶轉(zhuǎn)賬100元到B賬號。站在用戶角度而言,這是一個(gè)邏輯上的單一操作,然而在數(shù)據(jù)庫系統(tǒng)中,至少會(huì)分成兩個(gè)步驟來完成:
- 在這個(gè)過程中可能會(huì)出現(xiàn)以下問題:
- 為了便于解決這些問題,需要引入數(shù)據(jù)庫事務(wù)的概念。
- 定義
- 數(shù)據(jù)庫管理系統(tǒng)執(zhí)行過程中的一個(gè)邏輯單位,由一個(gè)有限的數(shù)據(jù)庫操作序列構(gòu)成。
- 必須滿足ACID屬性
- 例子
- 一個(gè)典型的數(shù)據(jù)庫事務(wù)如下所示
BEGIN TRANSACTION //事務(wù)開始 SQL1 SQL2 COMMIT/ROLLBACK //事務(wù)提交或回滾
為什么要有事務(wù)?
- 為數(shù)據(jù)庫操作序列提供了一個(gè)從失敗中恢復(fù)到正常狀態(tài)的方法,同時(shí)提供了數(shù)據(jù)庫即使在異常狀態(tài)下仍能保持一致性的方法。
- 當(dāng)多個(gè)應(yīng)用程序在并發(fā)訪問數(shù)據(jù)庫時(shí),可以在這些應(yīng)用程序之間提供一個(gè)隔離方法,以防止彼此的操作互相干擾。
ACID 特性
- 原子性(Atomicity):事務(wù)作為一個(gè)整體被執(zhí)行,包含在其中的對數(shù)據(jù)庫的操作要么全部被執(zhí)行,要么都不執(zhí)行。
- 一致性(Consistency):事務(wù)應(yīng)確保數(shù)據(jù)庫的狀態(tài)從一個(gè)一致狀態(tài)轉(zhuǎn)變?yōu)榱硪粋€(gè)一致狀態(tài),一致狀態(tài)的含義是數(shù)據(jù)庫中的數(shù)據(jù)應(yīng)滿足完整性約束。
- 隔離性(Isolation):多個(gè)事務(wù)并發(fā)執(zhí)行時(shí),一個(gè)事務(wù)的執(zhí)行不應(yīng)影響其他事務(wù)的執(zhí)行。
- 持久性(Durability):已被提交的事務(wù)對數(shù)據(jù)庫的修改應(yīng)該永久保存在數(shù)據(jù)庫中。
隔離級別
- 四個(gè)級別
- Read Uncommitted 讀未提交:就是一個(gè)事務(wù)可以讀取另一個(gè)未提交事務(wù)的數(shù)據(jù)。
- Read Committed 讀提交:就是一個(gè)事務(wù)要等另一個(gè)事務(wù)提交后才能讀取數(shù)據(jù)。若有事務(wù)對數(shù)據(jù)進(jìn)行更新(UPDATE)操作時(shí),讀操作事務(wù)要等待這個(gè)更新操作事務(wù)提交后才能讀取數(shù)據(jù),可以解決臟讀問題。
- Repeatable Read 重復(fù)讀:就是在開始讀取數(shù)據(jù)(事務(wù)開啟)時(shí),不再允許修改操作。重復(fù)讀可以解決不可重復(fù)讀問題。寫到這里,應(yīng)該明白的一點(diǎn)就是,不可重復(fù)讀對應(yīng)的是修改,即UPDATE操作。但是可能還會(huì)有幻讀問題。因?yàn)榛米x問題對應(yīng)的是插入INSERT操作,而不是UPDATE操作。
- Serializable 順序讀:是最高的事務(wù)隔離級別,在該級別下,事務(wù)串行化順序執(zhí)行,可以避免臟讀、不可重復(fù)讀與幻讀。但是這種事務(wù)隔離級別效率低下,比較耗數(shù)據(jù)庫性能,一般不使用。
- mysql 對應(yīng)的InnoDB默認(rèn)隔離級別是 重復(fù)讀
- 所有事務(wù)隔離級別都不允許出現(xiàn)臟寫,而串行化可以避免所有可能出現(xiàn)的并發(fā)異常,但是會(huì)極大的降低系統(tǒng)的并發(fā)處理能力。
- 共享鎖與排它鎖
- 排它鎖
- 共享鎖
- 讀
- 寫
- 隔離怎么實(shí)現(xiàn)?
- 共享鎖(S鎖) :(插入/修改/刪除)資源獲取S鎖之后,能加S鎖,不能加X鎖
- 排它鎖(X鎖) :資源加上X鎖之后,不能加S鎖,也不能加X鎖
- 鎖
- InnoDB存在兩種鎖
數(shù)據(jù)庫事務(wù)實(shí)現(xiàn)原理剖析
事務(wù)的實(shí)現(xiàn)原理
- 事務(wù)的執(zhí)行過程
- 系統(tǒng)會(huì)為每個(gè)事務(wù)開辟一個(gè)私有工作區(qū)
- 事務(wù)讀操作將從磁盤中拷貝數(shù)據(jù)項(xiàng)到工作區(qū)中,在執(zhí)行寫操作前所有的更新都作用于工作區(qū)中的拷貝.
- 事務(wù)的寫操作將把數(shù)據(jù)輸出到內(nèi)存的緩沖區(qū)中,再由緩沖區(qū)管理器將數(shù)據(jù)寫入到磁盤。
- 分特性
- 鎖
- MVCC 多版本并發(fā)控制
- 人們一般把基于鎖的并發(fā)控制機(jī)制稱成為悲觀機(jī)制,而把MVCC機(jī)制稱為樂觀機(jī)制。這是因?yàn)殒i機(jī)制是一種預(yù)防性的,讀會(huì)阻塞寫,寫也會(huì)阻塞讀,當(dāng)鎖定粒度較大,時(shí)間較長時(shí)并發(fā)性能就不會(huì)太好;而MVCC是一種后驗(yàn)性的,讀不阻塞寫,寫也不阻塞讀,等到提交的時(shí)候才檢驗(yàn)是否有沖突,由于沒有鎖,所以讀寫不會(huì)相互阻塞,從而大大提升了并發(fā)性能。
- 通過增加系統(tǒng)版本號,每次事務(wù)操作,會(huì)比較系統(tǒng)版本號
- InnoDB為每行記錄添加了一個(gè)版本號(系統(tǒng)版本號),每當(dāng)修改數(shù)據(jù)時(shí),版本號加一。在讀取事務(wù)開始時(shí),系統(tǒng)會(huì)給事務(wù)一個(gè)當(dāng)前版本號,事務(wù)會(huì)讀取版本號<=當(dāng)前版本號的數(shù)據(jù),這時(shí)就算另一個(gè)事務(wù)插入一個(gè)數(shù)據(jù),并立馬提交,新插入這條數(shù)據(jù)的版本號會(huì)比讀取事務(wù)的版本號高,因此讀取事務(wù)讀的數(shù)據(jù)還是不會(huì)變。
- MVCC 是什么?
- 基于CAS(Compare-and-swap)
- 有條件更新(Conditional Update)
- 通過undo log 來實(shí)現(xiàn)
- 原子性是事務(wù)的基本特性,保證了事務(wù)中的操作是不可拆分的整體,那么原子性是如何實(shí)現(xiàn)的呢?事務(wù)的原子性表現(xiàn)的兩個(gè)方面:
- 通過redo log 來實(shí)現(xiàn)
- 事務(wù)提交失敗,那么事務(wù)中的操作都失敗,這個(gè)是通過數(shù)據(jù)庫的撤銷操作日志來保證的,也稱之為undo log。
- 事務(wù)提交成功保證事務(wù)中的操作都會(huì)完成。1、是正確執(zhí)行完事務(wù),沒有出現(xiàn)任何問題;2、是事務(wù)提交成功但是出異常,數(shù)據(jù)庫恢復(fù)之后,提交完成的事務(wù)會(huì)保證數(shù)據(jù)庫完成該事物的操作。對于第一種正常情況不予討論,因?yàn)椴淮嬖?異常情況,那么第2種實(shí)際上是和上文說的持久性是相關(guān)聯(lián)的,而這個(gè)是基于重做日志(redo log)來保證提交完成的事務(wù)在異常情況下保證數(shù)據(jù)操作能夠進(jìn)行:
- 事務(wù)提交成功時(shí),那么事務(wù)中的操作總會(huì)完成
- 事務(wù)提交失敗,那么事務(wù)中的操作都失敗
- 原子性
- 一致性
- 隔離性
- Undo原理 與 Redo原理
- 和Undo Log相反,Redo Log記錄的是新數(shù)據(jù)的備份。在事務(wù)提交前,只要將Redo Log持久化即可,不需要將數(shù)據(jù)持久化。當(dāng)系統(tǒng)崩潰時(shí),雖然數(shù)據(jù)沒有持久化,但是Redo Log已經(jīng)持久化。系統(tǒng)可以根據(jù)Redo Log的內(nèi)容,將所有數(shù)據(jù)恢復(fù)到最新的狀態(tài)。
- 在操作任何數(shù)據(jù)之前,首先將數(shù)據(jù)備份到一個(gè)地方(這個(gè)存儲(chǔ)數(shù)據(jù)備份的地方稱為Undo Log)。然后進(jìn)行數(shù)據(jù)的修改。如果出現(xiàn)了錯(cuò)誤或者用戶執(zhí)行了ROLLBACK語句,系統(tǒng)可以利用Undo Log中的備份將數(shù)據(jù)恢復(fù)到事務(wù)開始之前的狀態(tài)。
- Undo原理:(備份舊數(shù)據(jù))
- Redo原理:(保存最新數(shù)據(jù))