mysql mvcc實現可重復讀和讀已提交


MYSQL 日志

  • binlog
    • binlog用於記錄數據庫執行的寫入性操作(不包括查詢)信息,以二進制的形式保存在磁盤中。binlog是mysql的邏輯日志,並且由Server層進行記錄,使用任何存儲引擎的mysql數據庫都會記錄binlog日志
    • binlog是通過追加的方式進行寫入的,可以通過max_binlog_size參數設置每個binlog文件的大小,當文件大小達到給定值之后,會生成新的文件來保存日志。
    • 在實際應用中,binlog的主要使用場景有兩個,分別是主從復制和數據恢復。
      • 主從復制:在Master端開啟binlog,然后將binlog發送到各個Slave端,Slave端重放binlog從而達到主從數據一致。
      • 數據恢復:通過使用mysqlbinlog工具來恢復數據。
    • binlog刷盤時機
      對於InnoDB存儲引擎而言,只有在事務提交時才會記錄biglog,此時記錄還在內存中,那么biglog是什么時候刷到磁盤中的呢?mysql通過sync_binlog參數控制biglog的刷盤時機,取值范圍是0-N:
      • 0:不去強制要求,由系統自行判斷何時寫入磁盤;
      • 1:每次commit的時候都要將binlog寫入磁盤;
      • N:每N個事務,才會將binlog寫入磁盤。
    • sync_binlog最安全的是設置是1,這也是MySQL 5.7.7之后版本的默認值。但是設置一個大一些的值可以提升數據庫性能,因此實際情況下也可以將值適當調大,犧牲一定的一致性來獲取更好的性能。
    • binlog日志有三種格式,分別為STATMENT、ROW和MIXED。
      • STATMENT。基於SQL語句的復制(statement-based replication, SBR),每一條會修改數據的sql語句會記錄到binlog中。
        • 優點:不需要記錄每一行的變化,減少了binlog日志量,節約了IO, 從而提高了性能;
        • 缺點:在某些情況下會導致主從數據不一致,比如執行sysdate()、slepp()等。
      • ROW。基於行的復制(row-based replication, RBR),不記錄每條sql語句的上下文信息,僅需記錄哪條數據被修改了。
        • 優點:不會出現某些特定情況下的存儲過程、或function、或trigger的調用和觸發無法被正確復制的問題;
        • 缺點:會產生大量的日志,尤其是alter table的時候會讓日志暴漲
      • MIXED。基於STATMENT和ROW兩種模式的混合復制(mixed-based replication, MBR),一般的復制使用STATEMENT模式保存binlog,對於STATEMENT模式無法復制的操作使用ROW模式保存binlog
  • redolog
    •  mysql是如何保證一致性的呢?最簡單的做法是在每次事務提交的時候,將該事務涉及修改的數據頁全部刷新到磁盤中。但是這么做會有嚴重的性能問題,主要體現在兩個方面
      • 因為Innodb是以頁為單位進行磁盤交互的,而一個事務很可能只修改一個數據頁里面的幾個字節,這個時候將完整的數據頁刷到磁盤的話,太浪費資源了!
      • 一個事務可能涉及修改多個數據頁,並且這些數據頁在物理上並不連續,使用隨機IO寫入性能太差!
    • 因此mysql設計了redo log,具體來說就是只記錄事務對數據頁做了哪些修改,這樣就能完美地解決性能問題了(相對而言文件更小並且是順序IO)
    • redo log包括兩部分:一個是內存中的日志緩沖(redo log buffer),另一個是磁盤上的日志文件(redo log file)。mysql每執行一條DML語句,先將記錄寫入redo log buffer,后續某個時間點再一次性將多個操作記錄寫到redo log file。這種先寫日志,再寫磁盤的技術就是MySQL里經常說到的WAL(Write-Ahead Logging) 技術
    • 在計算機操作系統中,用戶空間(user space)下的緩沖區數據一般情況下是無法直接寫入磁盤的,中間必須經過操作系統內核空間(kernel space)緩沖區(OS Buffer)。因此,redo log buffer寫入redo log file實際上是先寫入OS Buffer,然后再通過系統調用fsync()將其刷到redo log file中。
    • mysql支持三種將redo log buffer寫入redo log file的時機,可以通過innodb_flush_log_at_trx_commit參數配置,各參數值含義如下:
      • 0(無延遲)事務提交不會將redo log buffer寫入os buffer,而是每秒寫入os buffer並調用fsync寫入 redo log file中,也就是設置為零時每秒刷新寫入磁盤,當系統崩潰,會丟失一秒鍾的數據
      • 1(實時寫,實時刷)事務每次提交都將redo log buffer中的日志寫入os buffer並調用fsync刷到redo log file中,這種方式及時系統崩潰,因為每次提交都寫入磁盤,IO性能較差。
      • 2(實時寫,延遲刷)每次提交都寫入os buffer,然后每秒調用fsync()將os buffer中的日志寫入redo log file中。
  • undolog
    • 數據庫事務四大特性中有一個是原子性,具體來說就是 原子性是指對數據庫的一系列操作,要么全部成功,要么全部失敗,不可能出現部分成功的情況。

    • 原子性底層就是通過undo log實現的。undo log主要記錄了數據的邏輯變化,比如一條INSERT語句,對應一條DELETE的undo log,對於每個UPDATE語句,對應一條相反的UPDATE的undo log,這樣在發生錯誤時,就能回滾到事務之前的數據狀態。

    • 它的主要作用是將事務恢復到執行修改之前的樣子,但是,恢復的情況一般分為兩種,一種是邏輯恢復,一種是物理恢復,這里需要非常強調的是,undo的恢復是邏輯恢復,也就是說,如果你插入了100w條數據,導致innodb分配了一個新的數據頁來存儲這些數據,那么在事務進行回滾的時候,undo的功能並不是回收這個數據頁,而是將這些insert的操作,改變成delete的操作從而執行回滾。在這個過程中,共享表空間的大小並不會發生改變。除此之外,undo日志會將delete操作轉化為insert操作,update操作轉化為反向的update操作

 

 

 

mysql mvcc

  • MVCC(Multi-Version Concurrency Control)多版本並發控制,是用來在數據庫中控制並發的方法,實現對數據庫的並發訪問用的。在MySQL中,MVCC只在讀取已提交(Read Committed)和可重復讀(Repeatable Read)兩個事務級別下有效。其是通過Undo日志中的版本鏈和ReadView一致性視圖來實現的。MVCC就是在多個事務同時存在時,SELECT語句找尋到具體是版本鏈上的哪個版本,然后在找到的版本上返回其中所記錄的數據的過程。
  • 在MySQL中,會默認為我們的表后面添加三個隱藏字段
    • DB_ROW_ID:行ID,MySQL的B+樹索引特性要求每個表必須要有一個主鍵。如果沒有設置的話,會自動尋找第一個不包含NULL的唯一索引列作為主鍵。如果還是找不到,就會在這個DB_ROW_ID上自動生成一個唯一值,以此來當作主鍵(該列和MVCC的關系不大)
    • DB_TRX_ID:事務ID,記錄的是當前事務在做INSERT或UPDATE語句操作時的事務ID(DELETE語句被當做是UPDATE語句的特殊情況)
    • DB_ROLL_PTR:回滾指針,通過它可以將不同的版本串聯起來,形成版本鏈。相當於鏈表的next指針。
  • Read View(讀視圖)
    • Read View就是事務進行快照讀(select * from)操作的時候生產的讀視圖(Read View),在該事務執行的快照讀的那一刻,會生成事務系統當前的一個快照,記錄並維護系統當前活躍事務(未提交事務)的ID(當每個事務開啟時,都會被分配一個ID, 這個ID是遞增的,所以最新的事務,ID值越大)。
    •  Read View主要是用來做可見性判斷的, 即當我們某個事務執行快照讀的時候,對該記錄創建一個Read View讀視圖,把它比作條件用來判斷當前事務能夠看到哪個版本的數據,既可能是當前最新的數據,也有可能是該行記錄的undo log里面的某個版本的數據
    • ReadView中主要包含4個比較重要的內容:
      • m_ids:表示在生成ReadView時當前系統中活躍的讀寫事務的事務id列表。
      • 表示在生成ReadView時當前系統中活躍的讀寫事務中最小的事務id,也就是m_ids中的最小值。
      • max_trx_id:表示生成ReadView時系統中應該分配給下一個事務的id值。
      • creator_trx_id:表示生成該ReadView的快照讀操作產生的事務id。
    • 注意max_trx_id並不是m_ids中的最大值,事務id是遞增分配的。比方說現在有id為1, 2, 3這三個事務,后id為3的事務提交了。那么一個新的讀事務在生成ReadView時, m_ids就包括1和2, min_trx_id的值就是1,max_trx_id的值就是4。
    • 有了這個ReadView,這樣在訪問某條記錄時,只需要按照下邊的步驟判斷記錄的某個版本是否可見:
      • 如果被訪問版本的trx_id屬性值與ReadView中的creator_trx_id值相同,意味着當前事務在訪問它自己修改過的記錄,所以該版本可以被當前事務訪問。
      • 如果被訪問版本的trx_id屬性值小於ReadView中的min_trx_id值,表明生成該版本的事務在當前事務生成ReadView前已經提交,所以該版本可以被當前事務訪問。
      • 如果被訪問版本的trx_id屬性值大於ReadView中的max_trx_id值,表明生成該版本的事務在當前事務生成ReadView后才開啟,所以該版本不可以被當前事務訪問。
      • 如果被訪問版本的trx_id屬性值在ReadView的min_trx_id和max_trx_id之間,那就需要判斷一下trx_id屬性值是不是在m_ids列表中,如果在,說明創建ReadView時生成該版本的事務還是活躍的,該版本不可以被訪問;如果不在,說明創建ReadView時生成該版本的事務已經被提交,該版本可以被訪問
    • 當一個事務第一次執行查詢sql時,會生成一致性視圖 read-view(快照),它由執行查詢時所有未提交事務 id 數組(數組中最小的 id 為 min_id)和已創建的最大事務 id(max_id)組成,查詢時從 undo log 中最新的一條記錄開始跟 read-view 做對比,如果不符合比較規則,就根據回滾指針回滾到上一條記錄繼續比較,直到得到符合比較條件的查詢結果  

 

  

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM