MySQL 事務(4)

什麼是事務?

數據庫事務( transaction)是訪問並可能操做各類數據項的一個數據庫操做序列,這些操做要麼所有執行,要麼所有不執行,是一個不可分割的工做單位。事務由事務開始與事務結束之間執行的所有數據庫操做組成。sql

這裏有兩個關鍵點,第一,它是數據庫最小的工做單元,是不能夠再分的。第二,它可能包含了一個或一系列DML語句,包括insert delete update。(單條DDL(create drop) 和DCL(grant revoke)也會有事務)數據庫

事務的四大特性(ACID)

特性 說明
原子性(Atomicity) 事務中的所有操做在數據庫中是不可分割的,要麼所有完成,要麼所有不執行。
一致性(Consistent) 幾個並行執行的事務,其執行結果必須與按某一順序 串行執行的結果相一致。
隔離性(Isolation) 事務的執行不受其餘事務的干擾,事務執行的中間結果對其餘事務必須是透明的。
持久性(Durability) 對於任意已提交事務,系統必須保證該事務對數據庫的改變不被丟失,即便數據庫出現故障。
原子性(Atomicity)

以轉帳爲例,一個帳戶的餘額減小,對應一個帳戶增長這兩個必定是同時成功或同時失敗。服務器

所有成功好理解,問題是若是前一個操做已經成功,後一個操做失敗了,如何讓他所有失敗呢?這時候咱們必需要回滾。併發

原子性,在InnoDB裏面是經過undo log來實現的,它記錄了數據以前的值(邏輯日誌),一旦發生異常,就能夠用undo log來實現回滾操做。指針

一致性(Consistent)

還以轉帳爲例,A帳戶餘額有500元,這時有兩個轉帳請求過來,都要轉帳500元,若是兩個請求都執行成功,A帳戶的餘額就成-500元,但這是錯誤的,儲蓄卡的餘額是不能小於0的,因此違反了一致性。完整性一般須要用戶在代碼中控制。日誌

隔離性(Isolation)

有了事務的定義之後,在數據庫裏面會有不少的事務同時去操做咱們的同一張表或者同一行數據,必然會產生一些併發或者干擾的操做,那麼咱們對隔離性的定義,就是這些不少個的事務,對錶或者行的併發操做,應該是透明的,互相不干擾的。經過這種方式,咱們最終也是保證業務數據的一致性。code

像一致性的轉帳例子,應該是第一個事務轉帳成功,第二個事務提示餘額不足。blog

持久性(Durability)

事務的持久性是什麼意思呢?咱們對數據庫的任意的操做,增刪改,只要事務提交成功,那麼結果就是永久性的,不可能由於咱們系統宕機或者重啓了數據庫的服務器,它又恢復到原來的狀態了。這個就是事務的持久性。能夠理解爲數據已被寫入到存儲介質上。索引

持久性怎麼實現呢?數據庫崩潰恢復(crash-safe)是經過什麼實現的?事務

持久性是經過redo log和double write雙寫緩衝來實現的,咱們操做數據的時候,會先寫到內存的buffer pool裏面,同時記錄redo log,若是在刷盤以前出現異常,在重啓後就能夠讀取redo log的內容,寫入到磁盤,保證數據的持久性。

固然,恢復成功的前提是數據頁自己沒有被破壞,是完整的,這個經過雙寫緩衝(double write)保證。

原子性,隔離性,持久性,最後都是爲了實現一致性。

事務併發會遇到哪些問題?

當不少事務併發操做數據庫的表或行時,若是沒有咱們剛纔講的事務隔離性時,會帶來哪些問題?

如上圖有兩個事務,事務A先查詢id=1的這行數據,以後事務B修改age=18,但未提交,此時事務A再次查詢id=1的數據,這行數據age變成了18。這種在一個事務裏,因爲其餘事務修改了數據並無提交,而致使了先後兩次讀取數據不一致的狀況,這種事務併發問題,咱們將之定義爲髒讀

若是在轉帳案例裏面,咱們第一個事務讀取到第二個事務未提交的餘額進行了操做,但第二個事務進行了回滾,這個時候會致使數據不一致。

這種讀取其餘事務未提交的數據狀況,咱們叫作髒讀

一樣兩個事務,A事務經過id=1查到一條數據。而後在第二個事務裏執行一個update操做,而且修改了提交。而後A事務繼續查詢id=1的數據,此時A事務的先後兩次查詢數據不一致,像這種age到底等於16仍是18,那麼這種事務併發帶來的問題,咱們定義爲不可重複讀

這種一個事務讀取到了其餘事務已提交的數據致使先後兩次讀取數據不一致的狀況,咱們把它叫作不可重複讀。

在上圖中,咱們在A事務中執行了一個範圍查詢,這個時候知足條件的數據只有一條。在B事務裏面插入了一行新數據,而且提交了。此時在A事務再次查詢時,會發現多了一條數據。這種狀況咱們定義爲幻讀

不可重複讀和幻讀的區別在哪裏?

不可重複讀是修改或者刪除,幻讀是插入。

小結:上邊說的三大問題。不管是髒讀、不可重複讀、幻讀,都是數據庫的讀一致性問題,都是在一個事務先後兩次讀取出現了不一致的狀況。

讀一致性的問題,必需要由數據庫提供必定的事務隔離機制來解決。就像咱們去飯店吃飯,基本的設施和衛生保證都是飯店提供的。那麼咱們使用數據庫,隔離性的問題也必須由數據庫幫助咱們來解決。

MySql InnoDB 事務隔離級別

在MySQL InnoDB裏面,不須要使用串行化的隔離級別去解決全部問題。咱們來看一下MySQL InnoDB裏面對數據庫事務隔離級別的支持程度是什麼樣的。

事務隔離級別 髒讀 不可重複讀 幻讀
未提交讀(Read Uncommitted) 可能 可能 可能
已提交讀(Read Committed) 不可能 可能 可能
可重複讀(Repeatable Read) 不可能 不可能 對InnoDB不可能
串行化(Serializable) 不可能 不可能 不可能

InnoDB支持的四個隔離級別和SQL92定義的基本一致,隔離級別越高,事務的併發度就越低。惟一的區別就在於,InnoDB在RR的級別就解決了幻讀的問題。這個也是InnoDB默認使用RR做爲事務隔離級別的緣由,既保證了數據的一致性,又支持較高的併發度。

實現方案

若是要解決讀一致性的問題,保證一個事務中先後兩次讀取數據結果一致,實現事務隔離,你會怎麼作?

1、基於鎖的併發控制LBCC(Lock Based Concurrency Control)

既然要保證先後兩次讀取數據一致,那麼我讀取數據的時候,鎖定我要操做的數據,不容許其餘事務修改就好了。這種方案咱們叫作基於鎖的併發控制

若是僅僅是基於鎖來實現事務隔離,一個事務讀取的時候不容許其餘時候修改,那就意味着不支持併發的讀寫操做,而大多數應用都是讀多寫少的,這樣會極大地影響操做數據的效率。

2、多版本的併發控制 MVCC(Multi Version Concurrency Control)

若是要讓一個事務先後兩次讀取的數據保持一致,那麼咱們能夠在修改數據的時候給他創建一個備份或者叫快照,後面再來讀取這個快照就好了。這種方案咱們叫作多版本的併發控制

MVCC的核心思想是:我能夠查到在我這個事務開始以前已經存在的數據,即便它在後面被修改或者刪除了。在我這個事務以後新增的數據,我是查不到的。

那麼這個快照何時建立?讀取數據的時候,怎麼保證能讀取到這個快照而不是最新的數據?該如何實現?

InnoDB爲每行記錄都實現了4個隱藏字段:

字段名 長度 說明
DB_ROW_ID 6字節 行標識
DB_TRX_ID 6字節 插入或更新行的最後一個事務的事務ID,事務編號是自動遞增的(能夠理解爲建立版本號,在數據新增或者修改成新數據的時候,記錄當前事務ID)。
DB_ROLL_PTR 7字節 回滾指針(能夠理解爲刪除版本號,數據被刪除或記錄爲舊數據的時候,記錄當前事務ID)。
DELETE_FLAG 刪除標記

經過以上演示,經過事務ID的控制,不管其餘事務是插入、修改、刪除,第一個事務查詢到的數據都沒有變化。

能夠將各事務中的查詢邏輯按如下條件理解

select * from user_innoDB 
where DB_TRX_ID <= 2 or DB_ROLL_PTR <= 2;

隔離級別

事務隔離級別 髒讀 不可重複讀 幻讀
未提交讀(Read Uncommitted) 可能 可能 可能
已提交讀(Read Committed) 不可能 可能 可能
可重複讀(Repeatable Read) 不可能 不可能 對InnoDB不可能
串行化(Serializable) 不可能 不可能 不可能
Read Uncommitted (RU)

RU隔離級別:不加鎖。

Serializable

Serializable全部select語句都會被隱式的轉化爲select ... in share mode ,會和update、delete互斥。

Repeatable Read(RR)

RR隔離級別下,普通的select 使用快照讀(snapshot read),底層使用MVCC來實現。

加鎖的select 以及更新操做update、delete等語句使用當前讀(current read),底層使用記錄鎖、間隙鎖、臨鍵鎖

Read Committed(RC)

RC隔離級別下,普通的select 都是快照讀,使用MVCC實現。

加鎖的select 都使用記錄鎖,由於沒有Gap Lock。

除了兩種特殊狀況

  1. 外檢約束檢查(foreign-key constraint checking)
  2. 重複鍵檢查(duplicate-key checking)

以上兩種會使用間隙鎖封鎖區間。因此RC會出現幻讀的問題。

事務隔離級別如何選擇?

RU和Serializable確定不能用。爲何有些公司要用RC?

RC和RR的主要有幾個區別:

  1. RR的間隙鎖會致使鎖定範圍的擴大。
  2. 條件列未使用到索引,RR鎖表,RC鎖行。
  3. RC的「半一致性」(semi-consistent)讀能夠增長update操做的併發性。

在RC中,一個update語句,若是讀到一行已經加鎖記錄,此時InnoDB返回記錄最近提交的版本,由MySQL上層判斷此版本是否知足update的where條件。若知足(須要更新),則MySQL會從新發起一次讀操做,此時會讀取行的最新版本(並加鎖)。

實際上,若是可以正確地使用鎖(避免不使用索引加鎖),只鎖定須要的數據,用默認的RR級別就能夠。

相關文章
相關標籤/搜索