mysql中的事務是什麼

青灯夜游
發布: 2022-02-17 16:17:41
原創
11808 人瀏覽過

在mysql中,事務是一種機制、一個操作序列,是存取和更新資料庫的程式執行單元。事務中包含一個或多個資料庫操作命令,會把所有的命令作為一個整體一起向系統提交或撤銷操作請求,即這一組資料庫命令要么都執行,要么都不執行。

mysql中的事務是什麼

本教學操作環境:windows7系統、mysql5.6版本、Dell G3電腦。

資料庫的交易(Transaction)是一種機制、一個操作序列,是存取和更新資料庫的程式執行單元,包含了一組資料庫操作指令。

事務把所有的命令作為一個整體一起向系統提交或撤銷操作請求,即這一組資料庫命令要么都執行,要么都不執行,因此事務是一個不可分割的工作邏輯單元。

在資料庫系統上執行並發操作時,交易是作為最小的控制單元來使用的,特別適用於多用戶同時操作的資料庫系統。

作為一個關聯式資料庫,MySQL支援事務,本文介紹基於MySQL5.6。

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

1. 邏輯架構與儲存引擎

#圖片來源:https://blog.csdn .net/fuzhongmin05/article/details/70904190

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

#(1)第一層:處理客戶端連線、授權認證等。

(2)第二層:伺服器層,負責查詢語句的解析、最佳化、快取以及內建函數的實作、預存程序等。

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

如無特殊說明,後文所描述的內容都是基於InnoDB。

2. 提交與回滾

#典型的MySQL交易是如下操作的:

start transaction;
……  #一条或多条sql语句
commit;
登入後複製

其中start transaction標識事務開始,commit提交事務,將執行結果寫入資料庫。如果sql語句執行出現問題,會呼叫rollback,回滾所有已經執行成功的sql語句。當然,也可以在事務中直接使用rollback語句進行回滾。

自動提交

MySQL中預設採用的是自動提交(autocommit)模式,如下所示:

在自動提交模式下,如果沒有start transaction明確地開始一個事務,那麼每個sql語句都會被當作一個事務執行提交操作。

透過以下方式,可以關閉autocommit;需要注意的是,autocommit參數是針對連接的,在一個連接中修改了參數,不會對其他連接產生影響。

如果關閉了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是事務必須滿足的條件,不如說它們是衡量事務的四個維度。 以下將詳細介紹ACID特性及其實作原理;為了便於理解,介紹的順序不是嚴格依照A-C-I-D。

原子性

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 (可串行化),但实际应用中出于性能方面的考虑很少会使用可串行化。

隔离性追求的是并发情形下事务之间互不干扰。简单起见,我们主要考虑最简单的读操作和写操作(加锁读等特殊读操作会特殊说明),那么隔离性的探讨,主要可以分为两个方面:

  • (一个事务)写操作对(另一个事务)写操作的影响:锁机制保证隔离性
  • (一个事务)写操作对(另一个事务)读操作的影响:MVCC保证隔离性

2. 锁机制

首先来看两个事务的写操作之间的相互影响。隔离性要求同一时刻只能有一个事务对数据进行写操作,InnoDB通过锁机制来保证这一点。

锁机制的基本原理可以概括为:事务在修改数据之前,需要先获得相应的锁;获得锁之后,事务便可以修改数据;该事务操作期间,这部分数据是锁定的,其他事务如果需要修改数据,需要等待当前事务提交或回滚后释放锁。

行锁与表锁

按照粒度,锁可以分为表锁、行锁以及其他位于二者之间的锁。表锁在操作数据时会锁定整张表,并发性能较差;行锁则只锁定需要操作的数据,并发性能好。但是由于加锁本身需要消耗资源(获得锁、检查锁、释放锁等都需要消耗资源),因此在锁定数据较多情况下使用表锁可以节省大量资源。MySQL中不同的存储引擎支持的锁是不一样的,例如MyIsam只支持表锁,而InnoDB同时支持表锁和行锁,且出于性能考虑,绝大多数情况下使用的都是行锁。

如何查看锁信息

有多种方法可以查看InnoDB中锁的情况,例如:

select * from information_schema.innodb_locks; #锁的概况
show engine innodb status; #InnoDB整体状态,其中包括锁的情况
登入後複製

下面来看一个例子:

#在事务A中执行:
start transaction;
update account SET balance = 1000 where id = 1;
#在事务B中执行:
start transaction;
update account SET balance = 2000 where id = 1;
登入後複製

此时查看锁的情况:

show engine innodb status查看锁相关的部分:

通过上述命令可以查看事务24052和24053占用锁的情况;其中lock_type为RECORD,代表锁为行锁(记录锁);lock_mode为X,代表排它锁(写锁)。

除了排它锁(写锁)之外,MySQL中还有共享锁(读锁)的概念。由于本文重点是MySQL事务的实现原理,因此对锁的介绍到此为止,后续会专门写文章分析MySQL中不同锁的区别、使用场景等,欢迎关注。

介绍完写操作之间的相互影响,下面讨论写操作对读操作的影响。

3. 脏读、不可重复读和幻读

首先来看并发情况下,读操作可能存在的三类问题:

(1)脏读:当前事务(A)中可以读到其他事务(B)未提交的数据(脏数据),这种现象是脏读。举例如下(以账户余额表为例):

(2)不可重复读:在事务A中先后两次读取同一个数据,两次读取的结果不一样,这种现象称为不可重复读。脏读与不可重复读的区别在于:前者读到的是其他事务未提交的数据,后者读到的是其他事务已提交的数据。举例如下:

(3)幻讀:在事務A中依照某個條件先後兩次查詢資料庫,兩次查詢結果的條數不同,這種現象稱為幻讀。不可重複讀與幻讀的差異可以通俗的理解為:前者是資料變了,後者是資料的行數變了。舉例如下:

4. 交易隔離等級

##SQL標準中定義了四個隔離級別,並規定了每種隔離等級下上述幾個問題是否存在。一般來說,隔離等級越低,系統開銷越低,可支援的同時越高,但隔離性也越差。隔離等級與讀取問題的關係如下:

在實際應用中,

讀取未提交在並發時會導致很多問題,而效能相對於其他隔離等級提高卻很有限,因此使用較少。 可串列化強制交易串列,並發效率很低,只有當對資料一致性要求極高且可以接受沒有並發時使用,因此使用也較少。因此在大多數資料庫系統中,預設的隔離等級是讀取已提交(如Oracle)可重複讀取(後文簡稱RR

可以透過以下兩個指令分別查看全域隔離等級和本次會話的隔離等級:

InnoDB預設的隔離等級是RR,後文會重點介紹RR。要注意的是,在SQL標準中,RR是無法避免幻讀問題的,但InnoDB實作的RR避免了幻讀問題。

5. MVCC

RR解決髒讀、不可重複讀、幻讀等問題,使用的是MVCC:MVCC全名為Multi- Version Concurrency Control,即多版本的並發控制協定。以下的例子很好的體現了MVCC的特點:在同一時刻,不同的事務讀取到的資料可能是不同的(即多版本)-在T5時刻,事務A和事務C可以讀取到不同版本的數據。

MVCC最大的優點是讀不加鎖,因此讀寫不衝突,並發效能好。 InnoDB實作MVCC,多個版本的資料可以共存,主要基於以下技術及資料結構:

1)隱藏列:InnoDB中每行資料都有隱藏列,隱藏列中包含了本行資料的事務id、指向undo log的指標等。

2)基於undo log的版本鏈:前面說到每行資料的隱藏列中包含了指向undo log的指針,而每條undo log也會指向更早版本的undo log,從而形成一條版本鏈。

3)ReadView:透過隱藏列和版本鏈,MySQL可以將資料還原到指定版本;但是具體要還原到哪個版本,則需要根據ReadView來確定。所謂ReadView,是指交易(記做事務A)在某一時刻給整個事務系統(trx_sys)打快照,之後再進行讀取操作時,會將讀取到的資料中的事務id與trx_sys快照比較,從而判斷資料對該ReadView是否可見,即對事務A是否可見。

trx_sys中的主要內容,以及判斷可見性的方法如下:

    #low_limit_id:表示產生ReadView時系統中應該指派給下一個交易的id。如果資料的交易id大於等於low_limit_id,則對該ReadView不可見。
  • up_limit_id:表示產生ReadView時目前系統中活躍的讀寫事務中最小的事務id。如果資料的事務id小於up_limit_id,則對該ReadView可見。
  • rw_trx_ids:表示產生ReadView時目前系統中活躍的讀寫事務的事務id清單。如果資料的交易id在low_limit_id和up_limit_id之間,則需要判斷交易id是否在rw_trx_ids中:如果在,說明產生ReadView時交易仍在活躍中,因此資料對ReadView不可見;如果不在,說明產生ReadView時事務已經提交了,因此資料對ReadView可見。
以下以RR隔離等級為例,結合前文提到的幾個問題分別說明。

(1)髒讀

#

当事务A在T3时刻读取zhangsan的余额前,会生成ReadView,由于此时事务B没有提交仍然活跃,因此其事务id一定在ReadView的rw_trx_ids中,因此根据前面介绍的规则,事务B的修改对ReadView不可见。接下来,事务A根据指针指向的undo log查询上一版本的数据,得到zhangsan的余额为100。这样事务A就避免了脏读。

(2)不可重复读

当事务A在T2时刻读取zhangsan的余额前,会生成ReadView。此时事务B分两种情况讨论,一种是如图中所示,事务已经开始但没有提交,此时其事务id在ReadView的rw_trx_ids中;一种是事务B还没有开始,此时其事务id大于等于ReadView的low_limit_id。无论是哪种情况,根据前面介绍的规则,事务B的修改对ReadView都不可见。

当事务A在T5时刻再次读取zhangsan的余额时,会根据T2时刻生成的ReadView对数据的可见性进行判断,从而判断出事务B的修改不可见;因此事务A根据指针指向的undo log查询上一版本的数据,得到zhangsan的余额为100,从而避免了不可重复读。

(3)幻读

MVCC避免幻读的机制与避免不可重复读非常类似。

当事务A在T2时刻读取0

当事务A在T5时刻再次读取0

扩展

前面介绍的MVCC,是RR隔离级别下“非加锁读”实现隔离性的方式。下面是一些简单的扩展。

(1)读已提交(RC)隔离级别下的非加锁读

RC与RR一样,都使用了MVCC,其主要区别在于:

RR是在事务开始后第一次执行select前创建ReadView,直到事务提交都不会再创建。根据前面的介绍,RR可以避免脏读、不可重复读和幻读。

RC每次执行select前都会重新建立一个新的ReadView,因此如果事务A第一次select之后,事务B对数据进行了修改并提交,那么事务A第二次select时会重新建立新的ReadView,因此事务B的修改对事务A是可见的。因此RC隔离级别可以避免脏读,但是无法避免不可重复读和幻读。

(2)加锁读与next-key lock

按照是否加锁,MySQL的读可以分为两种:

一种是非加锁读,也称作快照读、一致性读,使用普通的select语句,这种情况下使用MVCC避免了脏读、不可重复读、幻读,保证了隔离性。

另一种是加锁读,查询语句有所不同,如下所示:

#共享锁读取
select...lock in share mode
#排它锁读取
select...for update
登入後複製

加锁读在查询时会对查询的数据加锁(共享锁或排它锁)。由于锁的特性,当某事务对数据进行加锁读后,其他事务无法对数据进行写操作,因此可以避免脏读和不可重复读。而避免幻读,则需要通过next-key lock。next-key lock是行锁的一种,实现相当于record lock(记录锁) + gap lock(间隙锁);其特点是不仅会锁住记录本身(record lock的功能),还会锁定一个范围(gap lock的功能)因此,加锁读同样可以避免脏读、不可重复读和幻读,保证隔离性。

6. 总结

概括來說,InnoDB實現的RR,透過鎖定機制(包含next-key lock)、MVCC(包括資料的隱藏列、基於undo log的版本鏈、ReadView)等,實現了一定程度的隔離性,可以滿足大多數場景的需求。

不過需要說明的是,RR雖然避免了幻讀問題,但畢竟不是Serializable,不能保證完全的隔離,下面是兩個例子:

第一個例子,如果在事務中第一次讀取採用非加鎖讀,第二次讀取採用加鎖讀,則如果在兩次讀取之間資料發生了變化,兩次讀取到的結果不一樣,因為加鎖讀時不會採用MVCC。

第二個例子,如下圖所示,大家可以自己驗證一下。

一致性

1. 基本概念

一致性是指事務執行結束後,資料庫的完整性約束沒有被破壞,事務執行的前後都是合法的資料狀態。 資料庫的完整性限制包括但不限於:實體完整性(如行的主鍵存在且唯一)、列完整性(如欄位的類型、大小、長度要符合要求)、外鍵約束、使用者自訂完整性(如轉帳前後,兩個帳戶餘額的和應該不變)。

2. 實現

可以說,一致性是事務追求的最終目標:前面提到的原子性、持久性和隔離性,都是為了保證資料庫狀態的一致性。此外,除了資料庫層面的保障,一致性的實現也需要應用層面來保障。

實現一致性的措施包括:

  • 保證原子性、持久性和隔離性,如果這些特性無法保證,交易的一致性也無法保證
  • 資料庫本身提供保障,例如不允許在整形列插入字串值、字串長度不能超過列的限制等
  • 應用程式層面進行保障,例如如果轉帳作業只扣除轉帳者的餘額,而沒有增加接收者的餘額,無論資料庫實現的多麼完美,也無法保證狀態的一致

總結

下面總結一下ACID特性及其實作原理:

  • 原子性:語句要麼全執行,要麼全不執行,是事務最核心的特性,事務本身就是以原子性來定義的;實現主要基於undo log
  • 持久性:保證事務提交後不會因為宕機等原因導致資料遺失;實現主要基於redo log
  • 隔離性:保證事務執行盡可能不受其他事務影響;InnoDB預設的隔離等級是RR,RR的實作主要基於鎖定機制(包含next-key lock)、MVCC(包含資料的隱藏欄位、基於undo log的版本鏈、ReadView)
  • 一致性:交易追求的最終目標,一致性的實作既需要資料庫層面的保障,也需要應用層面的保障
#

以上是mysql中的事務是什麼的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板