mysql的隔離級別并非是按照標準實現的,作為從pg切過來的程序員還真是不太適應,這篇文章討論mysql隔離級別實現的,希望對大家能有幫助。
什么是事務
事務是數據庫一組讀寫操作的集合,事務具有ACID四個特性,原子性,一致性,隔離性和持久性。
事務有四個隔離級別,分別是讀未提交,讀已提交,可重復讀和串行化。
以上這些內容相信熟悉傳統(tǒng)數據庫的人,對這些都很熟悉,接下來講的內容可能有些人就不太了解了。
事務的實現方式
數據庫事務的實現方式主要有兩種:
- 基于鎖的;
- 基于時間戳的,現在主流的實現就是基于時間戳的方式的一種,就是大家熟悉的MVCC機制;
因為機制不同,所以事務的表現也不盡相同。
不同機制下的不同隔離級別
SQL標準定義了四種隔離級別,分別是讀未提交,讀已提交,可重復讀,可串行化。很明顯,越低隔離級別的事務并發(fā)行更好,但是一致性更低,嚴格來說,低隔離級別的事務是不符合A和I的,常用的隔離級別多為讀已提交和可重復度。
但是隔離級別的定義是基于鎖并發(fā)控制實現的,基于MVCC機制實現的數據庫事務表現行為會稍有不同。
jim gray曾經有一篇論文討論不同機制實現的數據庫隔離級別的不同表現,并將隔離級別擴展到7個。見下圖:

基于此將常見的傳統(tǒng)數據庫隔離級別統(tǒng)計如下:
- SYBASE支持的隔離級別:degree 0(read uncommitted)、degree 1(read committed)、degree 2(repeatable read)、degree 3(serializable isolation);
- ORACLE支持的隔離級別:read committed(consistent read)、serializable(snapshot isolation);
- DB2支持的隔離級別:read uncommitted、cursor stability、read stability、repeatable read;
- Postgresql支持的隔離級別:read committed(consistent read)、repeatable read(snapshot isolation)、serializable isolation(Serialaizable Snapshot Isolation);
- SQL Server支持的隔離級別:read uncommitted、read committed snapshot 、read committed 、repeatable read、snapshot isolation、serializable isolation;
- MySQL支持的隔離級別:read uncommitted、read committed(consistent read)、repeatable read(snapshot isolation)、serializable isolation;
幻讀(P3/A3)和寫偏斜(A5B)
上圖的各個字母都是數據庫的各種不一致現象。如果把寫操作記作w,讀操作記作r,那么這些有害依賴可以表示為下圖
| identifier | query | phenomena |
|---|---|---|
| P0 | w1[x]...w2[x]...((c1 or a1) and (c2 or a2) in any order) | Dirty Write |
| P1 | w1[x]...r2[x]...((c1 or a1) and (c2 or a2) in any order) | Dirty Read |
| P2 | r1[x]...w2[x]...((c1 or a1) and (c2 or a2) any order) | Fuzzy / Non-Repeatable Read |
| P3 | r1[P]...w2[y in P]...((c1 or a1) and (c2 or a2) any order) | Phantom |
| P4 | r1[x]...w2[x]...w1[x]...c1 | Lost Update |
| P4C | rc1[x]...w2[x]...w1[x]...c1 | Lost Update |
| A3 | r1[P]...w2[y in P]...c2....r1[P]...c1 | Phantom |
| A5A | r1[x]...w2[x]...w2[y]...c2...r1[y]...(c1 or a1) | Read Skew |
| A5B | r1[x]...r2[y]...w1[y]...w2[x]...(c1 and c2 occur) | Write Skew |
mysql中的可重復度
幻讀
mysql是支持MVCC機制實現的數據庫,因此很多人(包括我)會想當然認為他的SI應該就是標準的實現,不會出現幻讀(A3/P3)的現象。接下來,請看如下例子:

如上圖所示,事務2的insert發(fā)生在兩次select之間,這兩次select也如SI一樣正確的顯示了該看到的結果,但是update發(fā)生之后,一切就變了,MySQL的RR隔離級別也會幻讀?。?!
寫偏斜
也許有人會說,mysql同時也是使用鎖的,因此發(fā)生幻讀不奇怪,所以我們可以看接下來這個寫偏斜的經典例子:

顯然,mysql也是會發(fā)生寫偏斜的。
mysql中可重復讀的實現
看源碼可以發(fā)現,mysql中的讀操作是使用MVCC機制實現,可以正確的查找到需要的行,但是寫操作實現的時候有兩點和我想的不太一樣:
- 寫操作永遠讀取已提交的數據,并沒有走MVCC的邏輯;
- 寫操作的并發(fā)是通過鎖控制的,不檢查更新行是否是對本事務可見的。
MVCC機制行的可見條件很簡單,可以總結為兩句話:
- 對不同事務,插入事務已提交,刪除事務未提交(update可以看做先刪除后插入);
- 對本事務,插入的statement發(fā)生在自己之前,刪除的statement未發(fā)生或在自己之后;
套用幻讀那個例子,本來事務1是不該看到新插入的行的(因為不符合可見條件1),但是update只讀取最新的行,因此對新插入的行做了一次更新,導致該行符合可見條件2,再次select就可以查到這個行。
根據這個實現,我們可以推理出,mysql的可重復讀同樣會發(fā)生lost update和read skew,只要測試的事務中存在寫操作。具體例子可見此處
mysql的可重復讀是比SI更低的隔離級別,在發(fā)生幻讀時,SI隔離級別事物的正確行為應該是后提交的事務回滾,而mysql兩個事務都可以提交,顯然,他的一致性更低,但是并發(fā)性更好(回滾率低),這是一次在用戶使用習慣,性能和一致性之間的權衡,至于優(yōu)劣,就見仁見智了,至少現在看來不壞。
postgresql中的可重復讀
無幻讀
pg實現的隔離級別是比較標準的,可重復度級別(實際是SI)沒有幻讀,這里舉兩個例子
第一個例子

類比mysql的第一個例子,和mysql不同,可以看到pg的事務update的時候只更新了兩行,不包括新插入的行
第二個例子

當該行同時被兩個可重復級別的事務更新時,后提交的事務會回滾,因為更新只能在最新的行上執(zhí)行,否則就是丟失更新了。
寫偏斜

可以看到,pg的可重復級別事務,還是存在寫偏斜的,這是符合標準的。
參考文檔
- 《A Critique of ANSI SQL Isolation Levels》
- mysql源碼
- pg源碼
- https://github.com/ept/hermitage/blob/master/mysql.md
廣告
最后,打個廣告,如果對創(chuàng)業(yè),分布式數據庫和開源社區(qū)感興趣,歡迎加入我們,實習和工作都很歡迎!
pingcap是國內為數不多的newsql方向的分布式數據庫,維護國內最頂級的開源社區(qū),關注度近萬,做類f1+spanner架構,和多家公司有合作關系。
TiDB: https://github.com/pingcap/tidb
TiKV: https://github.com/pingcap/tikv
Email: xuwentao@pingcap.com
微信: fbisland