一基礎概念
事務(Transaction)是通路和更新資料庫的程式執行單元;
事務中可能包含一個或多個sql語句,這些語句要麼都執行,
要麼都不執行。作為一個關系型資料庫,MySQL支援事務,本文介紹基于MySQL5.6。
mysql事務的基礎知識
邏輯架構和存儲引擎
如上圖所示,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是事務必須滿足的條件, 不如說它們是衡量事務的四個次元。
二 、原子性
- 定義
原子性是指一個事務是一個不可分割的工作機關,其中的操作要麼都做, 要麼都不做;如果事務中一個sql語句執行失敗, 則已執行的語句也必須復原,資料庫退回到事務前的狀态。
- 實作原理: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之前的 狀态。
三、持久性
- 定義
持久性是指事務一旦送出,它對資料庫的改變就應該是永久性的。接下來 的其他操作或故障不應該對其有任何影響。
- 實作原理: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大大減少。
- 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速度大大加快。
四、隔離性
- 定義
與原子性、持久性側重于研究事務本身不同,隔離性研究的是不同僚務之間的 互相影響。隔離性是指,事務内部的操作與其他事務是隔離的,并發執行的各 個事務之間不能互相幹擾。嚴格的隔離性,對應了事務隔離級别中Serializable (可串行化),但實際應用中出于性能方面的考慮很少會使用可串行化。 隔離性追求的是并發情形下事務之間互不幹擾。簡單起見,我們僅考慮最簡單 的讀操作和寫操作(暫時不考慮帶鎖讀等特殊操作),那麼隔離性的探讨,主要可 以分為兩個方面: 1. (一個事務)寫操作對(另一個事務)寫操作的影響:鎖機制保證隔離性 2. (一個事務)寫操作對(另一個事務)讀操作的影響:MVCC保證隔離性
- 鎖機制
首先來看兩個事務的寫操作之間的互相影響。隔離性要求同一時刻隻能有一個 事務對資料進行寫操作,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中還有共享鎖(讀鎖)的概念。其他篇幅
- 髒讀、不可重複讀和幻讀
-
事務隔離級别
3、4 見 mysql事務處理-四種隔離級别
- mvcc
RR(可重複讀)解決髒讀、不可重複讀、幻讀等問題,使用的是MVCC:MVCC全稱 Multi-Version Concurrency Control,即多版本的并發控制協定。 下面的例子很好的展現了MVCC的特點: 在同一時刻,不同的事務讀取到的資料可能是不同的(即多版本)——在T5時刻, 事務A和事務C可以讀取到不同版本的資料。
MVCC最大的優點是讀不加鎖,是以讀寫不沖突,并發性能好。InnoDB實作 MVCC,多個版本的資料可以共存,主要是依靠資料的隐藏列(也可以稱之為标 記位)和undo log。其中資料的隐藏列包括了該行資料的版本号、删除時間、指 向undo log的指針等等;當讀取資料時,MySQL可以通過隐藏列判斷是否需要 復原并找到復原需要的undo log,進而實作MVCC,隐藏列的詳細格式不再展開。
下面結合前文提到的幾個問題分别說明。
-
總結
1)髒讀
當事務A在T3時間節點讀取zhangsan的餘額時,會發現資料已被其他事務修改,且狀态為未提
交。此時事務A讀取最新資料後,根據資料的undo log執行復原操作,得到事務B修改前的數
據,進而避免了髒讀。
不可重複讀
當事務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機制。還是以前面的例子
來說明:
當事務A在T2節點第一次讀取0<id<5資料時,标記的不隻是id=1的資料,而是将範圍(0,5)進行了
标記,這樣當T5時刻再次讀取0<id<5資料時,便可以發現id=4的資料比之前标記的版本号更高,
此時再結合undo log執行復原操作,避免了幻讀。
- 總結
概括來說,InnoDB實作的RR,通過鎖機制、資料的隐藏列、undo log和類next- key lock,實作了一定程度的隔離性,可以滿足大多數場景的需要。不過需要說 明的是,RR雖然避免了幻讀問題,但是畢竟不是Serializable,不能保證完全的 隔離,下面是一個例子,大家可以自己驗證一下。
五、一緻性
- 基本概念
一緻性是指事務執行結束後,資料庫的完整性限制沒有被破壞,事務執行的前後 都是合法的資料狀态。資料庫的完整性限制包括但不限于:實體完整性(如行的 主鍵存在且唯一)、列完整性(如字段的類型、大小、長度要符合要求)、外鍵 限制、使用者自定義完整性(如轉賬前後,兩個賬戶餘額的和應該不變)。
- 實作
可以說,一緻性是事務追求的最終目标:前面提到的原子性、持久性和隔離 性,都是為了保證資料庫狀态的一緻性。此外,除了資料庫層面的保障,一緻 性的實作也需要應用層面進行保障。 實作一緻性的措施包括: 1. 保證原子性、持久性和隔離性,如果這些特性無法保證,事務的一緻性也 法保證 2. 資料庫本身提供保障,例如不允許向整形列插入字元串值、字元串長度不能 超過列的限制等 3. 應用層面進行保障,例如如果轉賬操作隻扣除轉賬者的餘額,而沒有增加接 收者的餘額,無論資料庫實作的多麼完美,也無法保證狀态的一緻
六、總結
下面總結一下ACID特性及其實作原理:
- 原子性:語句要麼全執行,要麼全不執行,是事務最核心的特性,事務本身就是以原子性來定義的;實作主要基于undo log
- 持久性:保證事務送出後不會因為當機等原因導緻資料丢失;實作主要基于redo log
- 隔離性:保證事務執行盡可能不受其他事務影響;InnoDB預設的隔離級别是RR,RR的實作主要基于鎖機制、資料的隐藏列、undo log和類next-key lock機制
- 一緻性:事務追求的最終目标,一緻性的實作既需要資料庫層面的保障,也需要應用層面的保障
轉自 并學習驗證
https://www.cnblogs.com/kismetv/p/10331633.html