面試官:數(shù)據(jù)庫事務(wù)的ACID靠什么來保證?
大家好,歡迎來到Tlog4J課堂,我是Jensen。
面試官:數(shù)據(jù)庫事務(wù)的四大特性是什么?
候選人:ACID,分別指原子性、一致性、隔離性、持久性(得意~)
面試官:那在MySQL的InnoDB中,ACID是怎么保證的呢?
候選人:啊這……
ACID大家耳熟能詳,ACID是指數(shù)據(jù)庫事務(wù)中的基本特性:原子性、一致性、隔離性、持久性,那么這四種特性在MySql中是怎么保證的呢?或者說,在InnoDB存儲引擎中,ACID是怎么實現(xiàn)的呢?這個在學(xué)校里的老師可沒教……
那今天咱們一起來看看,MySQL為了達(dá)成這四種特性做了一些什么事情。
首先,要回答這個問題得了解清楚MySQL的日志體系,MySQL在InnoDB存儲引擎級別有兩種日志:undo log日志和redo log日志,那在MySQL Server級別又有一個binlog日志,咱們結(jié)合這幾個日志來說明ACID特性。
Atomicity原子性保障
事務(wù)的原子性指一個事務(wù)(transaction)中的所有操作,要么全部完成,要么全部不完成,不會結(jié)束在中間某個環(huán)節(jié)。事務(wù)在執(zhí)行過程中發(fā)生錯誤,會被回滾(Rollback)到事務(wù)開始前的狀態(tài),就像這個事務(wù)從來沒有執(zhí)行過一樣。
原子性是由undo log日志保證的,它記錄了需要回滾的日志信息,也就是說我們的事務(wù)還沒提交需要回滾,那么事務(wù)回滾就是根據(jù)undo log日志來撤銷已經(jīng)執(zhí)行成功的SQL。
說白了,undo log其實就是SQL的反向執(zhí)行,它記錄了反向執(zhí)行的SQL語句,把正向語句回滾回去。
Consistency一致性保障
事務(wù)的一致性指的是在一個事務(wù)執(zhí)行之前和執(zhí)行之后數(shù)據(jù)庫都必須處于一致性狀態(tài)。
如果事務(wù)成功地完成,那么系統(tǒng)中所有變化將正確地應(yīng)用,系統(tǒng)處于有效狀態(tài);如果在事務(wù)中出現(xiàn)錯誤,那么系統(tǒng)中的所有變化將自動地回滾,系統(tǒng)返回到原始狀態(tài)。
一致性是ACID的目的,也就是說,只需要保證原子性、隔離性、持久性,自然也就保證了數(shù)據(jù)的一致性。
比如說,我們的ID在數(shù)據(jù)庫中是唯一的,此時插入了一個唯一ID,數(shù)據(jù)庫會給我們做一個檢查,告訴咱們是否發(fā)生了主鍵沖突,如果主鍵沖突數(shù)據(jù)就無法插入。
另一部分是業(yè)務(wù)數(shù)據(jù)的一致性,這需要程序代碼來保證。
比如說轉(zhuǎn)賬這個場景,假設(shè)我要轉(zhuǎn)賬100元出去,實際上數(shù)據(jù)庫中只有90元,那這時候就不應(yīng)該轉(zhuǎn)賬成功,這種情況通過數(shù)據(jù)庫是無法保證的,只能由程序來保證。
Isolation隔離性保障
事務(wù)的隔離性指的是在并發(fā)環(huán)境中,當(dāng)不同的事務(wù)同時操縱相同的數(shù)據(jù)時,每個事務(wù)都有各自的完整數(shù)據(jù)空間,由并發(fā)事務(wù)所做的修改必須與任何其他并發(fā)事務(wù)所做的修改隔離。
事務(wù)查看數(shù)據(jù)更新時,數(shù)據(jù)所處的狀態(tài)要么是另一事務(wù)修改它之前的狀態(tài),要么是另一事務(wù)修改它之后的狀態(tài),事務(wù)不會查看到中間狀態(tài)的數(shù)據(jù)。
在MySQL中隔離性是通過MVCC多版本并發(fā)控制機(jī)制來保證的,它是在事務(wù)隔離級別中最最重要的一個概念,那它是怎么實現(xiàn)的呢?
多版本并發(fā)控制:讀取數(shù)據(jù)時通過一種類似快照的方式將數(shù)據(jù)保存下來,這樣讀鎖和寫鎖就不沖突了,不同事務(wù)的session會看到自己特定版本的數(shù)據(jù),也就是版本鏈,通過版本鏈的概念來達(dá)到讀和寫能夠并發(fā)進(jìn)行。
MVCC只在READ COMMITTED(已提交讀)和REPEATABLE READ(可重復(fù)讀)兩個隔離級別下工作,其他兩個隔離級別和MVCC不兼容,這是因為READ UNCOMMITTED(讀未提交)總是讀取最新的數(shù)據(jù)行,而不是符合當(dāng)前事務(wù)版本的數(shù)據(jù)行,而ZERIALIZABLE(串行化)則會對所有讀取的行都加鎖,MVCC就沒有意義了。
在MySQL的InnoDB下,聚簇索引記錄中有兩個必要的隱藏列:
- trx_id:它用來存儲每次對某條聚簇索引記錄進(jìn)行修改時的事務(wù)ID,這個事務(wù)ID由MySQL分配。
- roll_pointer:每次對哪條聚簇索引記錄有修改的時候,都會把老版本寫入undo日志中,這個roll_pointer就是存了一個指針,它指向這條聚簇索引記錄的上一個版本的位置,通過它來獲得上一個版本的記錄信息(注意插入操作的undo日志沒有這個屬性,因為它沒有老版本)。
OK,理解了這些概念,咱們再來看看MVCC。
已提交讀和可重復(fù)讀的區(qū)別就在于它們生成ReadView的策略不同。
MVCC就是版本鏈+ReadView所組成的這么一種概念,當(dāng)我們掌握了版本鏈和ReadView這兩個概念,也就明白了MVCC,我們接著來看看這個ReadView。
我們在開啟事務(wù)時創(chuàng)建ReadView,ReadView維護(hù)了當(dāng)前活動的事務(wù)ID,即未提交的正在進(jìn)行中的事務(wù)ID,排序生成一個數(shù)組訪問數(shù)據(jù),獲取需要修改的記錄中的事務(wù)ID(獲取的是事務(wù)ID最大的記錄),然后去對比ReadView:
- 如果獲取的事務(wù)ID在ReadView的左邊(比ReadView都小),表示可以訪問(在左邊意味著該事務(wù)已經(jīng)提交)。
- 如果獲取的事務(wù)ID在ReadView的右邊(比ReadView都大),或者就在ReadView中,表示不可以訪問,獲取roll_pointer,取上一版本重新對比(在右邊意味著,該事務(wù)在ReadView生成之后出現(xiàn),在ReadView中意味著該事務(wù)還未提交)。
已提交讀隔離級別下的事務(wù)在每次查詢的開始都會生成一個獨立的ReadView,也就是說我每次select查出來的ReadView都會重新生成,所以ReadView可能會不一樣,就是說讀到的數(shù)據(jù)就會不一樣;
而可重復(fù)讀隔離級別則在第一次讀的時候生成一個ReadView,之后的讀都復(fù)用之前的ReadView,每次select查詢都是一樣的。
在這里咱們發(fā)現(xiàn)了這兩者的性能是有差別的,MySQL為了提高查詢性能,默認(rèn)使用了可重復(fù)讀這種隔離級別(原因之一)。
這就是MySQL的MVCC,通過版本鏈,實現(xiàn)多版本可并發(fā)讀-寫、寫-讀,通過ReadView生成策略的不同實現(xiàn)不同的隔離級別。
Durability持久性保障
事務(wù)的持久性指的是只要事務(wù)成功結(jié)束,它對數(shù)據(jù)庫所做的更新就必須永久保存下來,即使發(fā)生系統(tǒng)崩潰,重新啟動數(shù)據(jù)庫系統(tǒng)后,數(shù)據(jù)庫還能恢復(fù)到事務(wù)成功結(jié)束時的狀態(tài)。
持久性意味著事務(wù)操作最終要持久化到數(shù)據(jù)庫中,持久性是由 內(nèi)存+redo log來保證的,MySQL的InnoDB在修改數(shù)據(jù)的時候,同時在內(nèi)存和redo log記錄這次操作,宕機(jī)的時候可以從redo log中恢復(fù)數(shù)據(jù)。
同時,我們都知道MySQL Server的主從同步就是通過binlog來實現(xiàn)的,從服務(wù)器通過binlog文件的SQL拿過去執(zhí)行一遍,保證跟主服務(wù)器的數(shù)據(jù)一致,而binlog和redo log都存儲了表中的數(shù)據(jù),都可以用來做數(shù)據(jù)恢復(fù)的,那怎么保證binlog和redo log的數(shù)據(jù)一致呢?
下面是InnoDB下redo log的過程:
- 對redo log進(jìn)行寫盤,寫完后事務(wù)進(jìn)入prepare狀態(tài)。
- 如果前面prepare成功,馬上就會進(jìn)行binlog寫盤,再繼續(xù)將事務(wù)日志持久化到binlog。
- 如果binlog持久化成功,那么事務(wù)則進(jìn)入commit狀態(tài)(在redo log里面寫一條commit記錄)。
這意味著一個事務(wù)到底有沒有成功,由兩方面來保證:第一是redo log里面有沒有commit記錄,如果有commit記錄,那么binlog一定是持久化成功了,也就是說事務(wù)成功了。
再者就是redo log最終還會進(jìn)行刷盤,它的刷盤會在系統(tǒng)空閑時進(jìn)行,并不是寫到redo log時馬上進(jìn)行刷盤。
以上就是數(shù)據(jù)庫的基本特性ACID在MySQL中如何進(jìn)行保證的方法,ACID就是這樣通過InnoDB的幾個日志和MVCC來保證原子性、一致性、隔離性、持久化的。
最后,再問大家一個問題:MySQL是先設(shè)計ACID特性才有的底層實現(xiàn),還是先實現(xiàn)了底層才總結(jié)出ACID特性的呢?