MySQL事務是什么,它就是一組數據庫的操作,是訪問數據庫的程序單元,事務中可能包含一個或者多個 SQL 語句。這些SQL 語句要么都執行、要么都不執行。我們知道,在MySQL 中,有不同的存儲引擎,有的存儲引擎比如MyISAM 是不支持事務的,所以說MySQL 事務實際上是發生在 存儲引擎部分。
事務主要有四大特性,分別是原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)和 持久性(Durability)。它實際上是從四個方面來闡述MySQL 事務的特點,下面就分別來看MySQL 通過什么方式來實現這些特性。
一、原子性
1. 原子性定義
原子性就是指事務的不可分割性,對於一個事務而言,就是要么都執行,要么都不執行。在MySQL 中是通過回滾來實現,比如事務中的一個 SQL 語句失敗了,那么該事務的所有SQL 語句必須都進行回滾,退回到事務前的狀態。
2. InnoDB 中原子性的實現
上面說到,MySQL 中原子性是通過回滾的方式來實現,那么回滾是怎么實現的?這就涉及到MySQL 中的Undo 日志,原子性就是通過 Undo log 來實現的。
具體是Undo log 會在一個事務中,記錄當前 SQL 語句的上一個語句成功的執行狀態,如果在執行當前 SQL 語句失敗后,就可以通過 Undo log 來回滾到 SQL 語句執行前的狀態,這樣就能保證事務的原子操作。舉個例子,比如插入一條記錄:insert into test values(1,'劉備','蜀')
實際上得到的記錄圖如下,中間的 roll_pointer 是指向undo log的指針。
undo log 在事務提交后,undo log 日志也就會被回收。
二、持久性
1.持久性定義
持久性是指事務一旦提交,它對事務的改變是永久性的,哪怕系統發生了故障,也不會改變其提交的結果。持久性是通過 Redo log 來實現的。
2.InnoDB 中持久性的實現
在講持久性之前,先介紹一下MySQL 中 Buffer pool,我們知道MySQL 數據是存儲在磁盤中,為了實現快速讀寫數據,我們會在內存中設置一個 Buffer pool 緩沖池,數據庫可以直接與 Buffer Pool 進行讀取交互,定期再將 Buffer Pool 數據存儲到磁盤中,這樣會大大提高數據庫的讀寫效率。
但是如果系統斷電或者宕機,內存是無法保存信息的,而此時剛好Buffer Pool 數據沒有同步到磁盤上,就會造成數據丟失。因此就需要 redo log 來對更新和修改操作進行記錄,使得在系統重啟時能夠恢復到原來的狀態。
Redo log 是一種預寫式日志(write-Ahead Log),它記錄的是在某個數據頁上做了什么修改。當有記錄需要更新時,InnoDB 引擎會先把記錄寫到 redo log 中,在系統空閑時,再將操作記錄更新到磁盤中。redo log 結構如下圖所示:
- write pos 是當前記錄的位置
- checkpoint 當前要擦除的位置
- write pos 和 check point 之間是 redo 內存區域中還空着的部分,用於記錄新的操作。
redo log 只需要記錄真正修改的部分,它的同步效率要比 buffer 同步數據快的多。那么 redo log 何時會同步到磁盤中去,主要是 innodb_flush_log_at_trx_commit
這個參數的設置:
- 0:表示當提交事務時,並不將緩沖區的 redo log 寫入磁盤的日志文件,而是等待主線程每秒刷新
- 1:在事務提交時將緩沖區的 redo log 同步寫到磁盤中,保證一定會寫入成功
- 2:在事務提交時將緩沖區的redo 日志異步寫入到磁盤中,即不能完全保證 commit 時肯定會寫入到 redo 日志文件,只是有這個動作。
建議這個參數設置為1 ,同步寫入磁盤中。
3.Binlog 和 Redo log
binlog 和 redo log 日志的區別
我們知道 redo log 是InnoDB 存儲引擎的事務日志,那么對於 server 層是否也存在事務日志,答案是確定的,server 層的事務日志就是 binlog (歸檔日志)。為啥會出現兩種事務日志,是因為最開始的 MySQL 中並沒有 InnoDB 引擎,MySQL 自帶的引擎是 MyISAM ,用的就是 binlog 日志來實現事務。那么兩者具體有什么區別呢:
- redo log 是InnoDB 引擎特有的,binlog 是 server 層實現,所有的存儲引擎都可以使用
- redo log 是物理日志,它存儲的是在數據頁上的修改;binlog 是邏輯日志,存儲的是sql 語句的原始邏輯
- redo log 空間是固定的,會使用完並覆蓋原來的日志。binlog 可以追加寫入,不會覆蓋原來的日志
binlog 和 redo log 日志的兩階段提交
既然在MySQL 中存在兩種日志,那么為了讓兩份日志之間的邏輯一致,就需要兩階段提交來實現這一任務。具體怎么實現的,我們以這個語句update T set c=c+1 where ID = 2
來看:
- 1.執行器先通過執行引擎查找 ID=2 這一行,如果數據在內存Buffer Pool 中直接返回。如果在磁盤中,則先從磁盤中讀取到內存中,然后再返回。
- 2.對取到的數據進行操作,將值加1后得到新的數據,再調用引擎寫入數據,更新內存。
- 3.同時將對數據頁的修改記錄到 redo log 中,這個時候 redo log 處於第一個階段 prepare。
- 4.執行器生成對於這個操作的 binlog ,並將 binlog 寫入磁盤中
- 5.執行器調用提交事務接口,把剛剛寫入的 redo log 修改成 commit 狀態,更新到此完成。
三、隔離性
1.隔離性定義
隔離性是指事務內部的操作與其他事務是隔離的,並發過程中的各個事務之間不能互相干擾。對於事務的操作,主要分成兩種:讀操作與寫操作之間的影響、寫操作與寫操作之間的影響。
2.隔離性的實現
上面我們說到了事務之間的影響主要分成兩個方面,那么MySQL 中是如何處理這兩種情況的呢?
- 寫操作與寫操作:就像 java 中的鎖一樣,通過鎖來解決(MySQL 鎖后續會出一篇文章詳細介紹)
- 幻讀
- 寫操作與讀操作:主要是通過 MVCC 機制來解決(MVCC 后續會出一篇文章進行介紹)
- 臟讀
- 不可重復讀
寫操作與寫操作的隔離實現
我們可以通過鎖的方式,來保證同一時刻的一個數據的寫操作只能被一個事務所執行。
在MySQL 中,根據加鎖范圍,大致可以分成三類:全局鎖、表級鎖和行級鎖。 在一個事務修改數據前,需要獲取對應的鎖才能修改對應的數據。其他事務想要修改該數據,必須要等到之前的事務提交或回滾釋放鎖后,才能搶這個鎖來修改數據。
鎖的概況可以通過以下語句進行查詢:
# 鎖的概況
select * from information_schema.innodb_locks;
# InnoDB 整體狀態,也包括鎖的情況
show engine innodb status
寫操作與讀操作的隔離實現
為了保證性能,我們不能把所有操作都進行上鎖,對於寫操作和讀操作,可以使用不加鎖的方式來實現事務隔離。主要就是通過MySQL 中的 MVCC 機制來解決。
四、一致性
一致性的定義與實現
一致性的實現就是在前面三個特性實現的基礎上而來的,沒有前面三個特性的實現,也就達不到最后數據庫事務的一致性。