Mysql 事務及其原理


Mysql 事務及其原理

什么是事務

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

事務的 ACID 屬性

原子性(Atomicity):作為邏輯工作單元,一個事務里的所有操作的執行,要么全部成功,要么全部失敗。

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

隔離性(Isolation):通常來說,一個事務所做的修改在最終提交前,對其他事務是不可見的。為什么是通常來說,為了提高事務的並發引出不同的隔離級別,具體參考下一章節。

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

事務的隔離級別

為了盡可能的高並發,事務的隔離性被分為四個級別:讀未提交、讀已提交、可重復讀和串行化。用戶可以根據需要選擇不同的級別。

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

例:事務 A 可以讀到事務 B 修改的但還未提交的數據,會導致臟讀(可能事務 B 在提交后失敗了,事務 A 讀到的數據是臟的)。

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

例:事務 A 只能讀到事務 B 修改並提交后的數據,會導致不可重復讀(事務 A 中執行兩次查詢,一次在事務 B 提交過程中,一次在事務 B 提交之后,會導致兩次讀取的結果不一致)。

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

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

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

 

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

 

Mysql 中的事務

1、事務的自動提交

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; 
--以上命令可以打開和關閉自動提交

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

2、關閉自動提交后,用戶的所有 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:用來設置事務的隔離級別。

2、事務的隔離級別

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

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

mysql> select @@global.tx_isolation,@@tx_isolation;
+-----------------------+-----------------+
| @@global.tx_isolation | @@tx_isolation  |
+-----------------------+-----------------+
| REPEATABLE-READ       | REPEATABLE-READ |
+-----------------------+-----------------+

2、設置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)

3、事務的 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 則會對所有讀取的行都加鎖。

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

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

1、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 開始的日志部分進行恢復。
  • 順序讀取及並行應用重做日志。
  • 重做日志的應用具有冪等性。
  • 重做日志是物理日志,恢復的速度相對較快。
2、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 線程中做最后的刪除。

3、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. 提交事務


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM