目錄
内容概覽
事務是訪問并更新數據庫中各種數據項的一個程序執行單元。在事務中的操作,要麼都執行修改,要麼都不執行,這就是事務的目的,也是事務模型區别于文件系統的重要特征之一。
MySQL Server系統結構包括三層:SQL層、存儲引擎層和物理文件,事務操作是在存儲引擎層完成的。
MySQL Server系統結構
MySQL數據庫支持多種存儲引擎,有InnoDB,MyISAM,NDB,Memory等,并不是所有存儲引擎都支持事務操作。
本文介紹InnoDB存儲引擎對事務的支持。《MySQL技術内幕》對InnoDB存儲引擎的介紹:“InnoDB通過使用多版本并發控制(MVCC)來獲得高并發性,并且實現了SQL标準的4種隔離級别,默認為REPEATABLE(可重複讀)級别,同時使用一種稱為netx-key locking的策略來避免幻讀(phantom)現象的産生。”
一、事務特性(ACID)1)原子性 Atomicity事務是一個原子操作,事務中的操作要麼都執行,要麼都不執行,任何一個SQL語句執行失敗 ,那麼執行成功的SQL也必須撤銷,數據庫狀态應該退回到執行事務前的狀态。
2)一緻性 Consistency一緻性指事務操作前和操作後都必須滿足業務規則約束,數據庫的完整性約束沒有被破壞。
3)隔離性 Isolation隔離性也稱為并發控制(concurrency control)、可串行化(serializability)、鎖(locking)。多個事務之間的操作是相互隔離的,即該事務提交前對其他事務都不可見,通常使用鎖來實現。
4)持久性 Durability事務一旦提交,其結果是永久性的,即使發生宕機等故障,數據庫也能将數據恢複。需要注意的是,持久性隻能從事務本身的角度來保證結果的永久性,如果不是數據庫本身發生故障,而是一些外部的原因,如RAID卡損壞、自然災害等導緻數據庫發生問題,那麼所有提交的數據可能會丢失。
二、事務并發問題由于對數據庫的操作不同,事務之間并不是順序執行,而是并發執行的,事務并發可能會帶來如下問題:
1)更新丢失 (Lost Update)當多個事務操作同一行數據,由于每個事務都不知道其他事務的存在,更新數據時,一個事務的結果可能會被其他事務覆蓋,發生丢失更新的問題。
2)髒讀(Dirty Reads)如果一個事務對數據進行了更新,但事務還沒有提交,另一個事務讀到了該事務沒有提交的數據,那麼如果第一個事務回滾,第二個事務讀到的數據就是髒數據。
3)不可重複讀(Non-Repeatable Reads)——針對同一數據指在一個事務内兩次讀到的數據是不一樣的。比如事務A讀取某一數據,事務B修改了該數據,事務A為了對數據進行驗證而再次讀取該數據,便得到了不同的結果。
一種更易理解的說法是:在一個事務内,多次讀同一個數據。在這個事務還沒有結束時,另一個事務也訪問同一數據并修改數據。那麼,在第一個事務的兩次讀數據之間,由于另一個事務的修改,第一個事務兩次讀到的數據可能不一樣,這樣就發生了在一個事務内兩次讀到的數據是不一樣的,因此稱為不可重複讀,即原始讀取不可重複。
4)幻讀(Phantom Reads) ——針對多條數據幻讀是指同樣一個查詢在整個事務過程中多次執行後,查詢的結果集是不一樣的,也就是事務中讀取到了其他事務新增的數據,仿佛出現了幻象。不符合事務的隔離性,幻讀針對的是多條記錄。
三、事務隔離級别并發事務帶來的“更新丢失”問題,通常是可以完全避免的。防止更新丢失,并不能單靠數據庫事務控制器來解決,需要應用程序對要更新的數據加必要的鎖來解決。
“髒讀”、“不可重複讀”和“幻讀”,其實都是數據庫讀一緻性問題,必須由數據庫提供一定的事務隔離機制來解決。
為了解決“隔離”與“并發”的矛盾,ANSI SQL标準定義了 4個事務隔離級别,分别是:
READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ、SERIALIZABLE。
1)未提交讀(READ UNCOMMITTED)在一個事務中,可以讀取到其他事務未提交的數據變化,這種隔離級别會造成髒讀、不可重複讀、幻讀的問題。
2)已提交讀(READ COMMITTED)在一個事務中,可以讀取到其他事務已經提交的數據變化,這種隔離級别會造成不可重複讀、幻讀的問題。
例如事務A讀到了數據a, 事務B正好對數據a進行了更改,并且提交了。那麼事務A再次讀數據a時發現數據已改變。兩次同樣的查詢可能會得到不一樣的結果。
3)可重複讀(REPEATABLE READ)在一個事務中,直到事務結束前,都可以反複讀取到事務剛開始時看到的數據,并一直不會發生變化,避免了髒讀、不可重複讀問題,但無法解決幻讀問題。
4)可串行化(SERIALIZABLE)這是最高的隔離級别,它強制事務串行執行,不會出現髒讀、不可重複讀、幻讀問題。
5)隔離級别總覽
隔離級别 |
數據一緻性 |
髒讀 |
不可重複讀 |
幻讀 |
未提交讀 |
最低級别 |
是 |
是 |
是 |
已提交讀 |
語句級别 |
否 |
是 |
是 |
可重複讀 |
事務級别 |
否 |
否 |
是 |
可序列化 |
最高級别,事務級别 |
否 |
否 |
否 |
1)InnoDB存儲引擎使用MVCC實現了REPEATABLE READ(可重複讀),它的默認隔離級别也是REPEATABLE READ。
2)InnoDB存儲引擎在REPEATABLE READ事務隔離級别下,使用Next-Key Lock的鎖算法,避免了幻讀的産生。InnoDB存儲引擎已經能完全保證事務的隔離性要求,即達到SQL标準的SERIALIZABLE隔離級别。
實現事務隔離的方式,基本上可分為以下兩種。
- 一種是在讀取數據前,對其加鎖,阻止其他事務對數據進行修改。
- 另一種是不用加任何鎖,通過一定機制生成一個數據請求時間點的數據快照(Snapshot),并用這個快照來提供一定級别(語句級或事務級)的一緻性讀取。從用戶的角度來看,好像是數據庫可以提供同一數據的多個版本,因此,這種技術叫做數據多版本并發控制(MultiVersion Concurrency Control,簡稱MVCC或MCC),也經常稱為多版本數據庫。
下面分别介紹一下InnoDB存儲引擎的鎖和MVCC。
五、InnoDB鎖1)鎖的介紹MySQL 不同的存儲引擎支持不同的鎖機制,有3種粒度的鎖:
- 表級鎖:開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖沖突的概率最高,并發度最低。
- 行級鎖:開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖沖突的概率最低,并發度也最高。
- 頁面鎖:開銷和加鎖時間界于表鎖和行鎖之間;會出現死鎖;鎖定粒度界于表鎖和行鎖之間,并發度一般。
InnoDB存儲引擎既支持行級鎖,也支持表級鎖,但默認情況下是采用行級鎖。
InnoDB實現了兩種類型的行鎖:共享鎖、排他鎖。
共享鎖(S)——行鎖- 也稱為讀鎖,允許一個事務對某一行加共享鎖,其他事務仍可以再加共享鎖讀取此資源。多個共享鎖可以共存,但共享鎖和排他鎖不能共存。
- 對于普通SELECT語句,InnoDB實現時不會加任何鎖。
- 也稱為寫鎖,允許獲得排他鎖的事務更新數據,其他事務不能追加相同數據集的共享鎖和排他鎖。
- 對于UPDATE、DELETE和INSERT語句, InnoDB會自動給涉及數據集加排他鎖。
為了允許行鎖和表鎖共存,實現多粒度鎖機制,InnoDB 還有兩種内部使用的意向鎖(Intention Locks):意向共享鎖、意向排他鎖。這兩種意向鎖都是表鎖,意向鎖的主要作用是處理行鎖和表鎖之間的矛盾。
意向共享鎖(IS)——表鎖- 表示事務想要獲得一張表中某幾行的共享鎖。
- 事務在給一個數據行加共享鎖前必須先加IS鎖。
- 表示事務想要獲得一張表中某幾行的排他鎖。
- 事務在給一個數據行加排他鎖前必須先加IX鎖。
- 一個表有IX鎖,表示有事務正在鎖定某行或者試圖鎖定某行。
舉例說明:
事務A在更新行數據之前,先在表級别上加意向鎖,再加X鎖;加意向鎖是表示此表某一行被加了X鎖; 當事務B要對這個表加S鎖,如果表中數據很多,那麼事務B需要逐行檢查表是否能加S鎖,這樣會很耗時;如果此時在表級别上有意向鎖,那麼,事務B先檢查該表上是否存在意向鎖,存在的意向鎖是否與自己準備加的鎖沖突,如果有沖突,則等待直到事務A釋放,而無須逐條記錄去檢測,這樣速度會很快。
InnoDB行鎖模式兼容性
X鎖 |
IX鎖 |
S鎖 |
IS鎖 | |
X鎖 |
沖突 |
沖突 |
沖突 |
沖突 |
IX鎖 |
沖突 |
兼容 |
沖突 |
兼容 |
S鎖 |
沖突 |
沖突 |
兼容 |
兼容 |
IS鎖 |
沖突 |
兼容 |
兼容 |
兼容 |
- 如果一個事務請求的鎖模式與當前的鎖兼容,InnoDB 就将請求的鎖授予該事務;反之,如果兩者不兼容,該事務就要等待鎖釋放。
- 意向鎖之間是兼容的,不會産生沖突。
- 意向鎖是InnoDB自動加的,不需用戶幹預。在加行鎖之前,由InnoDB存儲引擎加上表的IS或IX鎖。
- 意向鎖存在的意義是為了更高效的獲取表鎖。
InnoDB行鎖是通過給索引上的索引項加鎖來實現的,如果沒有索引,InnoDB将通過隐藏的聚簇索引來對記錄加鎖。InnoDB存儲引擎有3種行鎖算法。
Record lock單個行記錄上的鎖,通過對索引項加鎖實現,鎖住的是索引,而不是記錄本身。
- 在不通過索引條件查詢時,InnoDB會鎖定表中的所有記錄,實際效果跟表鎖一樣。
- 當表有多個索引的時候,不同的事務可以使用不同的索引鎖定不同的行。
- 間隙鎖。對于鍵值在條件範圍内但并不存在的記錄,叫做“間隙(GAP)”。鎖定的是一個範圍,包括間隙,但不包含記錄本身。
- 是Record lock和Gap lock的組合,鎖定一個範圍,并且鎖定記錄本身。
- InnoDB使用Next-Key鎖的目的是為了防止幻讀,鎖定條件範圍内的數據,保證事務未提交時多次查到的數據是相同的。
- Next-key lock加鎖機制也會阻塞符合條件範圍内鍵值的并發插入,這往往會造成嚴重的鎖等待。因此,在實際應用開發中,尤其是并發插入比較多的應用,我們要盡量優化業務邏輯,盡量使用相等條件來訪問更新數據,避免使用範圍條件。
數據庫并發時,一般是讀寫并發、寫-寫并發。MVCC主要用于處理讀-寫沖突,MVCC可以做到不加鎖,非阻塞并發讀。
基本原理MVCC是通過保存某個時間點的快照來實現的,快照數據是指該行之前的曆史版本數據。 InnoDB為每次修改保存一個版本,版本與事務時間戳關聯,每行記錄可能有多個版本。讀取快照數據是不需要上鎖的,因為沒有事務需要對曆史數據進行修改。
對于不同的事務隔離級别,讀取到的快照數據是不同的。
- READ COMMITTED(已提交讀):在一個事務中,對于快照數據,總是讀取被鎖定行的最新一份快照數據。
- REPEATABLE READ(可重複讀):在一個事務中,對于快照數據,總是讀取事務開始時的行數據版本。
- 在并發讀寫數據庫時,使用快照讀可以做到在讀操作時不用阻塞寫操作,寫操作也不用阻塞讀操作,提高了數據庫并發讀寫的性能。
- 如下圖所示,在讀數據時,如果讀取的行被加了行鎖,這時讀操作不會去等待行鎖的釋放,而是會去讀取行的快照數據。
InnoDB非鎖定的一緻性讀
參考書籍《MySQL技術内幕:InnoDB存儲引擎(第2版)》
《MySQL技術内幕——SQL編程》
《深入淺出MySQL:數據庫開發、優化與管理維護(第2版)》
,