相信你們都用過事務以及瞭解他的特色,如原子性(Atomicity),一致性(Consistency),隔離型(Isolation)以及持久性(Durability)等。今天想跟你們一塊兒研究下事務內部究竟是怎麼實現的,在講解前我想先拋出個問題:mysql
事務想要作到什麼效果?sql
按我理解,無非是要作到可靠性以及併發處理數據庫
可靠性:數據庫要保證當insert或update操做時拋異常或者數據庫crash的時候須要保障數據的操做先後的一致,想要作到這個,我須要知道我修改以前和修改以後的狀態,因此就有了undo log和redo log。緩存
併發處理:也就是說當多個併發請求過來,而且其中有一個請求是對數據修改操做的時候會有影響,爲了不讀到髒數據,因此須要對事務之間的讀寫進行隔離,至於隔離到啥程度得看業務系統的場景了,實現這個就得用MySQL 的隔離級別。併發
下面我首先講實現事務功能的三個技術,分別是日誌文件(redo log 和 undo log),鎖技術以及MVCC,而後再講事務的實現原理,包括原子性是怎麼實現的,隔離型是怎麼實現的等等。最後在作一個總結,但願你們可以耐心看完ide
redo log與undo log介紹性能
mysql鎖技術以及MVCC基礎優化
事務的實現原理spa
總結線程
什麼是redo log ?
redo log叫作重作日誌,是用來實現事務的持久性。該日誌文件由兩部分組成:重作日誌緩衝(redo log buffer)以及重作日誌文件(redo log),前者是在內存中,後者在磁盤中。當事務提交以後會把全部修改信息都會存到該日誌中。假設有個表叫作tb1(id,username) 如今要插入數據(3,ceshi)
start transaction;
select balance from bank where name="zhangsan";
// 生成 重作日誌 balance=600
update bank set balance = balance - 400;
// 生成 重作日誌 amount=400
update finance set amount = amount + 400;
commit;
redo log 有什麼做用?
mysql 爲了提高性能不會把每次的修改都實時同步到磁盤,而是會先存到Boffer Pool(緩衝池)裏頭,把這個看成緩存來用。而後使用後臺線程去作緩衝池和磁盤之間的同步。
那麼問題來了,若是還沒來的同步的時候宕機或斷電了怎麼辦?還沒來得及執行上面圖中紅色的操做。這樣會致使丟部分已提交事務的修改信息!
因此引入了redo log來記錄已成功提交事務的修改信息,而且會把redo log持久化到磁盤,系統重啓以後在讀取redo log恢復最新數據。
總結:
redo log是用來恢復數據的 用於保障,已提交事務的持久化特性
什麼是 undo log ?
undo log 叫作回滾日誌,用於記錄數據被修改前的信息。他正好跟前面所說的重作日誌所記錄的相反,重作日誌記錄數據被修改後的信息。undo log主要記錄的是數據的邏輯變化,爲了在發生錯誤時回滾以前的操做,須要將以前的操做都記錄下來,而後在發生錯誤時才能夠回滾。
還用上面那兩張表
每次寫入數據或者修改數據以前都會把修改前的信息記錄到 undo log。
undo log 有什麼做用?
undo log 記錄事務修改以前版本的數據信息,所以假如因爲系統錯誤或者rollback操做而回滾的話能夠根據undo log的信息來進行回滾到沒被修改前的狀態。
總結:
undo log是用來回滾數據的用於保障 未提交事務的原子性
1. mysql鎖技術
當有多個請求來讀取表中的數據時能夠不採起任何操做,可是多個請求裏有讀請求,又有修改請求時必須有一種措施來進行併發控制。否則頗有可能會形成不一致。
讀寫鎖
解決上述問題很簡單,只需用兩種鎖的組合來對讀寫請求進行控制便可,這兩種鎖被稱爲:
共享鎖(shared lock),又叫作"讀鎖"
讀鎖是能夠共享的,或者說多個讀請求能夠共享一把鎖讀數據,不會形成阻塞。
排他鎖(exclusive lock),又叫作"寫鎖"
寫鎖會排斥其餘全部獲取鎖的請求,一直阻塞,直到寫入完成釋放鎖。
總結:
經過讀寫鎖,能夠作到讀讀能夠並行,可是不能作到寫讀,寫寫並行
事務的隔離性就是根據讀寫鎖來實現的!!!這個後面再說。
MVCC (MultiVersion Concurrency Control) 叫作多版本併發控制。
InnoDB的 MVCC ,是經過在每行記錄的後面保存兩個隱藏的列來實現的。這兩個列, 一個保存了行的建立時間,一個保存了行的過時時間, 固然存儲的並非實際的時間值,而是系統版本號。
以上片斷摘自《高性能Mysql》這本書對MVCC的定義。他的主要實現思想是經過數據多版原本作到讀寫分離。從而實現不加鎖讀進而作到讀寫並行。
MVCC在mysql中的實現依賴的是undo log與read view
undo log :undo log 中記錄某行數據的多個版本的數據。
read view :用來判斷當前版本數據的可見性
4、事務的實現
前面講的重作日誌,回滾日誌以及鎖技術就是實現事務的基礎。
事務的原子性是經過 undo log 來實現的
事務的持久性性是經過 redo log 來實現的
事務的隔離性是經過 (讀寫鎖+MVCC)來實現的
而事務的終極大 boss 一致性是經過原子性,持久性,隔離性來實現的!!!
原子性,持久性,隔離性折騰半天的目的也是爲了保障數據的一致性!
總之,ACID只是個概念,事務最終目的是要保障數據的可靠性,一致性。
什麼是原子性:
一個事務必須被視爲不可分割的最小工做單位,一個事務中的全部操做要麼所有成功提交,要麼所有失敗回滾,對於一個事務來講不可能只執行其中的部分操做,這就是事務的原子性。
上面這段話取自《高性能MySQL》這本書對原子性的定義,原子性能夠歸納爲就是要實現要麼所有失敗,要麼所有成功。
以上概念相信你們夥兒都瞭解,那麼數據庫是怎麼實現的呢?就是經過回滾操做。所謂回滾操做就是當發生錯誤異常或者顯式的執行rollback語句時須要把數據還原到原先的模樣,因此這時候就須要用到undo log來進行回滾,接下來看一下undo log在實現事務原子性時怎麼發揮做用的
假設有兩個表 bank和finance,表中原始數據如圖所示,當進行插入,刪除以及更新操做時生成的undo log以下面圖所示:
從上圖能夠了解到數據的變動都伴隨着回滾日誌的產生:
(1) 產生了被修改前數據(zhangsan,1000) 的回滾日誌
(2) 產生了被修改前數據(zhangsan,0) 的回滾日誌
根據上面流程能夠得出以下結論:
1.每條數據變動(insert/update/delete)操做都伴隨一條undo log的生成,而且回滾日誌必須先於數據持久化到磁盤上
2.所謂的回滾就是根據回滾日誌作逆向操做,好比delete的逆向操做爲insert,insert的逆向操做爲delete,update的逆向爲update等。
思考:爲何先寫日誌後寫數據庫? ---稍後作解釋
爲了作到同時成功或者失敗,當系統發生錯誤或者執行rollback操做時須要根據undo log 進行回滾
回滾操做就是要還原到原來的狀態,undo log記錄了數據被修改前的信息以及新增和被刪除的數據信息,根據undo log生成回滾語句,好比:
(1) 若是在回滾日誌裏有新增數據記錄,則生成刪除該條的語句
(2) 若是在回滾日誌裏有刪除數據記錄,則生成生成該條的語句
(3) 若是在回滾日誌裏有修改數據記錄,則生成修改到原先數據的語句
事務一旦提交,其所做作的修改會永久保存到數據庫中,此時即便系統崩潰修改的數據也不會丟失。
先了解一下MySQL的數據存儲機制,MySQL的表數據是存放在磁盤上的,所以想要存取的時候都要經歷磁盤IO,然而即便是使用SSD磁盤IO也是很是消耗性能的。爲此,爲了提高性能InnoDB提供了緩衝池(Buffer Pool),Buffer Pool中包含了磁盤數據頁的映射,能夠當作緩存來使用:
讀數據:會首先從緩衝池中讀取,若是緩衝池中沒有,則從磁盤讀取在放入緩衝池;
寫數據:會首先寫入緩衝池,緩衝池中的數據會按期同步到磁盤中;
上面這種緩衝池的措施雖然在性能方面帶來了質的飛躍,可是它也帶來了新的問題,當MySQL系統宕機,斷電的時候可能會丟數據!!!
由於咱們的數據已經提交了,但此時是在緩衝池裏頭,還沒來得及在磁盤持久化,因此咱們急需一種機制須要存一下已提交事務的數據,爲恢復數據使用。
因而 redo log就派上用場了。下面看下redo log是何時產生的
既然redo log也須要存儲,也涉及磁盤IO爲啥還用它?
(1)redo log 的存儲是順序存儲,而緩存同步是隨機操做。
(2)緩存同步是以數據頁爲單位的,每次傳輸的數據大小大於redo log。
隔離性是事務ACID特性裏最複雜的一個。在SQL標準裏定義了四種隔離級別,每一種級別都規定一個事務中的修改,哪些是事務之間可見的,哪些是不可見的。
級別越低的隔離級別能夠執行越高的併發,但同時實現複雜度以及開銷也越大。
Mysql 隔離級別有如下四種(級別由低到高):
READ UNCOMMITED (未提交讀)
READ COMMITED (提交讀)
REPEATABLE READ (可重複讀)
SERIALIZABLE (可重複讀)
只要完全理解了隔離級別以及他的實現原理就至關於理解了ACID裏的隔離型。前面說過原子性,隔離性,持久性的目的都是爲了要作到一致性,但隔離型跟其餘兩個有所區別,原子性和持久性是爲了要實現數據的可性保障靠,好比要作到宕機後的恢復,以及錯誤後的回滾。
那麼隔離性是要作到什麼呢? 隔離性是要管理多個併發讀寫請求的訪問順序。 這種順序包括串行或者是並行
說明一點,寫請求不只僅是指insert操做,又包括update操做。
總之,從隔離性的實現能夠看出這是一場數據的可靠性與性能之間的權衡。
可靠性性高的,併發性能低(好比 Serializable)
可靠性低的,併發性能高(好比 Read Uncommited)
READ UNCOMMITTED
在READ UNCOMMITTED隔離級別下,事務中的修改即便還沒提交,對其餘事務是可見的。事務能夠讀取未提交的數據,形成髒讀。
由於讀不會加任何鎖,因此寫操做在讀的過程當中修改數據,因此會形成髒讀。好處是能夠提高併發處理性能,能作到讀寫並行。
換句話說,讀的操做不能排斥寫請求。
優勢:讀寫並行,性能高
缺點:形成髒讀
READ COMMITTED
一個事務的修改在他提交以前的全部修改,對其餘事務都是不可見的。其餘事務能讀到已提交的修改變化。在不少場景下這種邏輯是能夠接受的。
InnoDB在 READ COMMITTED,使用排它鎖,讀取數據不加鎖而是使用了MVCC機制。或者換句話說他採用了讀寫分離機制。
可是該級別會產生不可重讀以及幻讀問題。
什麼是不可重讀?
在一個事務內屢次讀取的結果不同。
爲何會產生不可重複讀?
這跟 READ COMMITTED 級別下的MVCC機制有關係,在該隔離級別下每次 select的時候新生成一個版本號,因此每次select的時候讀的不是一個副本而是不一樣的副本。
在每次select之間有其餘事務更新了咱們讀取的數據並提交了,那就出現了不可重複讀
REPEATABLE READ(Mysql默認隔離級別)
在一個事務內的屢次讀取的結果是同樣的。這種級別下能夠避免,髒讀,不可重複讀等查詢問題。mysql 有兩種機制能夠達到這種隔離級別的效果,分別是採用讀寫鎖以及MVCC。
採用讀寫鎖實現:
爲何能可重複度?只要沒釋放讀鎖,在次讀的時候仍是能夠讀到第一次讀的數據。
優勢:實現起來簡單
缺點:沒法作到讀寫並行
採用MVCC實現:
爲何能可重複度?由於屢次讀取只生成一個版本,讀到的天然是相同數據。
優勢:讀寫並行
缺點:實現的複雜度高
可是在該隔離級別下仍會存在幻讀的問題,關於幻讀的解決我打算另開一篇來介紹。
SERIALIZABLE
該隔離級別理解起來最簡單,實現也最單。在隔離級別下除了不會形成數據不一致問題,沒其餘優勢。
--摘自《高性能Mysql》
數據庫老是從一個一致性的狀態轉移到另外一個一致性的狀態.
下面舉個例子:zhangsan 從銀行卡轉400到理財帳戶
start transaction;
select balance from bank where name="zhangsan";
// 生成 重作日誌 balance=600
update bank set balance = balance - 400;
// 生成 重作日誌 amount=400
update finance set amount = amount + 400;
commit;
1.假如執行完 update bank set balance = balance - 400;之發生異常了,銀行卡的錢也不能平白無辜的減小,而是回滾到最初狀態。
2.又或者事務提交以後,緩衝池還沒同步到磁盤的時候宕機了,這也是不能接受的,應該在重啓的時候恢復並持久化。
3.假若有併發事務請求的時候也應該作好事務之間的可見性問題,避免形成髒讀,不可重複讀,幻讀等。在涉及併發的狀況下每每在性能和一致性之間作平衡,作必定的取捨,因此隔離性也是對一致性的一種破壞。
本文出發點是想講一下Mysql的事務的實現原理。
實現事務採起了哪些技術以及思想?
原子性:使用 undo log ,從而達到回滾
持久性:使用 redo log,從而達到故障後恢復
隔離性:使用鎖以及MVCC,運用的優化思想有讀寫分離,讀讀並行,讀寫並行
一致性:經過回滾,以及恢復,和在併發環境下的隔離作到一致性。