MySQL 事務隔離級別-北極之北

MySQL 事務隔離級別

本文所說的 MySQL 事務都是指在 InnoDB 引擎下,MyISAM 引擎是不支持事務的。

事務具有原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)、持久性(Durability)四個特性,簡稱 ACID,缺一不可。今天要說的就是隔離性

髒讀

 

髒讀指的是讀到了其他事務未提交的數據。

—解決髒讀的方法正常來說,讀寫分割開即可

不可重複讀

 

不可重複讀指的是在同一事務內,不同的時刻讀到的同一批數據可能是不一樣的,可能會受到其他事務的影響,比如其他事務改了這批數據並提交了。通常針對數據更新(UPDATE)操作。

—解決不可重複度的方法一般鎖住當前行即可

幻讀

 

幻讀是針對數據插入(INSERT)操作來說的。假設事務A對某些行的內容作了更改,但是還未提交,此時事務B插入了與事務A更改前的記錄相同的記錄行,並且在事務A提交之前先提交了,而這時,在事務A中查詢,會發現好像剛剛的更改對於某些數據未起作用,但其實是事務B剛插入進來的,讓用戶感覺很魔幻,感覺出現了幻覺,這就叫幻讀。

—由於幻讀主要針對插入和刪除操作,,可能需要鎖表。

 

 

針對以上可能出現的問題,MYSQL有四種事物隔離級別

 

讀未提交

可能產生髒讀、不可重複度、幻讀

不可重複度 (讀提交)

解決了髒讀,但是還是會出現不可重複讀、幻讀

可重複讀

解決了髒讀,不可重複讀,但是可能產生幻讀

串行化

 

解決了髒讀、不可重複讀和幻讀

MySQL事物隔離的實現

讀未提交

最野蠻的方式,因爲它壓根兒就不加鎖,所以根本談不上什麼隔離效果,可以理解爲沒有隔離。

串行化

讀的時候加共享鎖,也就是其他事務可以併發讀,但是不能寫。寫的時候加排它鎖,其他事務不能併發寫也不能併發讀。(喘息規劃會在讀取的每一行數據上都加鎖,但是不會鎖表)

 

不可重複度

不可重複讀解決了髒讀,MySQL 採用了 MVVC (多版本併發控制) 的方式。

 

可重複讀

爲了解決不可重複讀,或者爲了實現可重複讀,MySQL 採用了 MVVC (多版本併發控制) 的方式。

MVCC

 

MVCC,全稱Multi-Version Concurrency Control,即多版本併發控制。MVCC是一種併發控制的方法,一般在數據庫管理系統中,實現對數據庫的併發訪問,在編程語言中實現事務內存。

瞭解MVCC前,先介紹一下MySQL innoDB的當前讀和快照讀

  • 當前讀 像select lock in share mode(共享鎖), select for update ; update, insert ,delete(排他鎖)這些操作都是一種當前讀,爲什麼叫當前讀?就是它讀取的是記錄的最新版本,讀取時還要保證其他併發事務不能修改當前記錄,會對讀取的記錄進行加鎖。

  • 快照讀 像不加鎖的select操作就是快照讀,即不加鎖的非阻塞讀;快照讀的前提是隔離級別不是串行級別,串行級別下的快照讀會退化成當前讀;之所以出現快照讀的情況,是基於提高併發性能的考慮,快照讀的實現是基於多版本併發控制,即MVCC,可以認爲MVCC是行鎖的一個變種,但它在很多情況下,避免了加鎖操作,降低了開銷;既然是基於多版本,即快照讀可能讀到的並不一定是數據的最新版本,而有可能是之前的歷史版本

快照讀就是MySQL爲我們實現MVCC理想模型的其中一個具體非阻塞讀功能。而相對而言,當前讀就是悲觀鎖的具體功能實現,如前文說的鎖表操作

數據庫併發場景有三種,分別爲:

  • 讀-讀:不存在任何問題,也不需要併發控制

  • 讀-寫:有線程安全問題,可能會造成事務隔離性問題,可能遇到髒讀,幻讀,不可重複讀

  • 寫-寫:有線程安全問題,可能會存在更新丟失問題,比如第一類更新丟失,第二類更新丟失

 

在InnoDB中,會在每行數據後添加兩個額外的隱藏的值來實現MVCC,這兩個值一個記錄這行數據何時被創建,另外一個記錄這行數據何時過期(或者被刪除)。 在實際操作中,存儲的並不是時間,而是事務的版本號,每開啓一個新事務,事務的版本號就會遞增。 在可重讀Repeatable reads事務隔離級別下:

SELECT時,讀取創建版本號<=當前事務版本號,刪除版本號爲空或>當前事務版本號。

INSERT時,保存當前事務版本號爲行的創建版本號。

DELETE時,保存當前事務版本號爲行的刪除版本號。

UPDATE時,插入一條新紀錄,保存當前事務版本號爲行創建版本號,同時保存當前事務版本號到原來刪除的行。

通過MVCC,雖然每行記錄都需要額外的存儲空間,更多的行檢查工作以及一些額外的維護工作,但可以減少鎖的使用,大多數讀操作都不用加鎖,讀數據操作很簡單,性能很好,並且也能保證只會讀取到符合標準的行,也只鎖住必要行。

讀-讀

一個快照來說,它能夠讀到那些版本數據,要遵循以下規則:

  1. 當前事務內的更新,可以讀到;

  2. 版本未提交,不能讀到;

  3. 版本已提交,但是卻在快照創建後提交的,不能讀到;

  4. 版本已提交,且是在快照創建前提交的,可以讀到;

寫-寫

兩個事物同時更新一行數據,最後結果應該是哪個事務的結果呢。肯定要是時間靠後的那個對不對。並且更新之前要先讀數據,這裏所說的讀和上面說到的讀不一樣,更新之前的讀叫做“當前讀”,總是當前版本的數據,也就是多版本中最新一次提交的那版。

 

不可重複讀(讀提交)如何解決髒讀(MVCC)

 

而不可重複度(讀提交)每次執行語句的時候都重新生成一次快照。這樣事物就不會讀到其他事物未提交的數據

 

可重複讀如何解決不可重複度(MVCC+行鎖)

 

可重複讀是在事務開始的時候生成一個當前事務全局性的快照,如果有其他事物去更改該事物的數據,發現該數據被加了行鎖,所以其他事物無法去更新它,最終,在該事物的時間內,多次去讀取數據,都是一致的,這就是可重複讀。

 

幻讀如何解決(MVCC+間隙鎖)

雖然可重複讀解決不可重複讀的問題,但是有可能產生幻讀。因爲僅僅使用行鎖,如果在行與行之間,被插入/刪除了數據,造成事物對間隙數據的操作未生效(事物兩次讀取數據,第一次讀取10行,第二次讀取11行,也被歸納爲幻讀範疇)

所以,mysql使用MVCC+間隙鎖的方式,鎖住事物操作的數據及他們之間的間隙,確保不會有其他事物操作該數據

 

MVCC 實現原理

InnoDBMVCC 的實現方式爲:每一行記錄都有兩個隱藏列:DATA_TRX_IDDATA_ROLL_PTR(如果沒有主鍵,則還會多一個隱藏的主鍵列)。

DATA_TRX_ID

記錄最近更新這條行記錄的事務 ID,大小爲 6 個字節

DATA_ROLL_PTR

表示指向該行回滾段(rollback segment)的指針,大小爲 7 個字節,InnoDB 便是通過這個指針找到之前版本的數據。該行記錄上所有舊版本,在 undo 中都通過鏈表的形式組織。

DB_ROW_ID

行標識(隱藏單調自增 ID),大小爲 6 字節,如果表沒有主鍵,InnoDB 會自動生成一個隱藏主鍵,因此會出現這個列。另外,每條記錄的頭信息(record header)裏都有一個專門的 bitdeleted_flag)來表示當前記錄是否已經被刪除。

 

在多個事務並行操作某行數據的情況下,不同事務對該行數據的 UPDATE 會產生多個版本,然後通過回滾指針組織成一條 Undo Log

undo-log 爲了簡化分析,關於undo-log本文只介紹如下這種情況,也是最普遍的一種情況: 一條update語句,它根據主鍵進行查找,並且不修改主鍵的值。 首先記錄undo-log,把本次修改的字段原始值記錄下來: 然後在本條記錄上進行修改: 修改後寫redo-log,redo-log是單獨存放的,存放在名爲ib_logfile的一組文件中:可以看出以上大體的流程就是先寫undo-log,然後本地修改,最後寫redo-log。

寫undo-log的函數btr_cur_upd_lock_and_undo最終會調用函數trx_undo_page_report_modify(或insert)。

把舊版本的事務id寫入undo-log:

把該行的標識字段寫入undo-log:

將舊值保存的undo-log中

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章