Mysql 事務-你想知道都在這

什麼是事務

什麼是事務?事務是做爲單個邏輯工做單元執行的一系列操做,通俗易懂的說就是一組原子性的 SQL 查詢。Mysql 中事務的支持在存儲引擎層,MyISAM 存儲引擎不支持事務,而 InnoDB 支持,這是 Mysql 5.5.5 之後默認引擎由 MyISAM 換成 InnoDB 的最根本緣由。java

事務的 ACID 屬性

原子性(Atomicity):做爲邏輯工做單元,一個事務裏的全部操做的執行,要麼所有成功,要麼所有失敗。mysql

一致性(Consistency):數據庫從一個一致性狀態變換到另一個一致性狀態,數據庫的完整性不會受到破壞。linux

隔離性(Isolation):一般來講,一個事務所作的修改在最終提交前,對其餘事務是不可見的。爲何是一般來講,爲了提升事務的併發引出不一樣的隔離級別,具體參考下一章節。面試

持久性(Durability):一旦事務提交,則其所作的修改就會永久保存到數據庫中,即便系統故障,修改的數據也不會丟失。sql

事務的隔離級別

爲了儘量的高併發,事務的隔離性被分爲四個級別:讀未提交、讀已提交、可重複讀和串行化。用戶能夠根據須要選擇不一樣的級別。數據庫

未提交讀(READ UNCOMMITTED):一個事務還未提交,它的變動就能被別的事務看到。緩存

:事務 A 能夠讀到事務 B 修改的但還未提交的數據,會致使髒讀(可能事務 B 在提交後失敗了,事務 A 讀到的數據是髒的)。安全

提交讀(READ COMMITTED):一個事務提交後,它的變動才能被其餘事務看到。大多數據庫系統的默認級別,但 Mysql 不是。bash

:事務 A 只能讀到事務 B 修改並提交後的數據,會致使不可重複讀(事務 A 中執行兩次查詢,一次在事務 B 提交過程當中,一次在事務 B 提交以後,會致使兩次讀取的結果不一致)。session

可重複讀(REPEATABLE READ):未提交的事務的變動不能被其餘事務看到,同時一次事務過程當中屢次讀取一樣記錄的結果是一致的。 :事務 A 在執行過程當中屢次獲取某範圍內的記錄,事務 B 提交後在此範圍內插入或者刪除 N條記錄,事務 A 執行過程當中屢次範圍讀會存在不一致,即幻讀(Mysql 的默認級別,InnoDB 經過 MVVC 解決了幻讀的問題)。

可串行化(SERIALIZABLE):當兩個事務間存在讀寫衝突時,數據庫經過加鎖強制事務串行執行,解決了前面所說的全部問題(髒讀、不可重複讀、幻讀)。是最高隔離的隔離級別。

用表格能夠更清晰的描述四種隔離級別的定義和可能存在的問題:

以上是對四種隔離級別的定義和初步認識,《 十分鐘搞懂MySQL四種事務隔離級別 》這篇文章中有動圖能夠完全弄明白隔離級別。

Mysql 中的事務

一、事務的自動提交

Mysql 默認採用自動提交(AUTOCOMMIT)模式,也就是說,若是不顯示地開始一個事務,則每一個查詢都被當作一個事務執行提交操做。 能夠經過如下命令查看 mysql 是否打開自動提交,

mysql> show variables like 'AUTOCOMMIT';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit    | ON    |
+---------------+-------+
1 row in set (0.01 sec)
--1 或者 ON 表示啓用, 0 或者 OFF 表示禁用
mysql> SET AUTOCOMMIT = 0/1; 
--以上命令能夠打開和關閉自動提交
複製代碼

一、經過 set autocommit = 0 關閉當前會話的自動提交,若是須要對全局生效必須再配置文件中進行修改。

二、關閉自動提交後,用戶的全部 DML 語句都會在同一個事務中,直到遇到 COMMIT 或 ROLLBACK 指令結束事務。

一張動圖來講明以上兩點:

固然,用戶能夠經過 start transaction 或者 begin 顯示的開啓一個事務。**顯示的開啓事務會自動執行 set autocommit = 0,並在 commit 或 rollback 結束一個事務後執行 set autocommit = 1。**更多事務的控制語句以下:

START TRANSACTION | BEGIN: 顯式地開啓一個事務;
COMMIT:也可使用 COMMIT WORK,不過兩者是等價的。COMMIT 會提交事務,並使已對數據庫進行的全部修改爲爲永久性的;
ROLLBACK:也可使用 ROLLBACK WORK,不過兩者是等價的。回滾會結束用戶的事務,並撤銷正在進行的全部未提交的修改;
SAVEPOINT identifier:SAVEPOINT 容許在事務中建立一個保存點,一個事務中能夠有多個 SAVEPOINT;
RELEASE SAVEPOINT identifier:刪除一個事務的保存點,當沒有指定的保存點時,執行該語句會拋出一個異常;
ROLLBACK TO identifier:把事務回滾到標記點;
SET TRANSACTION:用來設置事務的隔離級別。
複製代碼

二、事務的隔離級別

Mysql 支持事務最流行的存儲引擎非 InnoDB 莫屬,因此如下的 Mysql 隔離級別設置都是基於 InnoDB 的。

一、查看InnoDB存儲引擎系統級的隔離級別和會話級的隔離級別,命令和結果以下:

mysql> select @@global.tx_isolation,@@tx_isolation;
+-----------------------+-----------------+
| @@global.tx_isolation | @@tx_isolation  |
+-----------------------+-----------------+
| REPEATABLE-READ       | REPEATABLE-READ |
+-----------------------+-----------------+
複製代碼

二、設置InnoDB存儲引擎隔離級別:

語句:

set [ global | session ] transaction isolation level Read uncommitted | Read committed | Repeatable | Serializable;
複製代碼

示例:

mysql> set session  transaction isolation level Serializable;
Query OK, 0 rows affected (0.01 sec)

mysql> select @@global.tx_isolation,@@tx_isolation;
+-----------------------+----------------+
| @@global.tx_isolation | @@tx_isolation |
+-----------------------+----------------+
| REPEATABLE-READ       | SERIALIZABLE   |
+-----------------------+----------------+
1 row in set (0.00 sec)
複製代碼

三、事務的 MVCC 機制

Mysql 的事務型存儲引擎(InnoDB)使用 MVCC(Multi-Version Concurrency Control,多版本併發控制)代替行級鎖來提升併發讀寫的性能。InnoDB 的 MVCC 原理比較簡單,它經過在在每行記錄後面保存三個隱藏列(事務 id,行的建立的版本號、行的過時版本號)來實現的,下面是 InnoDB 在 REPEATABLE READ 隔離級別下 MVCC 的簡化工做原理:

INSERT: InnoDB 爲新插入的每一行保存當前系統版本號做爲行版本號。

UPDATE: InnoDB爲插入一行新記錄,保存當前系統版本號做爲行版本號,同時保存當前系統版本號到原來的行做爲行刪除標識。

DELETE: InnoDB爲刪除的每一行保存當前系統版本號做爲行刪除標識。

SELECT: InnoDB會根據如下兩個條件檢查每行記錄:

  • InnoDB只查找版本早於當前事務版本的數據行(也就是,行的系統版本號小於或等於事務的系統版本號),這樣能夠確保事務讀取的行,要麼是在事務開始前已經存在的,要麼是事務自身插入或者修改過的。
  • 行的刪除版本要麼未定義,要麼大於當前事務版本號。這能夠確保事務讀取到的行,在事務開始以前未被刪除。

只有符合上述兩個條件的記錄,才能返回做爲查詢結果。

下面用更淺顯易懂的例子說明 MVCC 下的 INSERT/DELETE/UPDATE/SELECT 操做: 假如 test 表有兩個字段 name 和 age;MVCC 的三個隱藏列字段名爲 transaction_id、 create_version 和 delete_version。

insert

update

delete

select 知足如下兩個條件的記錄才能被 select 讀取出來:

  • delete_version 未定義或者大於 select 所在事務的 delete_version 的行。
  • create_version 小於或等於 select 所在事務的的 create_version。

經過這個例來看下爲何 MVCC 在 REPEATABLE READ 隔離級別下能解決幻讀。假若有個事務開始於 update 以後 delete 以前,且結束於 delete 以後,以下:

start transaction;  //假如事務 id = 2.5
select * from test; //執行時間在 update 以後 delete 以前
select * from test; //執行時間在 delete 以後
commit;
複製代碼

若是不使用 MVCC 第一條 select * from test 能讀到 1 條記錄,而 第二條將讀取到 0 條記錄,同一事務中屢次 select 範圍查詢讀取到的記錄不一致即幻讀。而使用 MVVC 以後,兩條 select 語句讀取到的記錄相同。

MVCC 只在 REPEATABLE READ 和 READ COMMITTED 兩個隔離級別下工做。其餘兩個隔離級別都和MVCC不兼容,由於 READ UNCOMMITTED 老是讀取最新的數據行,而不是符合當前事務版本的數據行。而 SERIALIZABLE 則會對全部讀取的行都加鎖。

四、事務的實現(redo/undo log)

事務的隔離性經過鎖或 MVCC 機制來實現,而原子性、持久性和一致性經過 redo/undo log 來完成。redo log 稱爲重作日誌,用來保證事務的原子性和持久性。undo log 稱爲撤銷日誌,用來保證事務的一致性。

一、redo log

基本概念

重作日誌用來實現事務的持久性,由如下兩部分組成:

  • 重作日誌緩衝區(redo log buffer),內存中,易丟失。
  • 重作日誌文件(redo log file),磁盤中,持久的。

redo log file 是順序寫入的,在數據庫運行時不須要進行讀取,只會在數據庫啓動的時候讀取來進行數據的恢復工做。 redo log file 是物理日誌,所謂的物理日誌是指日誌中的內容都是直接操做物理頁的命令。重作時是對某個物理頁進行相應的操做。

總體流程

更新事務操做一次數據的流程圖以下所示:

  • 第一步:先將原始數據從磁盤中讀入內存中來,修改數據的內存拷貝。

  • 第二步:生成一條重作日誌並寫入redo log buffer,記錄的是數據被修改後的值。

  • 第三步:在必要的時候,採用追加寫的方式將 redo log buffer 中的內容刷新到 redo log file。

  • 第四步:按期將內存中修改的數據刷新到磁盤中。

以上比較重要的是第三步,其中必要的時候有如下幾種狀況:

  • 事務提交時(最多見的情景,在 commit 以前)
  • 當 log buffer 中有一半的內存空間被使用時
  • log checkpoint 時
  • 實例 shutdown 時
  • binlog切換時
  • 後臺線程

寫入策略

學過 linux 操做系統的都知道內存中數據寫入到磁盤文件中時若是不打開 O_DIRECT 選項。數據是要先寫入文件操做系統緩存區中的,而後再某個時刻 flush 到磁盤。流程以下:

事務提交時將 redo log buffer 寫入 redo log file,爲了保證數據必定能正確同步到磁盤(不只僅只寫到文件緩衝區中)文件中,InndoDB 默認狀況下調用了 fsync 進行寫操做。而 fsync 的性能比較低。固然這只是默認狀況,InnoDB 也提供了參數 innodb_flush_log_at_trx_commit 來配置 redo log 刷新到磁盤的策略,有如下三個值:

  • 當設置該值爲 1 時,每次事務提交都要作一次 fsync,這是最安全的配置,即便宕機也不會丟失事務;
  • 當設置爲 2 時,則在事務提交時只作 write 操做,只保證寫到系統的緩衝區,所以實例crash不會丟失事務,但宕機則可能丟失事務;
  • 當設置爲 0 時,事務提交不會觸發 redo 寫操做,而是留給後臺線程每秒一次的刷盤操做,所以實例 crash 將最多丟失一秒鐘內的事務。

用下圖能夠更直觀的說明 innodb_flush_log_at_trx_commit 不一樣值下的不一樣策略。操做越接近磁盤性能越低,固然可靠性愈來愈高。故性能:1 < 2 < 0,可靠性:0 < 2 < 1。

當 innodb_flush_log_at_trx_commit 設置爲 0 或者 2 時喪失了事務的 ACID 特性,一般在平常環境時將其設置爲 1,而在系統高峯時將其設置爲 2 以應對大負載。

恢復

InnoDB 引擎啓動時無論上次數據庫運行時是否正常關閉,都會嘗試進行恢復操做。整個恢復操做有以下特色:

  • 從 checkpoint 開始的日誌部分進行恢復。
  • 順序讀取及並行應用重作日誌。
  • 重作日誌的應用具備冪等性
  • 重作日誌是物理日誌,恢復的速度相對較快。
二、undo log

基本概念

  • undo log 用來實現事務的一致性,InndoDB 能夠經過 redo log 對頁進行重作操做。可是有時候事務須要進行回滾,這時就須要 undo log。
  • undo log 還可能夠用來協助 InnoDB 引擎實現 MVCC 機制。
  • undo log 是邏輯日誌,恢復時並非對物理頁直接進行恢復,而是邏輯地將數據庫恢復到原來的樣子。
  • undo log 的產生也會伴隨着 redo log 的產生。

寫入時機

事務開始以前,流程如圖:

日誌格式

在 InnoDB 中 undo log 分爲 insert undo log 和 update undo log

insert undo log

insert 操做產生的日誌。根據隔離性,insert 插入的記錄只對本事務可見,因此事務提交後能夠刪除因 insert 產生的日誌。

update undo log

delete 和 update 操做產生的日誌。根據前面的 MVCC 機制能夠知道此部分記錄還有可能要被其餘事務所使用,因此即便事務提交也不能刪除相應的日誌。在事務提交時會被保存到 undo log 鏈表,在 purge 線程中作最後的刪除。

三、redo 與 undo log 記錄過程

undo 記錄更新以前的日誌,爲了回滾。 redo 記錄更新以後的日誌,爲了重作。 redo log 與 undo log 產生過程的簡化版本以下,能夠更方便的理解 redo 與 undo 的區別。

假設有A,B兩個數據,原值分別爲 1, 2;現將A更新爲10,B 更新爲 20;undo log記錄信息過程以下:
1. 事務開始
2. 記錄 A = 1 到 undo log
3. 更新 A = 10
4. 記錄 A = 10 到 redo log
5. 記錄 B = 2 到 undo log
6. 更新 B = 20
7. 記錄 B = 20 到 redo log
8. redo log 信息寫入磁盤
9. 提交事務
複製代碼

總結

比起索引事務或許並不那麼耀眼,但事務也是數據庫中比較重要的一部分。不少時候咱們只是簡單的使用一些命令來進行事務操做,甚至在 Spring 框架中只須要一個註解便可搞定。但對其背後的原理並不必定理解,本篇文章對最經常使用的數據庫 Mysql 的事務作了一次全面的總結,但願能夠幫助你們更系統的理解事務。這樣不管是在和同事討論時、應付面試時均可以侃侃而談。

參考