mysql提交事务_mysql事务的实现原理
此篇文章算是對mysql事務的一個總結,基本把mysql事務相關的知識點都涵蓋到了,面試問來問去無非也就是這些,在了解這些之前我們先對mysql在執行的過程中有一個整體的認識,如下圖
如上圖所示,MySQL服務器邏輯架構從上往下可以分為三層:
(1)第一層:處理客戶端連接、授權認證等。
(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 inset (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 inset (0.00 sec)
如果關閉了autocommit,則所有的sql語句都在一個事務中,直到執行了commit或rollback,該事務結束,同時開始了另外一個事務。
特殊操作
在MySQL中,存在一些特殊的命令,如果在事務中執行了這些命令,會馬上強制執行commit提交事務;如DDL語句(create table/drop table/alter/table)、lock tables語句等等。
不過,常用的select、insert、update和delete命令,都不會強制提交事務。
事務的特點:ACID
原子性(Atomicity)
「定義」
「實現原理」在說明原子性原理之前,首先介紹一下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) 的回滾日志
根據上面流程可以得出如下結論:
每條數據變更(insert/update/delete)操作都伴隨一條undo log的生成,并且回滾日志必須先于數據持久化到磁盤上所謂的回滾就是根據回滾日志做逆向操作,比如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系統宕機,斷電的時候可能會丟數據!!!
因為我們的數據已經提交了,但此時是在緩沖池里頭,還沒來得及在磁盤持久化,所以我們急需一種機制需要存一下已提交事務的數據,為恢復數據使用。
于是redolog就派上用場了。下面看下redolog是什么時候產生的
既然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 inset (0.00 sec)
「MVCC」
RR解決臟讀、不可重復讀、幻讀等問題,使用的是MVCC:MVCC全稱Multi-VersionConcurrencyControl,即多版本的并發控制協議。下面的例子很好的體現了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-keylock是行鎖的一種,實現相當于recordlock(記錄鎖)+gaplock(間隙鎖);其特點是不僅會鎖住記錄本身(recordlock的功能),還會鎖定一個范圍(gaplock的功能)。當然,這里我們討論的是不加鎖讀:此時的next-keylock并不是真的加鎖,只是為讀取的數據增加了標記(標記內容包括數據的版本號等);準確起見姑且稱之為類next-keylock機制。還是以前面的例子來說明:
當事務A在T2節點第一次讀取0
總結
概括來說,InnoDB實現的RR,通過鎖機制、數據的隱藏列、undolog和類next-keylock,實現了一定程度的隔離性,可以滿足大多數場景的需要。不過需要說明的是,RR雖然避免了幻讀問題,但是畢竟不是Serializable,不能保證完全的隔離,下面是一個例子,大家可以自己驗證一下。
一致性
基本概念
一致性是指事務執行結束后,數據庫的完整性約束沒有被破壞,事務執行的前后都是合法的數據狀態。數據庫的完整性約束包括但不限于:實體完整性(如行的主鍵存在且唯一)、列完整性(如字段的類型、大小、長度要符合要求)、外鍵約束、用戶自定義完整性(如轉賬前后,兩個賬戶余額的和應該不變)。
實現
可以說,一致性是事務追求的最終目標:前面提到的原子性、持久性和隔離性,都是為了保證數據庫狀態的一致性。此外,除了數據庫層面的保障,一致性的實現也需要應用層面進行保障。
實現一致性的措施包括:
保證原子性、持久性和隔離性,如果這些特性無法保證,事務的一致性也無法保證數據庫本身提供保障,例如不允許向整形列插入字符串值、字符串長度不能超過列的限制等應用層面進行保障,例如如果轉賬操作只扣除轉賬者的余額,而沒有增加接收者的余額,無論數據庫實現的多么完美,也無法保證狀態的一致
總結
以上是生活随笔為你收集整理的mysql提交事务_mysql事务的实现原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ftp上传文件服务器报550错误_jav
- 下一篇: decimal转为string sql_