照例,我們先來一個場景~
面試官:"知道事務的四大特性麼?" 你:"懂,ACID嘛,原子性(Atomicity)、一緻性(Consistency)、隔離性(Isolation)、持久性(Durability)!" 面試官:"你們是用mysql資料庫吧,能簡單說說innodb中怎麼實作這四大特性的麼?“ 你:"我隻知道隔離性是怎麼做的balabala~~" 面試官:"還是回去等通知吧~"
OK,回到正題。說到事務的四大特性原子性(<code>Atomicity</code>)、一緻性(<code>Consistency</code>)、隔離性(<code>Isolation</code>)、持久性(<code>Durability</code>),懂的人很多。但是稍微涉及細節一點,這四大特性在資料庫中的實作原理是怎麼樣的?那就沒有幾個人能夠答得上來了。是以,我們這篇文章着重讨論一下四大特性在Mysql中的實作原理。
我們以從A賬戶轉賬50元到B賬戶為例進行說明一下ACID,四大特性。
根據定義,原子性是指一個事務是一個不可分割的工作機關,其中的操作要麼都做,要麼都不做。即要麼轉賬成功,要麼轉賬失敗,是不存在中間的狀态!
如果無法保證原子性會怎麼樣?
OK,就會出現資料不一緻的情形,A賬戶減去50元,而B賬戶增加50元操作失敗。系統将無故丢失50元~
根據定義,隔離性是指多個事務并發執行的時候,事務内部的操作與其他事務是隔離的,并發執行的各個事務之間不能互相幹擾。
如果無法保證隔離性會怎麼樣?
OK,假設A賬戶有200元,B賬戶0元。A賬戶往B賬戶轉賬兩次,金額為50元,分别在兩個事務中執行。如果無法保證隔離性,會出現下面的情形

如圖所示,如果不保證隔離性,A扣款兩次,而B隻加款一次,憑空消失了50元,依然出現了資料不一緻的情形!
<code>ps</code>:可能有細心的讀者已經發現了,mysql中是依靠鎖來解決隔離性問題。嗯,我們後面來說明。
根據定義,持久性是指事務一旦送出,它對資料庫的改變就應該是永久性的。接下來的其他操作或故障不應該對其有任何影響。
如果無法保證持久性會怎麼樣?
在Mysql中,為了解決CPU和磁盤速度不一緻問題,Mysql是将磁盤上的資料加載到記憶體,對記憶體進行操作,然後再回寫磁盤。好,假設此時當機了,在記憶體中修改的資料全部丢失了,持久性就無法保證。
設想一下,系統提示你轉賬成功。但是你發現金額沒有發生任何改變,此時資料出現了不合法的資料狀态,我們将這種狀态認為是資料不一緻的情形。
根據定義,一緻性是指事務執行前後,資料處于一種合法的狀态,這種狀态是語義上的而不是文法上的。
那什麼是合法的資料狀态呢?
oK,這個狀态是滿足預定的限制就叫做合法的狀态,再通俗一點,這狀态是由你自己來定義的。滿足這個狀态,資料就是一緻的,不滿足這個狀态,資料就是不一緻的!
如果無法保證一緻性會怎麼樣?
例一:A賬戶有200元,轉賬300元出去,此時A賬戶餘額為-100元。你自然就發現了此時資料是不一緻的,為什麼呢?因為你定義了一個狀态,餘額這列必須大于0。
例二:A賬戶200元,轉賬50元給B賬戶,A賬戶的錢扣了,但是B賬戶因為各種意外,餘額并沒有增加。你也知道此時資料是不一緻的,為什麼呢?因為你定義了一個狀态,要求A+B的餘額必須不變。
問題一:Mysql怎麼保證一緻性的?
OK,這個問題分為兩個層面來說。
從資料庫層面,資料庫通過原子性、隔離性、持久性來保證一緻性。也就是說ACID四大特性之中,C(一緻性)是目的,A(原子性)、I(隔離性)、D(持久性)是手段,是為了保證一緻性,資料庫提供的手段。資料庫必須要實作AID三大特性,才有可能實作一緻性。例如,原子性無法保證,顯然一緻性也無法保證。
但是,如果你在事務裡故意寫出違反限制的代碼,一緻性還是無法保證的。例如,你在轉賬的例子中,你的代碼裡故意不給B賬戶加錢,那一緻性還是無法保證。是以,還必須從應用層角度考慮。
從應用層面,通過代碼判斷資料庫資料是否有效,然後決定復原還是送出資料!
問題二: Mysql怎麼保證原子性的?
OK,是利用Innodb的<code>undo log</code>。
<code>undo log</code>名為復原日志,是實作原子性的關鍵,當事務復原時能夠撤銷所有已經成功執行的sql語句,他需要記錄你要復原的相應日志資訊。
例如
(1)當你delete一條資料的時候,就需要記錄這條資料的資訊,復原的時候,insert這條舊資料
(2)當你update一條資料的時候,就需要記錄之前的舊值,復原的時候,根據舊值執行update操作
(3)當年insert一條資料的時候,就需要這條記錄的主鍵,復原的時候,根據主鍵執行delete操作
<code>undo log</code>記錄了這些復原需要的資訊,當事務執行失敗或調用了rollback,導緻事務需要復原,便可以利用undo log中的資訊将資料復原到修改之前的樣子。
<code>ps</code>:具體的undo log日志長啥樣,這個可以寫一篇文章了。而且寫出來,看的人也不多,姑且先這麼簡單的了解吧。
問題三: Mysql怎麼保證持久性的?
OK,是利用Innodb的<code>redo log</code>。
正如之前說的,Mysql是先把磁盤上的資料加載到記憶體中,在記憶體中對資料進行修改,再刷回磁盤上。如果此時突然當機,記憶體中的資料就會丢失。
怎麼解決這個問題?
簡單啊,事務送出前直接把資料寫入磁盤就行啊。
這麼做有什麼問題?
隻修改一個頁面裡的一個位元組,就要将整個頁面刷入磁盤,太浪費資源了。畢竟一個頁面16kb大小,你隻改其中一點點東西,就要将16kb的内容刷入磁盤,聽着也不合理。
畢竟一個事務裡的SQL可能牽涉到多個資料頁的修改,而這些資料頁可能不是相鄰的,也就是屬于随機IO。顯然操作随機IO,速度會比較慢。
于是,決定采用<code>redo log</code>解決上面的問題。當做資料修改的時候,不僅在記憶體中操作,還會在<code>redo log</code>中記錄這次操作。當事務送出的時候,會将<code>redo log</code>日志進行刷盤(<code>redo log</code>一部分在記憶體中,一部分在磁盤上)。當資料庫當機重新開機的時候,會将<code>redo log</code>中的内容恢複到資料庫中,再根據<code>undo log</code>和<code>binlog</code>内容決定復原資料還是送出資料。
采用redo log的好處?
其實好處就是将<code>redo log</code>進行刷盤比對資料頁刷盤效率高,具體表現如下
<code>redo log</code>體積小,畢竟隻記錄了哪一頁修改了啥,是以體積小,刷盤快。
<code>redo log</code>是一直往末尾進行追加,屬于順序IO。效率顯然比随機IO來的快。
<code>ps</code>:不想具體去談<code>redo log</code>具體長什麼樣,因為内容太多了。
問題四: Mysql怎麼保證隔離性的?
OK,利用的是鎖和MVCC機制。還是拿轉賬例子來說明,有一個賬戶表如下
表名<code>t_balance</code>
id
user_id
balance
1
A
200
2
B
其中id是主鍵,user_id為賬戶名,balance為餘額。還是以轉賬兩次為例,如下圖所示
至于MVCC,即多版本并發控制(Multi Version Concurrency Control),一個行記錄資料有多個版本對快照資料,這些快照資料在<code>undo log</code>中。
如果一個事務讀取的行正在做DELELE或者UPDATE操作,讀取操作不會等行上的鎖釋放,而是讀取該行的快照版本。
由于MVCC機制在可重複讀(Repeateable Read)和讀已送出(Read Commited)的MVCC表現形式不同,就不贅述了。
但是有一點說明一下,在事務隔離級别為讀已送出(Read Commited)時,一個事務能夠讀到另一個事務已經送出的資料,是不滿足隔離性的。但是當事務隔離級别為可重複讀(Repeateable Read)中,是滿足隔離性的。
本文講了Mysql中事務ACID四大特性的實作原理,希望大家有所收獲。
作者:孤獨煙
出處: http://rjzheng.cnblogs.com/
本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。如果覺得還有幫助的話,可以點一下右下角的【推薦】。