mysql事務的實現原理

此篇文章算是對mysql事務的一個總結,基本把mysql事務相關的知識點都涵蓋到了,面試問來問去無非也就是這些,在瞭解這些以前咱們先對mysql在執行的過程當中 有一個總體的認識,以下圖 mysql

如上圖所示,MySQL服務器邏輯架構從上往下能夠分爲三層:面試

(1)第一層:處理客戶端鏈接、受權認證等。sql

(2)第二層:服務器層,負責查詢語句的解析、優化、緩存以及內置函數的實現、存儲過程等。數據庫

(3)第三層:存儲引擎,負責MySQL中數據的存儲和提取。MySQL中服務器層無論理事務,事務是由存儲引擎實現的。MySQL支持事務的存儲引擎有InnoDB、NDB Cluster等,其中InnoDB的使用最爲普遍;其餘存儲引擎不支持事務,如MyIsam、Memory等。緩存

具體過程都在圖中有所標註,大概看看有個認識就能夠了。接下來我們逐一總結服務器

典型的MySQL事務是以下操做的:

start transaction;
……  #一條或多條sql語句
commit;

其中start transaction標識事務開始,commit提交事務,將執行結果寫入到數據庫。若是sql語句執行出現問題,會調用rollback,回滾全部已經執行成功的sql語句。固然,也能夠在事務中直接使用rollback語句進行回滾。架構

自動提交

MySQL中默認採用的是自動提交(autocommit)模式,以下所示:併發

mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit    | ON    |
+---------------+-------+
1 row in set (0.00 sec)

在自動提交模式下,若是沒有start transaction顯式地開始一個事務,那麼每一個sql語句都會被當作一個事務執行提交操做。函數

經過以下方式,能夠關閉autocommit;須要注意的是,autocommit參數是針對鏈接的,在一個鏈接中修改了參數,不會對其餘鏈接產生影響。性能

mysql> set autocommit =0;
Query OK, 0 rows affected (0.00 sec)

mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit    | OFF   |
+---------------+-------+
1 row in set (0.00 sec)

若是關閉了autocommit,則全部的sql語句都在一個事務中,直到執行了commit或rollback,該事務結束,同時開始了另一個事務。

特殊操做

在MySQL中,存在一些特殊的命令,若是在事務中執行了這些命令,會立刻強制執行commit提交事務;如DDL語句(create table/drop table/alter/table)、lock tables語句等等。

不過,經常使用的select、insert、update和delete命令,都不會強制提交事務。

事務的特色:ACID

原子性(Atomicity)

定義

原子性是指一個事務是一個不可分割的工做單位,其中的操做要麼都作,要麼都不作;若是事務中一個sql語句執行失敗,則已執行的語句也必須回滾,數據庫退回到事務前的狀態。 簡單來講,就是

實現原理 在說明原子性原理以前,首先介紹一下MySQL的事務日誌。MySQL的日誌有不少種,如二進制日誌、錯誤日誌、查詢日誌、慢查詢日誌等,此外InnoDB存儲引擎還提供了兩種事務日誌:redo log(重作日誌)和undo log(回滾日誌)。其中redo log用於保證事務持久性;undo log則是事務原子性和隔離性實現的基礎。

下面說回undo log。實現原子性的關鍵,是當事務回滾時可以撤銷全部已經成功執行的sql語句。InnoDB實現回滾,靠的是undo log:當事務對數據庫進行修改時,InnoDB會生成對應的undo log;若是事務執行失敗或調用了rollback,致使事務須要回滾,即可以利用undo log中的信息將數據回滾到修改以前的樣子。

undo log屬於邏輯日誌,它記錄的是sql執行相關的信息。當發生回滾時,InnoDB會根據undo log的內容作與以前相反的工做:對於每一個insert,回滾時會執行delete;對於每一個delete,回滾時會執行insert;對於每一個update,回滾時會執行一個相反的update,把數據改回去。

以update操做爲例:當事務執行update時,其生成的undo log中會包含被修改行的主鍵(以便知道修改了哪些行)、修改了哪些列、這些列在修改先後的值等信息,回滾時即可以使用這些信息將數據還原到update以前的狀態。

從上圖能夠了解到數據的變動都伴隨着回滾日誌的產生:

(1) 產生了被修改前數據(zhangsan,1000) 的回滾日誌

(2) 產生了被修改前數據(zhangsan,0) 的回滾日誌

根據上面流程能夠得出以下結論:

  1. 每條數據變動(insert/update/delete)操做都伴隨一條undo log的生成,而且回滾日誌必須先於數據持久化到磁盤上

  2. 所謂的回滾就是根據回滾日誌作逆向操做,好比delete的逆向操做爲insert,insert的逆向操做爲delete,update的逆向爲update等。 回滾過程如圖

tips:undo log也能夠這麼理解

當delete一條記錄時,undo log中會記錄一條對應的insert記錄 
當insert一條記錄時,undo log中會記錄一條對應的delete記錄
當update一條記錄時,它記錄一條對應相反的update記錄

tips:邏輯日誌和物理日誌的區別 看記日誌的時候 是針對一行記錄,就是邏輯日誌 若是是一個數據頁,就是物理日誌

持久性(Durability)

定義

事務一旦提交,其所作的修改會永久保存到數據庫中,此時即便系統崩潰修改的數據也不會丟失。

實現原理:Redo log(WAL write ahead log)

先了解一下MySQL的數據存儲機制,MySQL的表數據是存放在磁盤上的,所以想要存取的時候都要經歷磁盤IO,然而即便是使用SSD磁盤IO也是很是消耗性能的。

爲此,爲了提高性能InnoDB提供了緩衝池(Buffer Pool),Buffer Pool中包含了磁盤數據頁的映射,能夠當作緩存來使用: 讀數據:會首先從緩衝池中讀取,若是緩衝池中沒有,則從磁盤讀取再放入緩衝池;

寫數據:會首先寫入緩衝池,緩衝池中的數據會按期同步到磁盤中(這一過程稱爲刷髒);

上面這種緩衝池的措施雖然在性能方面帶來了質的飛躍,可是它也帶來了新的問題,當MySQL系統宕機,斷電的時候可能會丟數據!!!

由於咱們的數據已經提交了,但此時是在緩衝池裏頭,還沒來得及在磁盤持久化,因此咱們急需一種機制須要存一下已提交事務的數據,爲恢復數據使用。

因而 redo log就派上用場了。下面看下redo log是何時產生的

既然redo log也須要存儲,也涉及磁盤IO爲啥還用它?

(1)刷髒是隨機IO,由於每次修改的數據位置隨機,但寫redo log是追加操做,屬於順序IO。

(2)刷髒是以數據頁(Page)爲單位的,MySQL默認頁大小是16KB,一個Page上一個小修改都要整頁寫入;而redo log中只包含真正須要寫入的部分,無效IO大大減小。

redo log與binlog

咱們知道,在MySQL中還存在binlog(二進制日誌)也能夠記錄寫操做並用於數據的恢復,但兩者是有着根本的不一樣的:

(1)做用不一樣:redo log是用於crash recovery的,保證MySQL宕機也不會影響持久性;binlog是用於point-in-time recovery的,保證服務器能夠基於時間點恢復數據,此外binlog還用於主從複製。

(2)層次不一樣:redo log是InnoDB存儲引擎實現的,而binlog是MySQL的服務器層(能夠參考文章前面對MySQL邏輯架構的介紹)實現的,同時支持InnoDB和其餘存儲引擎。

(3)內容不一樣:redo log是物理日誌,內容基於磁盤的Page;binlog的內容是二進制的,根據binlog_format參數的不一樣,可能基於sql語句、基於數據自己或者兩者的混合。

(4)寫入時機不一樣:binlog在事務提交時寫入;redo log的寫入時機相對多元:

前面曾提到:當事務提交時會調用fsync對redo log進行刷盤;這是默認狀況下的策略,修改innodb_flush_log_at_trx_commit參數能夠改變該策略,但事務的持久性將沒法保證。 除了事務提交時,還有其餘刷盤時機:如master thread每秒刷盤一次redo log等,這樣的好處是不必定要等到commit時刷盤,commit速度大大加快。

隔離性(Isolation)

定義

與原子性、持久性側重於研究事務自己不一樣,隔離性研究的是不一樣事務之間的相互影響。隔離性是指,事務內部的操做與其餘事務是隔離的,併發執行的各個事務之間不能互相干擾。嚴格的隔離性,對應了事務隔離級別中的Serializable (可串行化),但實際應用中出於性能方面的考慮不多會使用可串行化。

實現原理

隔離性追求的是併發情形下事務之間互不干擾。簡單起見,咱們僅考慮最簡單的讀操做和寫操做(暫時不考慮帶鎖讀等特殊操做),那麼隔離性的探討,主要能夠分爲兩個方面:

(一個事務)寫操做對(另外一個事務)寫操做的影響:鎖機制保證隔離性 (一個事務)寫操做對(另外一個事務)讀操做的影響:MVCC保證隔離性

髒讀、不可重複讀和幻讀

首先來看併發狀況下,讀操做可能存在的三類問題:

  • 髒讀:當前事務(A)中能夠讀到其餘事務(B)未提交的數據(髒數據),這種現象是髒讀。舉例以下(以帳戶餘額表爲例)

  • 不可重複讀:在事務A中前後兩次讀取同一個數據,兩次讀取的結果不同,這種現象稱爲不可重複讀。髒讀與不可重複讀的區別在於:前者讀到的是其餘事務未提交的數據,後者讀到的是其餘事務已提交的數據。舉例以下:

  • 幻讀:在事務A中按照某個條件前後兩次查詢數據庫,兩次查詢結果的條數不一樣,這種現象稱爲幻讀。不可重複讀與幻讀的區別能夠通俗的理解爲:前者是數據變了,後者是數據的行數變了。舉例以下

事務隔離級別

在實際應用中,讀未提交在併發時會致使不少問題,而性能相對於其餘隔離級別提升卻頗有限,所以使用較少。可串行化強制事務串行,併發效率很低,只有當對數據一致性要求極高且能夠接受沒有併發時使用,所以使用也較少。所以在大多數數據庫系統中,默認的隔離級別是讀已提交(如Oracle)或可重複讀(後文簡稱RR)。 能夠經過以下兩個命令分別查看隔離級別:

select @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set (0.00 sec)

MVCC

RR解決髒讀、不可重複讀、幻讀等問題,使用的是MVCC:MVCC全稱Multi-Version Concurrency Control,即多版本的併發控制協議。下面的例子很好的體現了MVCC的特色:在同一時刻,不一樣的事務讀取到的數據多是不一樣的(即多版本)——在T5時刻,事務A和事務C能夠讀取到不一樣版本的數據。

MVCC最大的優勢是讀不加鎖,所以讀寫不衝突,併發性能好。InnoDB實現MVCC,多個版本的數據能夠共存,主要是依靠數據的隱藏列(也能夠稱之爲標記位)和undo log。其中數據的隱藏列包括了該行數據的版本號、刪除時間、指向undo log的指針等等;當讀取數據時,MySQL能夠經過隱藏列判斷是否須要回滾並找到回滾須要的undo log,從而實現MVCC;隱藏列的詳細格式再也不展開。

下面結合前文提到的幾個問題分別說明 髒讀

當事務A在T3時間節點讀取zhangsan的餘額時,會發現數據已被其餘事務修改,且狀態爲未提交。此時事務A讀取最新數據後,根據數據的undo log執行回滾操做,獲得事務B修改前的數據,從而避免了髒讀。

不可重複讀

當事務A在T2節點第一次讀取數據時,會記錄該數據的版本號(數據的版本號是以row爲單位記錄的),假設版本號爲1;當事務B提交時,該行記錄的版本號增長,假設版本號爲2;當事務A在T5再一次讀取數據時,發現數據的版本號(2)大於第一次讀取時記錄的版本號(1),所以會根據undo log執行回滾操做,獲得版本號爲1時的數據,從而實現了可重複讀。

幻讀

InnoDB實現的RR經過next-key lock機制避免了幻讀現象。

next-key lock是行鎖的一種,實現至關於record lock(記錄鎖) + gap lock(間隙鎖);其特色是不只會鎖住記錄自己(record lock的功能),還會鎖定一個範圍(gap lock的功能)。固然,這裏咱們討論的是不加鎖讀:此時的next-key lock並非真的加鎖,只是爲讀取的數據增長了標記(標記內容包括數據的版本號等);準確起見姑且稱之爲類next-key lock機制。仍是之前面的例子來講明:

當事務A在T2節點第一次讀取0<id<5數據時,標記的不僅是id=1的數據,而是將範圍(0,5)進行了標記,這樣當T5時刻再次讀取0<id<5數據時,即可以發現id=2的數據比以前標記的版本號更高,此時再結合undo log執行回滾操做,避免了幻讀。

總結

歸納來講,InnoDB實現的RR,經過鎖機制、數據的隱藏列、undo log和類next-key lock,實現了必定程度的隔離性,能夠知足大多數場景的須要。不過須要說明的是,RR雖然避免了幻讀問題,可是畢竟不是Serializable,不能保證徹底的隔離,下面是一個例子,你們能夠本身驗證一下。

一致性

基本概念

一致性是指事務執行結束後,數據庫的完整性約束沒有被破壞,事務執行的先後都是合法的數據狀態。數據庫的完整性約束包括但不限於:實體完整性(如行的主鍵存在且惟一)、列完整性(如字段的類型、大小、長度要符合要求)、外鍵約束、用戶自定義完整性(如轉帳先後,兩個帳戶餘額的和應該不變)。

實現

能夠說,一致性是事務追求的最終目標:前面提到的原子性、持久性和隔離性,都是爲了保證數據庫狀態的一致性。此外,除了數據庫層面的保障,一致性的實現也須要應用層面進行保障。

實現一致性的措施包括:

  • 保證原子性、持久性和隔離性,若是這些特性沒法保證,事務的一致性也沒法保證

  • 數據庫自己提供保障,例如不容許向整形列插入字符串值、字符串長度不能超過列的限制等

  • 應用層面進行保障,例如若是轉帳操做只扣除轉帳者的餘額,而沒有增長接收者的餘額,不管數據庫實現的多麼完美,也沒法保證狀態的一致

相關文章
相關標籤/搜索