MySQL 學習筆記(一)MySQL 事務的ACID特性

Ethan_Wong發表於2022-03-05

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 機制來解決。

四、一致性

一致性的定義與實現

一致性的實現就是在前面三個特性實現的基礎上而來的,沒有前面三個特性的實現,也就達不到最後資料庫事務的一致性。

參考資料

https://time.geekbang.org/column/article/68963

https://www.cnblogs.com/kismetv/p/10331633.html

相關文章