[TOC]
## 什么是MVCC
全稱Multi-Version Concurrency Control,即`多版本并發控制`,主要是為了提高數據庫的`并發性能`。以下文章都是圍繞InnoDB引擎來講,因為myIsam不支持事務。
同一行數據平時發生讀寫請求時,會`上鎖阻塞`住。但mvcc用更好的方式去處理讀—寫請求,做到在發生讀—寫請求沖突時`不用加鎖`。
這個讀是指的`快照讀`,而不是`當前讀`,當前讀是一種加鎖操作,是`悲觀鎖`。
那它到底是怎么做到讀—寫`不用加鎖`的,`快照讀`和`當前讀`又是什么鬼,跟著你們的`貼心老哥`,繼續往下看。

## 當前讀、快照讀都是什么鬼
什么是MySQL InnoDB下的當前讀和快照讀?
### 當前讀
它讀取的數據庫記錄,都是`當前最新`的`版本`,會對當前讀取的數據進行`加鎖`,防止其他事務修改數據。是`悲觀鎖`的一種操作。
如下操作都是當前讀:
* select lock in share mode (共享鎖)
* select for update (排他鎖)
* update (排他鎖)
* insert (排他鎖)
* delete (排他鎖)
* 串行化事務隔離級別
### 快照讀
快照讀的實現是基于`多版本`并發控制,即MVCC,既然是多版本,那么快照讀讀到的數據不一定是當前最新的數據,有可能是之前`歷史版本`的數據。
如下操作是快照讀:
* 不加鎖的select操作(注:事務級別不是串行化)
### 快照讀與mvcc的關系
`MVCCC`是“維持一個數據的多個版本,使讀寫操作沒有沖突”的一個`抽象概念`。
這個概念需要具體功能去實現,這個具體實現就是`快照讀`。(具體實現下面講)
聽完`貼心老哥`的講解,是不是瞬間`茅廁頓開`。

## 數據庫并發場景
* `讀-讀`:不存在任何問題,也不需要并發控制
* `讀-寫`:有線程安全問題,可能會造成事務隔離性問題,可能遇到臟讀,幻讀,不可重復讀
* `寫-寫`:有線程安全問題,可能會存在更新丟失問題,比如第一類更新丟失,第二類更新丟失
## MVCC解決并發哪些問題?
mvcc用來解決讀—寫沖突的無鎖并發控制,就是為事務分配`單向增長`的`時間戳`。為每個數據修改保存一個`版本`,版本與事務時間戳`相關聯`。
讀操作`只讀取`該事務`開始前`的`數據庫快照`。
**解決問題如下:**
* `并發讀-寫時`:可以做到讀操作不阻塞寫操作,同時寫操作也不會阻塞讀操作。
* 解決`臟讀`、`幻讀`、`不可重復讀`等事務隔離問題,但不能解決上面的`寫-寫 更新丟失`問題。
**因此有了下面提高并發性能的`組合拳`:**
* `MVCC + 悲觀鎖`:MVCC解決讀寫沖突,悲觀鎖解決寫寫沖突
* `MVCC + 樂觀鎖`:MVCC解決讀寫沖突,樂觀鎖解決寫寫沖突
## MVCC的實現原理
它的實現原理主要是`版本鏈`,`undo日志` ,`Read View` 來實現的
### 版本鏈
我們數據庫中的每行數據,除了我們肉眼看見的數據,還有幾個`隱藏字段`,得開`天眼`才能看到。分別是`db_trx_id`、`db_roll_pointer`、`db_row_id`。
* db\_trx\_id
6byte,最近修改(修改/插入)`事務ID`:記錄`創建`這條記錄/`最后一次修改`該記錄的`事務ID`。
* db\_roll\_pointer(版本鏈關鍵)
7byte,`回滾指針`,指向`這條記錄`的`上一個版本`(存儲于rollback segment里)
* db\_row\_id
6byte,隱含的`自增ID`(隱藏主鍵),如果數據表`沒有主鍵`,InnoDB會自動以db\_row\_id產生一個`聚簇索引`。
* 實際還有一個`刪除flag`隱藏字段, 記錄被`更新`或`刪除`并不代表真的刪除,而是`刪除flag`變了

如上圖,`db_row_id`是數據庫默認為該行記錄生成的`唯一隱式主鍵`,`db_trx_id`是當前操作該記錄的`事務ID`,而`db_roll_pointer`是一個`回滾指針`,用于配合`undo日志`,指向上一個`舊版本`。
每次對數據庫記錄進行改動,都會記錄一條`undo日志`,每條undo日志也都有一個`roll_pointer`屬性(INSERT操作對應的undo日志沒有該屬性,因為該記錄并沒有更早的版本),可以將這些`undo日志都連起來`,`串成一個鏈表`,所以現在的情況就像下圖一樣:

對該記錄每次更新后,都會將舊值放到一條undo日志中,就算是該記錄的一個舊版本,隨著更新次數的增多,所有的版本都會被`roll_pointer`屬性連接成一個`鏈表`,我們把這個鏈表稱之為`版本鏈`,版本鏈的頭節點就是當前記錄最新的值。另外,每個版本中還包含生成該版本時對應的事務id,這個信息很重要,在根據ReadView判斷版本可見性的時候會用到。
### undo日志
Undo log 主要用于`記錄`數據被`修改之前`的日志,在表信息修改之前先會把數據拷貝到`undo log`里。
當`事務`進行`回滾時`可以通過undo log 里的日志進行`數據還原`。
**Undo log 的用途**
* 保證`事務`進行`rollback`時的`原子性和一致性`,當事務進行`回滾`的時候可以用undo log的數據進行`恢復`。
* 用于MVCC`快照讀`的數據,在MVCC多版本控制中,通過讀取`undo log`的`歷史版本數據`可以實現`不同事務版本號`都擁有自己`獨立的快照數據版本`。
**undo log主要分為兩種:**
* insert undo log
代表事務在insert新記錄時產生的undo log , 只在事務回滾時需要,并且在事務提交后可以被立即丟棄
* update undo log(主要)
事務在進行update或delete時產生的undo log ; 不僅在事務回滾時需要,在快照讀時也需要;
所以不能隨便刪除,只有在快速讀或事務回滾不涉及該日志時,對應的日志才會被purge線程統一清除
### Read View(讀視圖)
事務進行`快照讀`操作的時候生產的`讀視圖`(Read View),在該事務執行的快照讀的那一刻,會生成數據庫系統當前的一個`快照`。
記錄并維護系統當前`活躍事務的ID`(沒有commit,當每個事務開啟時,都會被分配一個ID, 這個ID是遞增的,所以越新的事務,ID值越大),是系統中當前不應該被`本事務`看到的`其他事務id列表`。
Read View主要是用來做`可見性`判斷的, 即當我們`某個事務`執行`快照讀`的時候,對該記錄創建一個Read View讀視圖,把它比作條件用來判斷`當前事務`能夠看到`哪個版本`的數據,既可能是當前`最新`的數據,也有可能是該行記錄的undo log里面的`某個版本`的數據。
**Read View幾個屬性**
* `trx_ids`: 當前系統活躍(`未提交`)事務版本號集合。
* `low_limit_id`: 創建當前read view 時“當前系統`最大事務版本號`+1”。
* `up_limit_id`: 創建當前read view 時“系統正處于活躍事務`最小版本號`”
* `creator_trx_id`: 創建當前read view的事務版本號;
### Read View可見性判斷條件

* `db_trx_id` < `up_limit_id` || `db_trx_id` == `creator_trx_id`(顯示)
如果數據事務ID小于read view中的`最小活躍事務ID`,則可以肯定該數據是在`當前事務啟之前`就已經`存在`了的,所以可以`顯示`。
或者數據的`事務ID`等于`creator_trx_id` ,那么說明這個數據就是當前事務`自己生成的`,自己生成的數據自己當然能看見,所以這種情況下此數據也是可以`顯示`的。
* `db_trx_id` >= `low_limit_id`(不顯示)
如果數據事務ID大于read view 中的當前系統的`最大事務ID`,則說明該數據是在當前read view 創建`之后才產生`的,所以數據`不顯示`。如果小于則進入下一個判斷
* `db_trx_id`是否在`活躍事務`(trx\_ids)中
* `不存在`:則說明read view產生的時候事務`已經commit`了,這種情況數據則可以`顯示`。
* `已存在`:則代表我Read View生成時刻,你這個事務還在活躍,還沒有Commit,你修改的數據,我當前事務也是看不見的。

## MVCC和事務隔離級別
上面所講的`Read View`用于支持`RC`(Read Committed,讀提交)和`RR`(Repeatable Read,可重復讀)`隔離級別`的`實現`。
### RR、RC生成時機
* `RC`隔離級別下,是每個`快照讀`都會`生成并獲取最新`的`Read View`;
* 而在`RR`隔離級別下,則是`同一個事務中`的`第一個快照讀`才會創建`Read View`, `之后的`快照讀獲取的都是`同一個Read View`,之后的查詢就`不會重復生成`了,所以一個事務的查詢結果每次`都是一樣的`。
### 解決幻讀問題
* `快照讀`:通過MVCC來進行控制的,不用加鎖。按照MVCC中規定的“語法”進行增刪改查等操作,以避免幻讀。
* `當前讀`:通過next-key鎖(行鎖+gap鎖)來解決問題的。
### RC、RR級別下的InnoDB快照讀區別
* 在RR級別下的某個事務的對某條記錄的第一次快照讀會創建一個快照及Read View, 將當前系統活躍的其他事務記錄起來,此后在調用快照讀的時候,還是使用的是同一個Read View,所以只要當前事務在其他事務提交更新之前使用過快照讀,那么之后的快照讀使用的都是同一個Read View,所以對之后的修改不可見;
* 即RR級別下,快照讀生成Read View時,Read View會記錄此時所有其他活動事務的快照,這些事務的修改對于當前事務都是不可見的。而早于Read View創建的事務所做的修改均是可見
* 而在RC級別下的,事務中,每次快照讀都會新生成一個快照和Read View, 這就是我們在RC級別下的事務中可以看到別的事務提交的更新的原因
作者:IT老哥
鏈接:https://juejin.cn/post/6871046354018238472
來源:掘金
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
- 一.JVM
- 1.1 java代碼是怎么運行的
- 1.2 JVM的內存區域
- 1.3 JVM運行時內存
- 1.4 JVM內存分配策略
- 1.5 JVM類加載機制與對象的生命周期
- 1.6 常用的垃圾回收算法
- 1.7 JVM垃圾收集器
- 1.8 CMS垃圾收集器
- 1.9 G1垃圾收集器
- 2.面試相關文章
- 2.1 可能是把Java內存區域講得最清楚的一篇文章
- 2.0 GC調優參數
- 2.1GC排查系列
- 2.2 內存泄漏和內存溢出
- 2.2.3 深入理解JVM-hotspot虛擬機對象探秘
- 1.10 并發的可達性分析相關問題
- 二.Java集合架構
- 1.ArrayList深入源碼分析
- 2.Vector深入源碼分析
- 3.LinkedList深入源碼分析
- 4.HashMap深入源碼分析
- 5.ConcurrentHashMap深入源碼分析
- 6.HashSet,LinkedHashSet 和 LinkedHashMap
- 7.容器中的設計模式
- 8.集合架構之面試指南
- 9.TreeSet和TreeMap
- 三.Java基礎
- 1.基礎概念
- 1.1 Java程序初始化的順序是怎么樣的
- 1.2 Java和C++的區別
- 1.3 反射
- 1.4 注解
- 1.5 泛型
- 1.6 字節與字符的區別以及訪問修飾符
- 1.7 深拷貝與淺拷貝
- 1.8 字符串常量池
- 2.面向對象
- 3.關鍵字
- 4.基本數據類型與運算
- 5.字符串與數組
- 6.異常處理
- 7.Object 通用方法
- 8.Java8
- 8.1 Java 8 Tutorial
- 8.2 Java 8 數據流(Stream)
- 8.3 Java 8 并發教程:線程和執行器
- 8.4 Java 8 并發教程:同步和鎖
- 8.5 Java 8 并發教程:原子變量和 ConcurrentMap
- 8.6 Java 8 API 示例:字符串、數值、算術和文件
- 8.7 在 Java 8 中避免 Null 檢查
- 8.8 使用 Intellij IDEA 解決 Java 8 的數據流問題
- 四.Java 并發編程
- 1.線程的實現/創建
- 2.線程生命周期/狀態轉換
- 3.線程池
- 4.線程中的協作、中斷
- 5.Java鎖
- 5.1 樂觀鎖、悲觀鎖和自旋鎖
- 5.2 Synchronized
- 5.3 ReentrantLock
- 5.4 公平鎖和非公平鎖
- 5.3.1 說說ReentrantLock的實現原理,以及ReentrantLock的核心源碼是如何實現的?
- 5.5 鎖優化和升級
- 6.多線程的上下文切換
- 7.死鎖的產生和解決
- 8.J.U.C(java.util.concurrent)
- 0.簡化版(快速復習用)
- 9.鎖優化
- 10.Java 內存模型(JMM)
- 11.ThreadLocal詳解
- 12 CAS
- 13.AQS
- 0.ArrayBlockingQueue和LinkedBlockingQueue的實現原理
- 1.DelayQueue的實現原理
- 14.Thread.join()實現原理
- 15.PriorityQueue 的特性和原理
- 16.CyclicBarrier的實際使用場景
- 五.Java I/O NIO
- 1.I/O模型簡述
- 2.Java NIO之緩沖區
- 3.JAVA NIO之文件通道
- 4.Java NIO之套接字通道
- 5.Java NIO之選擇器
- 6.基于 Java NIO 實現簡單的 HTTP 服務器
- 7.BIO-NIO-AIO
- 8.netty(一)
- 9.NIO面試題
- 六.Java設計模式
- 1.單例模式
- 2.策略模式
- 3.模板方法
- 4.適配器模式
- 5.簡單工廠
- 6.門面模式
- 7.代理模式
- 七.數據結構和算法
- 1.什么是紅黑樹
- 2.二叉樹
- 2.1 二叉樹的前序、中序、后序遍歷
- 3.排序算法匯總
- 4.java實現鏈表及鏈表的重用操作
- 4.1算法題-鏈表反轉
- 5.圖的概述
- 6.常見的幾道字符串算法題
- 7.幾道常見的鏈表算法題
- 8.leetcode常見算法題1
- 9.LRU緩存策略
- 10.二進制及位運算
- 10.1.二進制和十進制轉換
- 10.2.位運算
- 11.常見鏈表算法題
- 12.算法好文推薦
- 13.跳表
- 八.Spring 全家桶
- 1.Spring IOC
- 2.Spring AOP
- 3.Spring 事務管理
- 4.SpringMVC 運行流程和手動實現
- 0.Spring 核心技術
- 5.spring如何解決循環依賴問題
- 6.springboot自動裝配原理
- 7.Spring中的循環依賴解決機制中,為什么要三級緩存,用二級緩存不夠嗎
- 8.beanFactory和factoryBean有什么區別
- 九.數據庫
- 1.mybatis
- 1.1 MyBatis-# 與 $ 區別以及 sql 預編譯
- Mybatis系列1-Configuration
- Mybatis系列2-SQL執行過程
- Mybatis系列3-之SqlSession
- Mybatis系列4-之Executor
- Mybatis系列5-StatementHandler
- Mybatis系列6-MappedStatement
- Mybatis系列7-參數設置揭秘(ParameterHandler)
- Mybatis系列8-緩存機制
- 2.淺談聚簇索引和非聚簇索引的區別
- 3.mysql 證明為什么用limit時,offset很大會影響性能
- 4.MySQL中的索引
- 5.數據庫索引2
- 6.面試題收集
- 7.MySQL行鎖、表鎖、間隙鎖詳解
- 8.數據庫MVCC詳解
- 9.一條SQL查詢語句是如何執行的
- 10.MySQL 的 crash-safe 原理解析
- 11.MySQL 性能優化神器 Explain 使用分析
- 12.mysql中,一條update語句執行的過程是怎么樣的?期間用到了mysql的哪些log,分別有什么作用
- 十.Redis
- 0.快速復習回顧Redis
- 1.通俗易懂的Redis數據結構基礎教程
- 2.分布式鎖(一)
- 3.分布式鎖(二)
- 4.延時隊列
- 5.位圖Bitmaps
- 6.Bitmaps(位圖)的使用
- 7.Scan
- 8.redis緩存雪崩、緩存擊穿、緩存穿透
- 9.Redis為什么是單線程、及高并發快的3大原因詳解
- 10.布隆過濾器你值得擁有的開發利器
- 11.Redis哨兵、復制、集群的設計原理與區別
- 12.redis的IO多路復用
- 13.相關redis面試題
- 14.redis集群
- 十一.中間件
- 1.RabbitMQ
- 1.1 RabbitMQ實戰,hello world
- 1.2 RabbitMQ 實戰,工作隊列
- 1.3 RabbitMQ 實戰, 發布訂閱
- 1.4 RabbitMQ 實戰,路由
- 1.5 RabbitMQ 實戰,主題
- 1.6 Spring AMQP 的 AMQP 抽象
- 1.7 Spring AMQP 實戰 – 整合 RabbitMQ 發送郵件
- 1.8 RabbitMQ 的消息持久化與 Spring AMQP 的實現剖析
- 1.9 RabbitMQ必備核心知識
- 2.RocketMQ 的幾個簡單問題與答案
- 2.Kafka
- 2.1 kafka 基礎概念和術語
- 2.2 Kafka的重平衡(Rebalance)
- 2.3.kafka日志機制
- 2.4 kafka是pull還是push的方式傳遞消息的?
- 2.5 Kafka的數據處理流程
- 2.6 Kafka的腦裂預防和處理機制
- 2.7 Kafka中partition副本的Leader選舉機制
- 2.8 如果Leader掛了的時候,follower沒來得及同步,是否會出現數據不一致
- 2.9 kafka的partition副本是否會出現腦裂情況
- 十二.Zookeeper
- 0.什么是Zookeeper(漫畫)
- 1.使用docker安裝Zookeeper偽集群
- 3.ZooKeeper-Plus
- 4.zk實現分布式鎖
- 5.ZooKeeper之Watcher機制
- 6.Zookeeper之選舉及數據一致性
- 十三.計算機網絡
- 1.進制轉換:二進制、八進制、十六進制、十進制之間的轉換
- 2.位運算
- 3.計算機網絡面試題匯總1
- 十四.Docker
- 100.面試題收集合集
- 1.美團面試常見問題總結
- 2.b站部分面試題
- 3.比心面試題
- 4.騰訊面試題
- 5.哈羅部分面試
- 6.筆記
- 十五.Storm
- 1.Storm和流處理簡介
- 2.Storm 核心概念詳解
- 3.Storm 單機版本環境搭建
- 4.Storm 集群環境搭建
- 5.Storm 編程模型詳解
- 6.Storm 項目三種打包方式對比分析
- 7.Storm 集成 Redis 詳解
- 8.Storm 集成 HDFS 和 HBase
- 9.Storm 集成 Kafka
- 十六.Elasticsearch
- 1.初識ElasticSearch
- 2.文檔基本CRUD、集群健康檢查
- 3.shard&replica
- 4.document核心元數據解析及ES的并發控制
- 5.document的批量操作及數據路由原理
- 6.倒排索引
- 十七.分布式相關
- 1.分布式事務解決方案一網打盡
- 2.關于xxx怎么保證高可用的問題
- 3.一致性hash原理與實現
- 4.微服務注冊中心 Nacos 比 Eureka的優勢
- 5.Raft 協議算法
- 6.為什么微服務架構中需要網關
- 0.CAP與BASE理論
- 十八.Dubbo
- 1.快速掌握Dubbo常規應用
- 2.Dubbo應用進階
- 3.Dubbo調用模塊詳解
- 4.Dubbo調用模塊源碼分析
- 6.Dubbo協議模塊