天天看點

mysql事務:ACID特性的實作原理

一基礎概念

事務(Transaction)是通路和更新資料庫的程式執行單元;
事務中可能包含一個或多個sql語句,這些語句要麼都執行,
要麼都不執行。作為一個關系型資料庫,MySQL支援事務,本文介紹基于MySQL5.6。
           

mysql事務的基礎知識

邏輯架構和存儲引擎

mysql事務:ACID特性的實作原理

如上圖所示,MySQL伺服器邏輯架構從上往下可以分為三層:

(1)第一層:處理用戶端連接配接、授權認證等。

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

(3)第三層:存儲引擎,負責MySQL中資料的存儲和提取。MySQL中伺服器層不管理事務,事務是由存儲引擎實作的。MySQL支援事務的存儲引擎有InnoDB、NDB Cluster等,其中InnoDB的使用最為廣泛;其他存儲引擎不支援事務,如MyIsam、Memory等。

如無特殊說明,後文中描述的内容都是基于InnoDB。

2、送出和復原

典型的MySQL事務是如下操作的:

tart 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.01 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指令,
都不會強制送出事務。
           

3、ACID特性

ACID是衡量事務的四個特性:
           
  • 原子性(Atomicity,或稱不可分割性)
  • 一緻性(Consistency)
  • 隔離性(Isolation)
  • 持久性(Durability)
    按照嚴格的标準,隻有同時滿足ACID特性才是事務;
      但是在各大資料庫廠商的實作中,真正滿足ACID的事務少之又少。
      例如MySQL的NDB Cluster事務不滿足持久性和隔離性;
      InnoDB預設事務隔離級别是可重複讀,不滿足隔離性;
      Oracle預設的事務隔離級别為READ COMMITTED,
      不滿足隔離性……是以與其說ACID是事務必須滿足的條件,
      不如說它們是衡量事務的四個次元。
               

二 、原子性

  1. 定義
    原子性是指一個事務是一個不可分割的工作機關,其中的操作要麼都做,
     要麼都不做;如果事務中一個sql語句執行失敗,
     則已執行的語句也必須復原,資料庫退回到事務前的狀态。
               
  2. 實作原理:undo log
    在說明原子性原理之前,首先介紹一下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. 定義
    持久性是指事務一旦送出,它對資料庫的改變就應該是永久性的。接下來
     的其他操作或故障不應該對其有任何影響。
               
  2. 實作原理:redo log
    redo log和undo log都屬于InnoDB的事務日志。下面先聊一下redo log存
     在的背景。
    
     InnoDB作為MySQL的存儲引擎,資料是存放在磁盤中的,但如果每次讀寫資料
     都需要磁盤IO,效率會很低。為此,InnoDB提供了緩存(Buffer Pool),Buffer 
     Pool中包含了磁盤中部分資料頁的映射,作為通路資料庫的緩沖:當從資料庫
     讀取資料時,會首先從Buffer Pool中讀取,如果Buffer Pool中沒有,則從磁盤讀
     取後放入Buffer Pool;當向資料庫寫入資料時,會首先寫入Buffer Pool,Buffer 
     Pool中修改的資料會定期重新整理到磁盤中(這一過程稱為刷髒)。
    
     Buffer Pool的使用大大提高了讀寫資料的效率,但是也帶了新的問題:如果
     MySQL當機,而此時Buffer Pool中修改的資料還沒有重新整理到磁盤,就會導緻數
     據的丢失,事務的持久性無法保證。
     
     于是,redo log被引入來解決這個問題:當資料修改時,除了修改Buffer Pool中
     的資料,還會在redo log記錄這次操作;當事務送出時,會調用fsync接口對redo
      log進行刷盤。如果MySQL當機,重新開機時可以讀取redo log中的資料,對資料庫
      進行恢複。redo log采用的是WAL(Write-ahead logging,預寫式日志),所有
      修改先寫入日志,再更新到Buffer Pool,保證了資料不會因MySQL當機而丢
      失,進而滿足了持久性要求。
     
     既然redo log也需要在事務送出時将日志寫入磁盤,為什麼它比直接将Buffer 
     Pool中修改的資料寫入磁盤(即刷髒)要快呢?主要有以下兩方面的原因:
     
     (1)刷髒是随機IO,因為每次修改的資料位置随機,但寫redo log是追加操
     作,屬于順序IO。
     
     (2)刷髒是以資料頁(Page)為機關的,MySQL預設頁大小是16KB,一個
     Page上一個小修改都要整頁寫入;而redo log中隻包含真正需要寫入的部分,無
     效IO大大減少。
               
  3. 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速度大大加快。
           

四、隔離性

  1. 定義
    與原子性、持久性側重于研究事務本身不同,隔離性研究的是不同僚務之間的
     互相影響。隔離性是指,事務内部的操作與其他事務是隔離的,并發執行的各
     個事務之間不能互相幹擾。嚴格的隔離性,對應了事務隔離級别中Serializable 
     (可串行化),但實際應用中出于性能方面的考慮很少會使用可串行化。
    
     隔離性追求的是并發情形下事務之間互不幹擾。簡單起見,我們僅考慮最簡單
     的讀操作和寫操作(暫時不考慮帶鎖讀等特殊操作),那麼隔離性的探讨,主要可
     以分為兩個方面:
     1. (一個事務)寫操作對(另一個事務)寫操作的影響:鎖機制保證隔離性
     2. (一個事務)寫操作對(另一個事務)讀操作的影響:MVCC保證隔離性
               
  2. 鎖機制
    首先來看兩個事務的寫操作之間的互相影響。隔離性要求同一時刻隻能有一個
     事務對資料進行寫操作,InnoDB通過鎖機制來保證這一點。
    
     鎖機制的基本原理可以概括為:事務在修改資料之前,需要先獲得相應的鎖;
     獲得鎖之後,事務便可以修改資料;該事務操作期間,這部分資料是鎖定的,
     其他事務如果需要修改資料,需要等待目前事務送出或復原後釋放鎖。
               

行鎖與表鎖

按照粒度,鎖可以分為表鎖、行鎖以及其他位于二者之間的鎖。表鎖在操作資料時會鎖定整張
	表,并發性能較差;行鎖則隻鎖定需要操作的資料,并發性能好。但是由于加鎖本身需要消耗
	資源(獲得鎖、檢查鎖、釋放鎖等都需要消耗資源),是以在鎖定資料較多情況下使用表鎖可以
	節省大量資源。MySQL中不同的存儲引擎支援的鎖是不一樣的,例如MyIsam隻支援表鎖,而
	InnoDB同時支援表鎖和行鎖,且出于性能考慮,絕大多數情況下使用的都是行鎖。
           

如何檢視鎖資訊

有多種方法可以檢視InnoDB中鎖的情況,例如:

select * from information_schema.innodb_locks; #鎖的概況
show engine innodb status; #InnoDB整體狀态,其中包括鎖的情況
           

試驗

事務1
mysql> start transaction;  							操作1
Query OK, 0 rows affected (0.00 sec)
mysql> update user set name='cdb1' where id=19 ;	操作2
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0
mysql> select * from information_schema.innodb_locks; 操作5
+---------------+-------------+-----------+-----------+----------------+------------+------------+-----------+----------+-----------+
| lock_id       | lock_trx_id | lock_mode | lock_type | lock_table     | lock_index | lock_space | lock_page | lock_rec | lock_data |
+---------------+-------------+-----------+-----------+----------------+------------+------------+-----------+----------+-----------+
| 34749:354:3:2 | 34749       | X         | RECORD    | `study`.`user` | PRIMARY    |        354 |         3 |        2 | 19        |
| 34748:354:3:2 | 34748       | X         | RECORD    | `study`.`user` | PRIMARY    |        354 |         3 |        2 | 19        |
+---------------+-------------+-----------+-----------+----------------+------------+------------+-----------+----------+-----------+


事務2 執行
mysql> start transaction;      						操作3
Query OK, 0 rows affected (0.00 sec)

mysql> update user set name='cdb2' where id=19;		操作4
-- 等待鎖釋放

           
此時在事務1中執行操作5  檢視鎖情況
通過上述指令可以檢視事務34749和34748占用鎖的情況;其中lock_type為RECORD,代表鎖為
行鎖(記錄鎖);lock_mode為X,代表排它鎖(寫鎖)。
	
除了排它鎖(寫鎖)之外,MySQL中還有共享鎖(讀鎖)的概念。其他篇幅
           
  1. 髒讀、不可重複讀和幻讀
  2. 事務隔離級别

    3、4 見 mysql事務處理-四種隔離級别

  3. mvcc
    RR(可重複讀)解決髒讀、不可重複讀、幻讀等問題,使用的是MVCC:MVCC全稱
     Multi-Version Concurrency Control,即多版本的并發控制協定。
     下面的例子很好的展現了MVCC的特點:
     在同一時刻,不同的事務讀取到的資料可能是不同的(即多版本)——在T5時刻,
     事務A和事務C可以讀取到不同版本的資料。
               
    mysql事務:ACID特性的實作原理
    MVCC最大的優點是讀不加鎖,是以讀寫不沖突,并發性能好。InnoDB實作
     MVCC,多個版本的資料可以共存,主要是依靠資料的隐藏列(也可以稱之為标
     記位)和undo log。其中資料的隐藏列包括了該行資料的版本号、删除時間、指
     向undo log的指針等等;當讀取資料時,MySQL可以通過隐藏列判斷是否需要
     復原并找到復原需要的undo log,進而實作MVCC,隐藏列的詳細格式不再展開。
               

下面結合前文提到的幾個問題分别說明。

  1. 總結

    1)髒讀

mysql事務:ACID特性的實作原理
當事務A在T3時間節點讀取zhangsan的餘額時,會發現資料已被其他事務修改,且狀态為未提
	交。此時事務A讀取最新資料後,根據資料的undo log執行復原操作,得到事務B修改前的數
	據,進而避免了髒讀。
           

不可重複讀

mysql事務:ACID特性的實作原理
當事務A在T2節點第一次讀取資料時,會記錄該資料的版本号(資料的版本号是以row為機關記錄
的),假設版本号為1;當事務B送出時,該行記錄的版本号增加,假設版本号為2;當事務A在T5
再一次讀取資料時,發現資料的版本号(2)大于第一次讀取時記錄的版本号(1),是以會根據
undo log執行復原操作,得到版本号為1時的資料,進而實作了可重複讀。
           

3)幻讀

InnoDB實作的RR通過next-key lock機制避免了幻讀現象。
	
	next-key lock是行鎖的一種,實作相當于record lock(記錄鎖) + gap lock(間隙鎖);其特點是不
	僅會鎖住記錄本身(record lock的功能),還會鎖定一個範圍(gap lock的功能)。當然,這裡我們
	讨論的是不加鎖讀:此時的next-key lock并不是真的加鎖,隻是為讀取的資料增加了标記(标
	記内容包括資料的版本号等);準确起見姑且稱之為類next-key lock機制。還是以前面的例子
	來說明:
           
mysql事務:ACID特性的實作原理
當事務A在T2節點第一次讀取0<id<5資料時,标記的不隻是id=1的資料,而是将範圍(0,5)進行了
标記,這樣當T5時刻再次讀取0<id<5資料時,便可以發現id=4的資料比之前标記的版本号更高,
此時再結合undo log執行復原操作,避免了幻讀。
           
  1. 總結
    概括來說,InnoDB實作的RR,通過鎖機制、資料的隐藏列、undo log和類next-
     key lock,實作了一定程度的隔離性,可以滿足大多數場景的需要。不過需要說
     明的是,RR雖然避免了幻讀問題,但是畢竟不是Serializable,不能保證完全的
     隔離,下面是一個例子,大家可以自己驗證一下。
               
mysql事務:ACID特性的實作原理

五、一緻性

  1. 基本概念
    一緻性是指事務執行結束後,資料庫的完整性限制沒有被破壞,事務執行的前後
    都是合法的資料狀态。資料庫的完整性限制包括但不限于:實體完整性(如行的
    主鍵存在且唯一)、列完整性(如字段的類型、大小、長度要符合要求)、外鍵
    限制、使用者自定義完整性(如轉賬前後,兩個賬戶餘額的和應該不變)。
               
  2. 實作
    可以說,一緻性是事務追求的最終目标:前面提到的原子性、持久性和隔離
     性,都是為了保證資料庫狀态的一緻性。此外,除了資料庫層面的保障,一緻
     性的實作也需要應用層面進行保障。
     實作一緻性的措施包括:
     1. 保證原子性、持久性和隔離性,如果這些特性無法保證,事務的一緻性也
     法保證
     2. 資料庫本身提供保障,例如不允許向整形列插入字元串值、字元串長度不能
     超過列的限制等
     3. 應用層面進行保障,例如如果轉賬操作隻扣除轉賬者的餘額,而沒有增加接
     收者的餘額,無論資料庫實作的多麼完美,也無法保證狀态的一緻
               

六、總結

下面總結一下ACID特性及其實作原理:
           
  • 原子性:語句要麼全執行,要麼全不執行,是事務最核心的特性,事務本身就是以原子性來定義的;實作主要基于undo log
  • 持久性:保證事務送出後不會因為當機等原因導緻資料丢失;實作主要基于redo log
  • 隔離性:保證事務執行盡可能不受其他事務影響;InnoDB預設的隔離級别是RR,RR的實作主要基于鎖機制、資料的隐藏列、undo log和類next-key lock機制
  • 一緻性:事務追求的最終目标,一緻性的實作既需要資料庫層面的保障,也需要應用層面的保障

轉自 并學習驗證

https://www.cnblogs.com/kismetv/p/10331633.html