深入學習MySQL事務:ACID特性的實現(xiàn)原理

事務是MySQL等關系型數(shù)據(jù)庫區(qū)別于NOSQL的重要方面,是保證數(shù)據(jù)一致性的重要手段。

創(chuàng)新互聯(lián)是一家專注于網(wǎng)站設計、成都做網(wǎng)站與策劃設計,三沙網(wǎng)站建設哪家好?創(chuàng)新互聯(lián)做網(wǎng)站,專注于網(wǎng)站建設十載,網(wǎng)設計領域的專業(yè)建站公司;建站業(yè)務涵蓋:三沙等地區(qū)。三沙做網(wǎng)站價格咨詢:13518219792

MySQL博大精深,文章疏漏之處在所難免,歡迎批評指正。

一、基礎概念

事務(Transaction)是訪問和更新數(shù)據(jù)庫的程序執(zhí)行單元;事務中可能包含一個或多個sql語句,這些語句要么都執(zhí)行,要么都不執(zhí)行。作為一個關系型數(shù)據(jù)庫,MySQL支持事務,本文介紹基于MySQL5.6。

首先回顧一下MySQL事務的基礎知識。

1. 邏輯架構和存儲引擎

深入學習MySQL事務:ACID特性的實現(xiàn)原理

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

第一層:處理客戶端連接、授權認證等。

第二層:服務器層,負責查詢語句的解析、優(yōu)化、緩存以及內(nèi)置函數(shù)的實現(xiàn)、存儲過程等。

第三層:存儲引擎,負責MySQL中數(shù)據(jù)的存儲和提取。MySQL中服務器層不管理事務,事務是由存儲引擎實現(xiàn)的。MySQL支持事務的存儲引擎有InnoDB、NDB Cluster等,其中InnoDB的使用最為廣泛;其他存儲引擎不支持事務,如MyIsam、Memory等。

如無特殊說明,后文中描述的內(nèi)容都是基于InnoDB。

2. 提交和回滾

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

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

其中start transaction標識事務開始,commit提交事務,將執(zhí)行結果寫入到數(shù)據(jù)庫。如果sql語句執(zhí)行出現(xiàn)問題,會調(diào)用rollback,回滾所有已經(jīng)執(zhí)行成功的sql語句。當然,也可以在事務中直接使用rollback語句進行回滾。

自動提交

MySQL中默認采用的是自動提交(autocommit)模式,如下所示:

深入學習MySQL事務:ACID特性的實現(xiàn)原理

在自動提交模式下,如果沒有start transaction顯式地開始一個事務,那么每個sql語句都會被當做一個事務執(zhí)行提交操作。

通過如下方式,可以關閉autocommit;需要注意的是,autocommit參數(shù)是針對連接的,在一個連接中修改了參數(shù),不會對其他連接產(chǎn)生影響。

深入學習MySQL事務:ACID特性的實現(xiàn)原理

如果關閉了autocommit,則所有的sql語句都在一個事務中,直到執(zhí)行了commit或rollback,該事務結束,同時開始了另外一個事務。

特殊操作

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

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

3. ACID特性

ACID是衡量事務的四個特性:

原子性:(Atomicity)

一致性:(Consistency)

隔離性:(Isolation)

持久性:(Durability)

按照嚴格的標準,只有同時滿足ACID特性才是事務;但是在各大數(shù)據(jù)庫廠商的實現(xiàn)中,真正滿足ACID的事務少之又少。例如MySQL的NDB Cluster事務不滿足持久性和隔離性;InnoDB默認事務隔離級別是可重復讀,不滿足隔離性;Oracle默認的事務隔離級別為READ COMMITTED,不滿足隔離性……因此與其說ACID是事務必須滿足的條件,不如說它們是衡量事務的四個維度。

下面將詳細介紹ACID特性及其實現(xiàn)原理;為了便于理解,介紹的順序不是嚴格按照A-C-I-D。

二、原子性

1. 定義

原子性是指一個事務是一個不可分割的工作單位,其中的操作要么都做,要么都不做;如果事務中一個sql語句執(zhí)行失敗,則已執(zhí)行的語句也必須回滾,數(shù)據(jù)庫退回到事務前的狀態(tài)。

2. 實現(xiàn)原理:undo log

在說明原子性原理之前,首先介紹一下MySQL的事務日志。MySQL的日志有很多種,如二進制日志、錯誤日志、查詢?nèi)罩?、慢查詢?nèi)罩镜?,此外InnoDB存儲引擎還提供了兩種事務日志:redo log(重做日志)和undo log(回滾日志)。其中redo log用于保證事務持久性;undo log則是事務原子性和隔離性實現(xiàn)的基礎。

下面說回undo log。實現(xiàn)原子性的關鍵,是當事務回滾時能夠撤銷所有已經(jīng)成功執(zhí)行的sql語句。InnoDB實現(xiàn)回滾,靠的是undo log:當事務對數(shù)據(jù)庫進行修改時,InnoDB會生成對應的undo log;如果事務執(zhí)行失敗或調(diào)用了rollback,導致事務需要回滾,便可以利用undo log中的信息將數(shù)據(jù)回滾到修改之前的樣子。

undo log屬于邏輯日志,它記錄的是sql執(zhí)行相關的信息。當發(fā)生回滾時,InnoDB會根據(jù)undo log的內(nèi)容做與之前相反的工作:對于每個insert,回滾時會執(zhí)行delete;對于每個delete,回滾時會執(zhí)行insert;對于每個update,回滾時會執(zhí)行一個相反的update,把數(shù)據(jù)改回去。

以update操作為例:當事務執(zhí)行update時,其生成的undo log中會包含被修改行的主鍵(以便知道修改了哪些行)、修改了哪些列、這些列在修改前后的值等信息,回滾時便可以使用這些信息將數(shù)據(jù)還原到update之前的狀態(tài)。

三、持久性

1. 定義

持久性是指事務一旦提交,它對數(shù)據(jù)庫的改變就應該是永久性的。接下來的其他操作或故障不應該對其有任何影響。

2. 實現(xiàn)原理:redo log

redo log和undo log都屬于InnoDB的事務日志。下面先聊一下redo log存在的背景。

InnoDB作為MySQL的存儲引擎,數(shù)據(jù)是存放在磁盤中的,但如果每次讀寫數(shù)據(jù)都需要磁盤IO,效率會很低。為此,InnoDB提供了緩存(Buffer Pool),Buffer Pool中包含了磁盤中部分數(shù)據(jù)頁的映射,作為訪問數(shù)據(jù)庫的緩沖:當從數(shù)據(jù)庫讀取數(shù)據(jù)時,會首先從Buffer Pool中讀取,如果Buffer Pool中沒有,則從磁盤讀取后放入Buffer Pool;當向數(shù)據(jù)庫寫入數(shù)據(jù)時,會首先寫入Buffer Pool,Buffer Pool中修改的數(shù)據(jù)會定期刷新到磁盤中(這一過程稱為刷臟)。

Buffer Pool的使用大大提高了讀寫數(shù)據(jù)的效率,但是也帶了新的問題:如果MySQL宕機,而此時Buffer Pool中修改的數(shù)據(jù)還沒有刷新到磁盤,就會導致數(shù)據(jù)的丟失,事務的持久性無法保證。

于是,redo log被引入來解決這個問題:當數(shù)據(jù)修改時,除了修改Buffer Pool中的數(shù)據(jù),還會在redo log記錄這次操作;當事務提交時,會調(diào)用fsync接口對redo log進行刷盤。如果MySQL宕機,重啟時可以讀取redo log中的數(shù)據(jù),對數(shù)據(jù)庫進行恢復。redo log采用的是WAL(Write-ahead logging,預寫式日志),所有修改先寫入日志,再更新到Buffer Pool,保證了數(shù)據(jù)不會因MySQL宕機而丟失,從而滿足了持久性要求。

既然redo log也需要在事務提交時將日志寫入磁盤,為什么它比直接將Buffer Pool中修改的數(shù)據(jù)寫入磁盤(即刷臟)要快呢?主要有以下兩方面的原因:

1、刷臟是隨機IO,因為每次修改的數(shù)據(jù)位置隨機,但寫redo log是追加操作,屬于順序IO。

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

3. redo log與binlog

我們知道,在MySQL中還存在binlog(二進制日志)也可以記錄寫操作并用于數(shù)據(jù)的恢復,但二者是有著根本的不同的:

  1. 作用不同:redo log是用于crash recovery的,保證MySQL宕機也不會影響持久性;binlog是用于point-in-time recovery的,保證服務器可以基于時間點恢復數(shù)據(jù),此外binlog還用于主從復制。

  2. 層次不同:redo log是InnoDB存儲引擎實現(xiàn)的,而binlog是MySQL的服務器層(可以參考文章前面對MySQL邏輯架構的介紹)實現(xiàn)的,同時支持InnoDB和其他存儲引擎。

  3. 內(nèi)容不同:redo log是物理日志,內(nèi)容基于磁盤的Page;binlog是邏輯日志,內(nèi)容是一條條sql。

  4. 寫入時機不同:binlog在事務提交時寫入;redo log的寫入時機相對多元:

前面曾提到:當事務提交時會調(diào)用fsync對redo log進行刷盤;這是默認情況下的策略,修改innodb_flush_log_at_trx_commit參數(shù)可以改變該策略,但事務的持久性將無法保證。

除了事務提交時,還有其他刷盤時機:如master thread每秒刷盤一次redo log等,這樣的好處是不一定要等到commit時刷盤,commit速度大大加快。

四、隔離性

1. 定義

與原子性、持久性側(cè)重于研究事務本身不同,隔離×××的是不同事務之間的相互影響。隔離性是指,事務內(nèi)部的操作與其他事務是隔離的,并發(fā)執(zhí)行的各個事務之間不能互相干擾。嚴格的隔離性,對應了事務隔離級別中的Serializable (可串行化),但實際應用中出于性能方面的考慮很少會使用可串行化。

隔離性追求的是并發(fā)情形下事務之間互不干擾。簡單起見,我們僅考慮最簡單的讀操作和寫操作(暫時不考慮帶鎖讀等特殊操作),那么隔離性的探討,主要可以分為兩個方面:

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

(一個事務)寫操作對(另一個事務)讀操作的影響:MVCC保證隔離性

2. 鎖機制

首先來看兩個事務的寫操作之間的相互影響。隔離性要求同一時刻只能有一個事務對數(shù)據(jù)進行寫操作,InnoDB通過鎖機制來保證這一點。

鎖機制的基本原理可以概括為:事務在修改數(shù)據(jù)之前,需要先獲得相應的鎖;獲得鎖之后,事務便可以修改數(shù)據(jù);該事務操作期間,這部分數(shù)據(jù)是鎖定的,其他事務如果需要修改數(shù)據(jù),需要等待當前事務提交或回滾后釋放鎖。

行鎖與表鎖

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

如何查看鎖信息

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

select * from information_schema.innodb_locks; #鎖的概況
show engine innodb status; #InnoDB整體狀態(tài),其中包括鎖的情況

下面來看一個例子:

#在事務A中執(zhí)行:start?transaction;update?account?SET?balance?=?1000?where?id?=?1;#在事務B中執(zhí)行:start?transaction;update?account?SET?balance?=?2000?where?id?=?1;

此時查看鎖的情況:

深入學習MySQL事務:ACID特性的實現(xiàn)原理

show engine innodb status查看鎖相關的部分:

深入學習MySQL事務:ACID特性的實現(xiàn)原理

通過上述命令可以查看事務24052和24053占用鎖的情況;其中l(wèi)ock_type為RECORD,代表鎖為行鎖(記錄鎖);lock_mode為X,代表排它鎖(寫鎖)。

除了排它鎖(寫鎖)之外,MySQL中還有共享鎖(讀鎖)的概念。由于本文重點是MySQL事務的實現(xiàn)原理,因此對鎖的介紹到此為止,后續(xù)會專門寫文章分析MySQL中不同鎖的區(qū)別、使用場景等,歡迎關注。

介紹完寫操作之間的相互影響,下面討論寫操作對讀操作的影響。

3. 臟讀、不可重復讀和幻讀

首先來看并發(fā)情況下,讀操作可能存在的三類問題:

臟讀:當前事務(A)中可以讀到其他事務(B)未提交的數(shù)據(jù)(臟數(shù)據(jù)),這種現(xiàn)象是臟讀。舉例如下(以賬戶余額表為例):

深入學習MySQL事務:ACID特性的實現(xiàn)原理

不可重復讀:在事務A中先后兩次讀取同一個數(shù)據(jù),兩次讀取的結果不一樣,這種現(xiàn)象稱為不可重復讀。臟讀與不可重復讀的區(qū)別在于:前者讀到的是其他事務未提交的數(shù)據(jù),后者讀到的是其他事務已提交的數(shù)據(jù)。舉例如下:

深入學習MySQL事務:ACID特性的實現(xiàn)原理

幻讀:在事務A中按照某個條件先后兩次查詢數(shù)據(jù)庫,兩次查詢結果的條數(shù)不同,這種現(xiàn)象稱為幻讀。不可重復讀與幻讀的區(qū)別可以通俗的理解為:前者是數(shù)據(jù)變了,后者是數(shù)據(jù)的行數(shù)變了。舉例如下:

深入學習MySQL事務:ACID特性的實現(xiàn)原理

4. 事務隔離級別

SQL標準中定義了四種隔離級別,并規(guī)定了每種隔離級別下上述幾個問題是否存在。一般來說,隔離級別越低,系統(tǒng)開銷越低,可支持的并發(fā)越高,但隔離性也越差。隔離級別與讀問題的關系如下:

深入學習MySQL事務:ACID特性的實現(xiàn)原理

在實際應用中,讀未提交在并發(fā)時會導致很多問題,而性能相對于其他隔離級別提高卻很有限,因此使用較少??纱谢瘡娭剖聞沾校l(fā)效率很低,只有當對數(shù)據(jù)一致性要求極高且可以接受沒有并發(fā)時使用,因此使用也較少。因此在大多數(shù)數(shù)據(jù)庫系統(tǒng)中,默認的隔離級別是讀已提交(如Oracle)或可重復讀(后文簡稱RR)。

可以通過如下兩個命令分別查看全局隔離級別和本次會話的隔離級別:

深入學習MySQL事務:ACID特性的實現(xiàn)原理

深入學習MySQL事務:ACID特性的實現(xiàn)原理

InnoDB默認的隔離級別是RR,后文會重點介紹RR。需要注意的是,在SQL標準中,RR是無法避免幻讀問題的,但是InnoDB實現(xiàn)的RR避免了幻讀問題。

5. MVCC

RR解決臟讀、不可重復讀、幻讀等問題,使用的是MVCC:MVCC全稱Multi-Version Concurrency Control,即多版本的并發(fā)控制協(xié)議。下面的例子很好的體現(xiàn)了MVCC的特點:在同一時刻,不同的事務讀取到的數(shù)據(jù)可能是不同的(即多版本)——在T5時刻,事務A和事務C可以讀取到不同版本的數(shù)據(jù)。

深入學習MySQL事務:ACID特性的實現(xiàn)原理

MVCC最大的優(yōu)點是讀不加鎖,因此讀寫不沖突,并發(fā)性能好。InnoDB實現(xiàn)MVCC,多個版本的數(shù)據(jù)可以共存,主要是依靠數(shù)據(jù)的隱藏列(也可以稱之為標記位)和undo log。其中數(shù)據(jù)的隱藏列包括了該行數(shù)據(jù)的版本號、刪除時間、指向undo log的指針等等;當讀取數(shù)據(jù)時,MySQL可以通過隱藏列判斷是否需要回滾并找到回滾需要的undo log,從而實現(xiàn)MVCC;隱藏列的詳細格式不再展開。

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

臟讀

深入學習MySQL事務:ACID特性的實現(xiàn)原理

當事務A在T3時間節(jié)點讀取zhangsan的余額時,會發(fā)現(xiàn)數(shù)據(jù)已被其他事務修改,且狀態(tài)為未提交。此時事務A讀取最新數(shù)據(jù)后,根據(jù)數(shù)據(jù)的undo log執(zhí)行回滾操作,得到事務B修改前的數(shù)據(jù),從而避免了臟讀。

不可重復讀

深入學習MySQL事務:ACID特性的實現(xiàn)原理

當事務A在T2節(jié)點第一次讀取數(shù)據(jù)時,會記錄該數(shù)據(jù)的版本號(數(shù)據(jù)的版本號是以row為單位記錄的),假設版本號為1;當事務B提交時,該行記錄的版本號增加,假設版本號為2;當事務A在T5再一次讀取數(shù)據(jù)時,發(fā)現(xiàn)數(shù)據(jù)的版本號(2)大于第一次讀取時記錄的版本號(1),因此會根據(jù)undo log執(zhí)行回滾操作,得到版本號為1時的數(shù)據(jù),從而實現(xiàn)了可重復讀。

幻讀

InnoDB實現(xiàn)的RR通過next-key lock機制避免了幻讀現(xiàn)象。

next-key lock是行鎖的一種,實現(xiàn)相當于record lock(記錄鎖) + gap lock(間隙鎖);其特點是不僅會鎖住記錄本身(record lock的功能),還會鎖定一個范圍(gap lock的功能)。當然,這里我們討論的是不加鎖讀:此時的next-key lock并不是真的加鎖,只是為讀取的數(shù)據(jù)增加了標記(標記內(nèi)容包括數(shù)據(jù)的版本號等);準確起見姑且稱之為類next-key lock機制。還是以前面的例子來說明:

深入學習MySQL事務:ACID特性的實現(xiàn)原理

當事務A在T2節(jié)點第一次讀取0<id<5數(shù)據(jù)時,標記的不只是id=1的數(shù)據(jù),而是將范圍(0,5)進行了標記,這樣當T5時刻再次讀取0<id<5數(shù)據(jù)時,便可以發(fā)現(xiàn)id=4的數(shù)據(jù)比之前標記的版本號更高,此時再結合undo log執(zhí)行回滾操作,避免了幻讀。

6. 總結

概括來說,InnoDB實現(xiàn)的RR,通過鎖機制、數(shù)據(jù)的隱藏列、undo log和類next-key lock,實現(xiàn)了一定程度的隔離性,可以滿足大多數(shù)場景的需要。不過需要說明的是,RR雖然避免了幻讀問題,但是畢竟不是Serializable,不能保證完全的隔離,下面是一個例子,大家可以自己驗證一下。

深入學習MySQL事務:ACID特性的實現(xiàn)原理

五、一致性

##1. 基本概念

一致性是指事務執(zhí)行結束后,數(shù)據(jù)庫的完整性約束沒有被破壞,事務執(zhí)行的前后都是合法的數(shù)據(jù)狀態(tài)。數(shù)據(jù)庫的完整性約束包括但不限于:實體完整性(如行的主鍵存在且唯一)、列完整性(如字段的類型、大小、長度要符合要求)、外鍵約束、用戶自定義完整性(如轉(zhuǎn)賬前后,兩個賬戶余額的和應該不變)。

2. 實現(xiàn)

可以說,一致性是事務追求的最終目標:前面提到的原子性、持久性和隔離性,都是為了保證數(shù)據(jù)庫狀態(tài)的一致性。此外,除了數(shù)據(jù)庫層面的保障,一致性的實現(xiàn)也需要應用層面進行保障。

實現(xiàn)一致性的措施包括:

保證原子性、持久性和隔離性,如果這些特性無法保證,事務的一致性也無法保證

數(shù)據(jù)庫本身提供保障,例如不允許向×××列插入字符串值、字符串長度不能超過列的限制等

應用層面進行保障,例如如果轉(zhuǎn)賬操作只扣除轉(zhuǎn)賬者的余額,而沒有增加接收者的余額,無論數(shù)據(jù)庫實現(xiàn)的多么完美,也無法保證狀態(tài)的一致

六、總結

下面總結一下ACID特性及其實現(xiàn)原理:

原子性:語句要么全執(zhí)行,要么全不執(zhí)行,是事務最核心的特性,事務本身就是以原子性來定義的;實現(xiàn)主要基于undo log

持久性:保證事務提交后不會因為宕機等原因?qū)е聰?shù)據(jù)丟失;實現(xiàn)主要基于redo log

隔離性:保證事務執(zhí)行盡可能不受其他事務影響;InnoDB默認的隔離級別是RR,RR的實現(xiàn)主要基于鎖機制、數(shù)據(jù)的隱藏列、undo log和類next-key lock機制

一致性:事務追求的最終目標,一致性的實現(xiàn)既需要數(shù)據(jù)庫層面的保障,也需要應用層面的保障

最后

大家覺得不錯可以點個贊在關注下我,以后還會分享更多文章!

本文題目:深入學習MySQL事務:ACID特性的實現(xiàn)原理
文章來源:http://muchs.cn/article2/ighsoc.html

成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站收錄云服務器、網(wǎng)站設計、手機網(wǎng)站建設、關鍵詞優(yōu)化虛擬主機

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

手機網(wǎng)站建設