MySQL事物原理及事務隔離級別

mysql事物mysql

事務是訪問數據庫的一個操做序列,數據庫應用系統經過事務集來完成對數據庫的存取。事務的正確執行使得數據庫從一種狀態轉換爲另外一種狀態程序員

事務必須服從ISO/IEC所制定的ACID原則。ACID是原子性(atomicity)、一致性(consistency)、隔離性(isolation)、持久性(durability)的縮寫,這四種狀態的意思是:sql

一、原子性數據庫

即不可分割,事務要麼所有被執行,要麼所有不執行。若是事務的全部子事務所有提交成功,則全部的數據庫操做被提交,數據庫狀態發生變化;若是有子事務失敗,則其餘子事務的數據庫操做被回滾,即數據庫回到事務執行前的狀態,不會發生狀態轉換緩存

二、一致性服務器

事務的執行使得數據庫從一種正確狀態轉換成另一種正確狀態架構

三、隔離性併發

在事務正確提交以前,不容許把事務對該數據的改變提供給任何其餘事務,即在事務正確提交以前,它可能的結果不該該顯示給其餘事務性能

四、持久性atom

事務正確提交以後,其結果將永遠保存在數據庫之中,即便在事務提交以後有了其餘故障,事務的處理結果也會獲得保存

ACID 實現原理

首先介紹一下 MySQL 的事務日誌。MySQL 的日誌有不少種,如二進制日誌、錯誤日誌、查詢日誌、慢查詢日誌等。

此外 InnoDB 存儲引擎還提供了兩種事務日誌:

redo log(重作日誌)

undo log(回滾日誌)

其中 redo log 用於保證事務持久性;undo log 則是事務原子性和隔離性實現的基礎。

原子性:

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 和 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 中修改的數據寫入磁盤(即刷髒)要快呢?

主要有如下兩方面的緣由:

刷髒是隨機 IO,由於每次修改的數據位置隨機,但寫 redo log 是追加操做,屬於順序 IO。刷髒是以數據頁(Page)爲單位的,MySQL 默認頁大小是 16KB,一個 Page 上一個小修改都要整頁寫入;而 redo log 中只包含真正須要寫入的部分,無效 IO 大大減小。redo log 與 binlog

咱們知道,在 MySQL 中還存在 binlog(二進制日誌)也能夠記錄寫操做並用於數據的恢復,但兩者是有着根本的不一樣的。

做用不一樣:

redo log 是用於 crash recovery 的,保證 MySQL 宕機也不會影響持久性;binlog 是用於 point-in-time recovery 的,保證服務器能夠基於時間點恢復數據,此外 binlog 還用於主從複製。層次不一樣:

redo log 是 InnoDB 存儲引擎實現的,而 binlog 是 MySQL 的服務器層(能夠參考文章前面對 MySQL 邏輯架構的介紹)實現的,同時支持 InnoDB 和其餘存儲引擎。內容不一樣:

redo log 是物理日誌,內容基於磁盤的 Page。binlog 是邏輯日誌,內容是一條條 sql。寫入時機不一樣:

redo log 的寫入時機相對多元。前面曾提到,當事務提交時會調用 fsync 對 redo log 進行刷盤;這是默認狀況下的策略,修改 innodb_flush_log_at_trx_commit 參數能夠改變該策略,但事務的持久性將沒法保證。除了事務提交時,還有其餘刷盤時機:如 master thread 每秒刷盤一次 redo log 等,這樣的好處是不必定要等到 commit 時刷盤,commit 速度大大加快。

binlog 在事務提交時寫入。

隔離性:

與原子性、持久性側重於研究事務自己不一樣,隔離性研究的是不一樣事務之間的相互影響。

隔離性是指事務內部的操做與其餘事務是隔離的,併發執行的各個事務之間不能互相干擾。

嚴格的隔離性,對應了事務隔離級別中的 Serializable(可串行化),但實際應用中出於性能方面的考慮不多會使用可串行化。

隔離性追求的是併發情形下事務之間互不干擾。簡單起見,咱們僅考慮最簡單的讀操做和寫操做(暫時不考慮帶鎖讀等特殊操做)。

那麼隔離性的探討,主要能夠分爲兩個方面:

(一個事務)寫操做對(另外一個事務)寫操做的影響:鎖機制保證隔離性。

(一個事務)寫操做對(另外一個事務)讀操做的影響:MVCC 保證隔離性。

事務的做用

事務管理對於企業級應用而言相當重要,它保證了用戶的每一次操做都是可靠的,即使出現了異常的訪問狀況,也不至於破壞後臺數據的完整性。就像銀行的自動提款機ATM,一般ATM均可以正常爲客戶服務,可是也不免遇到操做過程當中及其忽然出故障的狀況,此時,事務就必須確保出故障前對帳戶的操做不生效,就像用戶剛纔徹底沒有使用過ATM機同樣,以保證用戶和銀行的利益都不受損失。

事務隔離級別

數據庫事務的隔離級別有4種,由低到高分別爲Read uncommitted 、Read committed 、Repeatable read 、Serializable 。並且,在事務的併發操做中可能會出現髒讀,不可重複讀,幻讀。下面經過事例一一闡述它們的概念與聯繫。

Read uncommitted

讀未提交,顧名思義,就是一個事務能夠讀取另外一個未提交事務的數據。

事例:老闆要給程序員發工資,程序員的工資是3.6萬/月。可是發工資時老闆不當心按錯了數字,按成3.9萬/月,該錢已經打到程序員的戶口,可是事務尚未提交,就在這時,程序員去查看本身這個月的工資,發現比往常多了3千元,覺得漲工資了很是高興。可是老闆及時發現了不對,立刻回滾差點就提交了的事務,將數字改爲3.6萬再提交。

分析:實際程序員這個月的工資仍是3.6萬,可是程序員看到的是3.9萬。他看到的是老闆還沒提交事務時的數據。這就是髒讀。


那怎麼解決髒讀呢?Read committed!讀提交,能解決髒讀問題。


Read committed

讀提交,顧名思義,就是一個事務要等另外一個事務提交後才能讀取數據。

事例:程序員拿着信用卡去享受生活(卡里固然是隻有3.6萬),當他埋單時(程序員事務開啓),收費系統事先檢測到他的卡里有3.6萬,就在這個時候!!程序員的妻子要把錢所有轉出充當家用,並提交。當收費系統準備扣款時,再檢測卡里的金額,發現已經沒錢了(第二次檢測金額固然要等待妻子轉出金額事務提交完)。程序員就會很鬱悶,明明卡里是有錢的…

分析:這就是讀提交,如有事務對數據進行更新(UPDATE)操做時,讀操做事務要等待這個更新操做事務提交後才能讀取數據,能夠解決髒讀問題。但在這個事例中,出現了一個事務範圍內兩個相同的查詢卻返回了不一樣數據,這就是不可重複讀。


那怎麼解決可能的不可重複讀問題?Repeatable read !


Repeatable read

重複讀,就是在開始讀取數據(事務開啓)時,再也不容許修改操做

事例:程序員拿着信用卡去享受生活(卡里固然是隻有3.6萬),當他埋單時(事務開啓,不容許其餘事務的UPDATE修改操做),收費系統事先檢測到他的卡里有3.6萬。這個時候他的妻子不能轉出金額了。接下來收費系統就能夠扣款了。

分析:重複讀能夠解決不可重複讀問題。寫到這裏,應該明白的一點就是,不可重複讀對應的是修改,即UPDATE操做。可是可能還會有幻讀問題。由於幻讀問題對應的是插入INSERT操做,而不是UPDATE操做。


何時會出現幻讀?

事例:程序員某一天去消費,花了2千元,而後他的妻子去查看他今天的消費記錄(全表掃描FTS,妻子事務開啓),看到確實是花了2千元,就在這個時候,程序員花了1萬買了一部電腦,即新增INSERT了一條消費記錄,並提交。當妻子打印程序員的消費記錄清單時(妻子事務提交),發現花了1.2萬元,彷佛出現了幻覺,這就是幻讀。


那怎麼解決幻讀問題?Serializable!


Serializable 序列化

Serializable 是最高的事務隔離級別,在該級別下,事務串行化順序執行,能夠避免髒讀、不可重複讀與幻讀。可是這種事務隔離級別效率低下,比較耗數據庫性能,通常不使用。


值得一提的是:大多數數據庫默認的事務隔離級別是Read committed,好比Sql Server , Oracle。Mysql的默認隔離級別是Repeatable read。

 

 

tips:

在事務中,每一個正確的原子操做都會被順序執行,直到遇到錯誤的原子操做,此時事務會將以前的操做進行回滾。回滾的意思是若是以前是插入操做,那麼會執行刪 除插入的記錄,若是以前是update操做,也會執行update操做將以前的記錄還原

所以,正確的原子操做是真正被執行過的。是物理執行。

在當前事務中確實能看到插入的記錄。最後只不過刪除了。可是AUTO_INCREMENT不會應刪除而改變值。

爲何auto_increament沒有回滾?

由於innodb的auto_increament的計數器記錄的當前值是保存在存內存中的,並非存在於磁盤上,當mysql server處於運行的時候,這個計數值只會隨着insert改增加,不會隨着delete而減小。而當mysql server啓動時,當咱們須要去查詢auto_increment計數值時,mysql便會自動執行:SELECT MAX(id) FROM 表名 FOR UPDATE;語句來得到當前auto_increment列的最大值,而後將這個值放到auto_increment計數器中。因此就算 Rollback MySQL的auto_increament計數器也不會做負運算。

相關文章
相關標籤/搜索