learn.lianglianglee.com/文章/MySQL中的事务和MVCC.md.html
2022-05-11 19:04:14 +08:00

707 lines
42 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>MySQL中的事务和MVCC.md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/文章/AQS 万字图文全面解析.md.html">AQS 万字图文全面解析.md.html</a>
</li>
<li>
<a href="/文章/Docker 镜像构建原理及源码分析.md.html">Docker 镜像构建原理及源码分析.md.html</a>
</li>
<li>
<a href="/文章/ElasticSearch 小白从入门到精通.md.html">ElasticSearch 小白从入门到精通.md.html</a>
</li>
<li>
<a href="/文章/JVM CPU Profiler技术原理及源码深度解析.md.html">JVM CPU Profiler技术原理及源码深度解析.md.html</a>
</li>
<li>
<a href="/文章/JVM 垃圾收集器.md.html">JVM 垃圾收集器.md.html</a>
</li>
<li>
<a href="/文章/JVM 面试的 30 个知识点.md.html">JVM 面试的 30 个知识点.md.html</a>
</li>
<li>
<a href="/文章/Java IO 体系、线程模型大总结.md.html">Java IO 体系、线程模型大总结.md.html</a>
</li>
<li>
<a href="/文章/Java NIO浅析.md.html">Java NIO浅析.md.html</a>
</li>
<li>
<a href="/文章/Java 面试题集锦(网络篇).md.html">Java 面试题集锦(网络篇).md.html</a>
</li>
<li>
<a href="/文章/Java-直接内存 DirectMemory 详解.md.html">Java-直接内存 DirectMemory 详解.md.html</a>
</li>
<li>
<a href="/文章/Java中9种常见的CMS GC问题分析与解决.md.html">Java中9种常见的CMS GC问题分析与解决.md.html</a>
</li>
<li>
<a href="/文章/Java中9种常见的CMS GC问题分析与解决.md.html">Java中9种常见的CMS GC问题分析与解决.md.html</a>
</li>
<li>
<a href="/文章/Java中的SPI.md.html">Java中的SPI.md.html</a>
</li>
<li>
<a href="/文章/Java中的ThreadLocal.md.html">Java中的ThreadLocal.md.html</a>
</li>
<li>
<a href="/文章/Java线程池实现原理及其在美团业务中的实践.md.html">Java线程池实现原理及其在美团业务中的实践.md.html</a>
</li>
<li>
<a href="/文章/Java魔法类Unsafe应用解析.md.html">Java魔法类Unsafe应用解析.md.html</a>
</li>
<li>
<a href="/文章/Kafka 源码阅读笔记.md.html">Kafka 源码阅读笔记.md.html</a>
</li>
<li>
<a href="/文章/Kafka、ActiveMQ、RabbitMQ、RocketMQ 区别以及高可用原理.md.html">Kafka、ActiveMQ、RabbitMQ、RocketMQ 区别以及高可用原理.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB Buffer Pool.md.html">MySQL · 引擎特性 · InnoDB Buffer Pool.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB IO子系统.md.html">MySQL · 引擎特性 · InnoDB IO子系统.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB 事务系统.md.html">MySQL · 引擎特性 · InnoDB 事务系统.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB 同步机制.md.html">MySQL · 引擎特性 · InnoDB 同步机制.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB 数据页解析.md.html">MySQL · 引擎特性 · InnoDB 数据页解析.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB崩溃恢复.md.html">MySQL · 引擎特性 · InnoDB崩溃恢复.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · 临时表那些事儿.md.html">MySQL · 引擎特性 · 临时表那些事儿.md.html</a>
</li>
<li>
<a href="/文章/MySQL 主从复制 半同步复制.md.html">MySQL 主从复制 半同步复制.md.html</a>
</li>
<li>
<a href="/文章/MySQL 主从复制 基于GTID复制.md.html">MySQL 主从复制 基于GTID复制.md.html</a>
</li>
<li>
<a href="/文章/MySQL 主从复制.md.html">MySQL 主从复制.md.html</a>
</li>
<li>
<a href="/文章/MySQL 事务日志(redo log和undo log).md.html">MySQL 事务日志(redo log和undo log).md.html</a>
</li>
<li>
<a href="/文章/MySQL 亿级别数据迁移实战代码分享.md.html">MySQL 亿级别数据迁移实战代码分享.md.html</a>
</li>
<li>
<a href="/文章/MySQL 从一条数据说起-InnoDB行存储数据结构.md.html">MySQL 从一条数据说起-InnoDB行存储数据结构.md.html</a>
</li>
<li>
<a href="/文章/MySQL 地基基础:事务和锁的面纱.md.html">MySQL 地基基础:事务和锁的面纱.md.html</a>
</li>
<li>
<a href="/文章/MySQL 地基基础:数据字典.md.html">MySQL 地基基础:数据字典.md.html</a>
</li>
<li>
<a href="/文章/MySQL 地基基础:数据库字符集.md.html">MySQL 地基基础:数据库字符集.md.html</a>
</li>
<li>
<a href="/文章/MySQL 性能优化:碎片整理.md.html">MySQL 性能优化:碎片整理.md.html</a>
</li>
<li>
<a href="/文章/MySQL 故障诊断:一个 ALTER TALBE 执行了很久,你慌不慌?.md.html">MySQL 故障诊断:一个 ALTER TALBE 执行了很久,你慌不慌?.md.html</a>
</li>
<li>
<a href="/文章/MySQL 故障诊断:如何在日志中轻松定位大事务.md.html">MySQL 故障诊断:如何在日志中轻松定位大事务.md.html</a>
</li>
<li>
<a href="/文章/MySQL 故障诊断:教你快速定位加锁的 SQL.md.html">MySQL 故障诊断:教你快速定位加锁的 SQL.md.html</a>
</li>
<li>
<a href="/文章/MySQL 日志详解.md.html">MySQL 日志详解.md.html</a>
</li>
<li>
<a href="/文章/MySQL 的半同步是什么?.md.html">MySQL 的半同步是什么?.md.html</a>
</li>
<li>
<a class="current-tab" href="/文章/MySQL中的事务和MVCC.md.html">MySQL中的事务和MVCC.md.html</a>
</li>
<li>
<a href="/文章/MySQL事务_事务隔离级别详解.md.html">MySQL事务_事务隔离级别详解.md.html</a>
</li>
<li>
<a href="/文章/MySQL优化优化 select count().md.html">MySQL优化优化 select count().md.html</a>
</li>
<li>
<a href="/文章/MySQL共享锁、排他锁、悲观锁、乐观锁.md.html">MySQL共享锁、排他锁、悲观锁、乐观锁.md.html</a>
</li>
<li>
<a href="/文章/MySQL的MVCC多版本并发控制.md.html">MySQL的MVCC多版本并发控制.md.html</a>
</li>
<li>
<a href="/文章/QingStor 对象存储架构设计及最佳实践.md.html">QingStor 对象存储架构设计及最佳实践.md.html</a>
</li>
<li>
<a href="/文章/RocketMQ 面试题集锦.md.html">RocketMQ 面试题集锦.md.html</a>
</li>
<li>
<a href="/文章/SnowFlake 雪花算法生成分布式 ID.md.html">SnowFlake 雪花算法生成分布式 ID.md.html</a>
</li>
<li>
<a href="/文章/Spring Boot 2.x 结合 k8s 实现分布式微服务架构.md.html">Spring Boot 2.x 结合 k8s 实现分布式微服务架构.md.html</a>
</li>
<li>
<a href="/文章/Spring Boot 教程:如何开发一个 starter.md.html">Spring Boot 教程:如何开发一个 starter.md.html</a>
</li>
<li>
<a href="/文章/Spring MVC 原理.md.html">Spring MVC 原理.md.html</a>
</li>
<li>
<a href="/文章/Spring MyBatis和Spring整合的奥秘.md.html">Spring MyBatis和Spring整合的奥秘.md.html</a>
</li>
<li>
<a href="/文章/Spring 帮助你更好的理解Spring循环依赖.md.html">Spring 帮助你更好的理解Spring循环依赖.md.html</a>
</li>
<li>
<a href="/文章/Spring 循环依赖及解决方式.md.html">Spring 循环依赖及解决方式.md.html</a>
</li>
<li>
<a href="/文章/Spring中眼花缭乱的BeanDefinition.md.html">Spring中眼花缭乱的BeanDefinition.md.html</a>
</li>
<li>
<a href="/文章/Vert.x 基础入门.md.html">Vert.x 基础入门.md.html</a>
</li>
<li>
<a href="/文章/eBay 的 Elasticsearch 性能调优实践.md.html">eBay 的 Elasticsearch 性能调优实践.md.html</a>
</li>
<li>
<a href="/文章/不可不说的Java“锁”事.md.html">不可不说的Java“锁”事.md.html</a>
</li>
<li>
<a href="/文章/互联网并发限流实战.md.html">互联网并发限流实战.md.html</a>
</li>
<li>
<a href="/文章/从ReentrantLock的实现看AQS的原理及应用.md.html">从ReentrantLock的实现看AQS的原理及应用.md.html</a>
</li>
<li>
<a href="/文章/从SpringCloud开始聊微服务架构.md.html">从SpringCloud开始聊微服务架构.md.html</a>
</li>
<li>
<a href="/文章/全面了解 JDK 线程池实现原理.md.html">全面了解 JDK 线程池实现原理.md.html</a>
</li>
<li>
<a href="/文章/分布式一致性理论与算法.md.html">分布式一致性理论与算法.md.html</a>
</li>
<li>
<a href="/文章/分布式一致性算法 Raft.md.html">分布式一致性算法 Raft.md.html</a>
</li>
<li>
<a href="/文章/分布式唯一 ID 解析.md.html">分布式唯一 ID 解析.md.html</a>
</li>
<li>
<a href="/文章/分布式链路追踪:集群管理设计.md.html">分布式链路追踪:集群管理设计.md.html</a>
</li>
<li>
<a href="/文章/动态代理种类及原理,你知道多少?.md.html">动态代理种类及原理,你知道多少?.md.html</a>
</li>
<li>
<a href="/文章/响应式架构与 RxJava 在有赞零售的实践.md.html">响应式架构与 RxJava 在有赞零售的实践.md.html</a>
</li>
<li>
<a href="/文章/大数据算法——布隆过滤器.md.html">大数据算法——布隆过滤器.md.html</a>
</li>
<li>
<a href="/文章/如何优雅地记录操作日志?.md.html">如何优雅地记录操作日志?.md.html</a>
</li>
<li>
<a href="/文章/如何设计一个亿级消息量的 IM 系统.md.html">如何设计一个亿级消息量的 IM 系统.md.html</a>
</li>
<li>
<a href="/文章/异步网络模型.md.html">异步网络模型.md.html</a>
</li>
<li>
<a href="/文章/当我们在讨论CQRS时我们在讨论些神马.md.html">当我们在讨论CQRS时我们在讨论些神马.md.html</a>
</li>
<li>
<a href="/文章/彻底理解 MySQL 的索引机制.md.html">彻底理解 MySQL 的索引机制.md.html</a>
</li>
<li>
<a href="/文章/最全的 116 道 Redis 面试题解答.md.html">最全的 116 道 Redis 面试题解答.md.html</a>
</li>
<li>
<a href="/文章/有赞权限系统(SAM).md.html">有赞权限系统(SAM).md.html</a>
</li>
<li>
<a href="/文章/有赞零售中台建设方法的探索与实践.md.html">有赞零售中台建设方法的探索与实践.md.html</a>
</li>
<li>
<a href="/文章/服务注册与发现原理剖析Eureka、Zookeeper、Nacos.md.html">服务注册与发现原理剖析Eureka、Zookeeper、Nacos.md.html</a>
</li>
<li>
<a href="/文章/深入浅出Cache.md.html">深入浅出Cache.md.html</a>
</li>
<li>
<a href="/文章/深入理解 MySQL 底层实现.md.html">深入理解 MySQL 底层实现.md.html</a>
</li>
<li>
<a href="/文章/漫画讲解 git rebase VS git merge.md.html">漫画讲解 git rebase VS git merge.md.html</a>
</li>
<li>
<a href="/文章/生成浏览器唯一稳定 ID 的探索.md.html">生成浏览器唯一稳定 ID 的探索.md.html</a>
</li>
<li>
<a href="/文章/缓存 如何保证缓存与数据库的双写一致性?.md.html">缓存 如何保证缓存与数据库的双写一致性?.md.html</a>
</li>
<li>
<a href="/文章/网易严选怎么做全链路监控的?.md.html">网易严选怎么做全链路监控的?.md.html</a>
</li>
<li>
<a href="/文章/美团万亿级 KV 存储架构与实践.md.html">美团万亿级 KV 存储架构与实践.md.html</a>
</li>
<li>
<a href="/文章/美团点评Kubernetes集群管理实践.md.html">美团点评Kubernetes集群管理实践.md.html</a>
</li>
<li>
<a href="/文章/美团百亿规模API网关服务Shepherd的设计与实现.md.html">美团百亿规模API网关服务Shepherd的设计与实现.md.html</a>
</li>
<li>
<a href="/文章/解读《阿里巴巴 Java 开发手册》背后的思考.md.html">解读《阿里巴巴 Java 开发手册》背后的思考.md.html</a>
</li>
<li>
<a href="/文章/认识 MySQL 和 Redis 的数据一致性问题.md.html">认识 MySQL 和 Redis 的数据一致性问题.md.html</a>
</li>
<li>
<a href="/文章/进阶Dockerfile 高阶使用指南及镜像优化.md.html">进阶Dockerfile 高阶使用指南及镜像优化.md.html</a>
</li>
<li>
<a href="/文章/铁总在用的高性能分布式缓存计算框架 Geode.md.html">铁总在用的高性能分布式缓存计算框架 Geode.md.html</a>
</li>
<li>
<a href="/文章/阿里云PolarDB及其共享存储PolarFS技术实现分析.md.html">阿里云PolarDB及其共享存储PolarFS技术实现分析.md.html</a>
</li>
<li>
<a href="/文章/阿里云PolarDB及其共享存储PolarFS技术实现分析.md.html">阿里云PolarDB及其共享存储PolarFS技术实现分析.md.html</a>
</li>
<li>
<a href="/文章/面试最常被问的 Java 后端题.md.html">面试最常被问的 Java 后端题.md.html</a>
</li>
<li>
<a href="/文章/领域驱动设计在互联网业务开发中的实践.md.html">领域驱动设计在互联网业务开发中的实践.md.html</a>
</li>
<li>
<a href="/文章/领域驱动设计的菱形对称架构.md.html">领域驱动设计的菱形对称架构.md.html</a>
</li>
<li>
<a href="/文章/高效构建 Docker 镜像的最佳实践.md.html">高效构建 Docker 镜像的最佳实践.md.html</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>MySQL中的事务和MVCC</h1>
<p>虽然我们不是DBA可能对数据库没那么了解但是对于数据库中的索引、事务、锁我们还是必须要有一个较为浅显的认识今天我就和大家聊聊事务。</p>
<h3>为什么要有事务</h3>
<p>说到事务,不得不提到转账的事情,几乎所有的关于事务的文章都会提到这个老掉牙的案例,我也不例外。</p>
<p>转账在数据库层面可以简单的抽象成两个部分:</p>
<ul>
<li>从自己的账户中扣除转账金额;</li>
<li>往对方账户中增加转账金额。</li>
</ul>
<p>如果先从自己的账户中扣除转账金额再往对方账户中增加转账金额扣除执行成功增加执行失败那自己的账户白白少了100块欲哭无泪。</p>
<p>如果先往对方账户中增加转账金额再从自己的账户中扣除转账金额增加执行成功扣除执行失败那对方账户白白增加了100块自己的账户也没有扣钱喜大普奔。</p>
<p>不管是让你欲哭无泪,还是喜大普奔,银行都不会容忍这样的事情发生,他们会引入事务来解决这类问题。</p>
<h3>事务的特性</h3>
<ol>
<li>原子性Atomicity事务包含的所有操作要么全部成功提交要么全部失败回滚</li>
<li>一致性Consistency事务的执行的前后数据的完整性保持一致。</li>
<li>隔离性Isolation一个事务执行的过程中不应该受到其他事务的干扰。</li>
<li>持久性Durability事务一旦结束数据就持久到数据库即使提交后数据库发生崩溃也不会丢失提交的数据。</li>
</ol>
<p>四种特性简称ACID其中最不好理解的就是一致性有不少人认为原子性、隔离性、持久性就是为了保证一致性我们也不搞学术研究一致性到底该怎么解释到底怎么定义一致性就看各位看官的了。</p>
<h3>事务的隔离级别</h3>
<p>从某个角度来说,我们可以控制的、或者说需要研究的只有隔离性这一个特性,而要控制隔离性,几乎只有调整隔离级别这一个手段,下面我们就来看看事务的隔离级别。</p>
<p>数据库是一个客户端/服务器架构的软件每个客户端与服务器连接后就会产生一个session会话客户端和服务器的交互就是在session中进行的理论上来说如果服务器同时只能处理一个事务其他的事务都排队等待当该事务提交后服务器才处理下一个事务这样才真正具有“隔离性”什么问题都没有了但是如果是这样性能就太差了在性能和隔离性之间只能做一些平衡所以数据库提供了好几个隔离级别供我们选择。</p>
<p>在讲隔离级别之前,我们先来看看事务并发执行会遇到什么问题。</p>
<p>为了保证下面的叙述可以顺利进行,我们要先建一张表:</p>
<pre><code>CREATE TABLE `student` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL COMMENT '姓名',
`age` int(11) DEFAULT NULL COMMENT '年龄',
`grade` int(11) DEFAULT NULL COMMENT '年级',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;
</code></pre>
<h4>脏写</h4>
<p><img src="assets/15100432-64db941b7108e9b8.png" alt="image.png" />
如图所示:</p>
<ol>
<li>sessionA和sessionB开启了一个事务</li>
<li>sessionB把id=2的name修改成了“地底王”</li>
<li>sessionA把id=2的name修改成了“梦境地底王”</li>
<li>sessionB回滚了事务</li>
<li>sessionA提交了事务。</li>
</ol>
<p>如果sessionB在回滚事务的时候把sessionA的修改也给回滚了导致sessionA的提交丢失了这种现象就被称为“脏写”。sessionA会一脸懵逼我明明修改了数据也提交了数据为什么数据没有变化呢。</p>
<h4>脏读</h4>
<p><img src="assets/15100432-bb45dfb5d2cdaaae.png" alt="image.png" />
如图所示:</p>
<ol>
<li>sessionA和sessionB开启了一个事务</li>
<li>sessionB把id=2的name修改成了“地底王”此时还未提交</li>
<li>sessionA查询了id=2的数据如果读出来的数据的name是“地底王”也就是读到了sessionB还没有提交的数据就被称为“脏读”。</li>
</ol>
<h4>不可重复读</h4>
<p><img src="assets/15100432-3e016ee5dd623ca4.png" alt="image.png" />
如图所示:</p>
<ol>
<li>sessionA和sessionB开启了一个事务</li>
<li>sessionA查询id=2的数据假如name是“地底王”</li>
<li>sessionB把id=2的name修改成了“梦境地底王”随后提交了事务</li>
<li>sessionA再一次查询了id=2的数据如果name是“梦境地底王”说明在同一个事务中sessionA前后读到的数据不一致就被称为“不可重复读”。</li>
</ol>
<h4>幻读</h4>
<p><img src="assets/15100432-bb07c982c8449ec0.png" alt="image.png" />
如图所示:</p>
<ol>
<li>sessionA和sessionB开启了一个事务</li>
<li>sessionA查询name=“地底王”的数据,假设此时读到了一条记录;</li>
<li>sessionB又插入一条name=“地底王”的数据,随后提交;</li>
<li>seesionA再一次查询name=“地底王”的数据,如果此时读到了两条记录,第二次查询读到了第一次查询未查询出来的数据,就被称为“幻读”。</li>
</ol>
<h4>四种隔离级别</h4>
<p>我们知道了在并发执行事务的时候,会遇到什么问题,有些问题比较严重,有些问题比较轻微,一般来说,我们认为按照严重性排序是这样的:</p>
<p>脏写&gt;脏读&gt;不可重复读&gt;幻读</p>
<p>在SQL标准定义中设定了四种隔离级别来解决上述的问题</p>
<ul>
<li>未提交读READ UNCOMMITTED
最低的隔离级别,会有“脏读”、“不可重复读”,“幻读”三个问题。</li>
<li>读已提交READ COMMITTED
SQLServer默认隔离级别可以避免“脏读”会有“不可重复读”“幻读”两个问题。</li>
<li>可重复读REPEATABLE READ
可以避免“脏读”,“不可重复读”两个问题,会有“幻读”问题。
MySQL默认隔离级别但是在MySQL中此隔离级别解决了“幻读”问题。</li>
<li>串行化SERIALIZABLE
所有的问题都不会发生。</li>
</ul>
<p>因为脏写的问题实在太严重了,在任何隔离级别下,都不会有脏写的问题。</p>
<h3>MVCC</h3>
<p>前面说的都是开胃菜,相信大部分小伙伴对于上述内容都是手到擒来,所以我连如何修改事务隔离级别都没有介绍,各种实验也都没有做,就是要把大量的时间、文字投入到这一部分内容中来。</p>
<p>MVCC全称是Mutil-Version Concurrency Control翻译成中文是多版本并发控制MySQL就利用了MVCC来判断在一个事务中哪个数据可以被读出来哪个数据不能被读出来。</p>
<h4>多版本</h4>
<p>在看MVCC之前我们有必要知道另外一个知识点数据库存储一行行数据是分为两个部分来存储的一个是数据行的额外信息本篇博客不涉及一个是真实的数据记录MySQL会为每一行真实数据记录添加两三个隐藏的字段</p>
<ul>
<li>row_id
非必须如果表中有自定义的主键或者有Unique键就不会添加row_id字段如果两者都没有MySQL会“自作主张”添加row_id字段。</li>
<li>transaction_id
必须事务Id代表这一行数据是由哪个事务id创建的。</li>
<li>roll_pointer
必须,回滚指针,指向这行数据的上一个版本。</li>
</ul>
<p>如下图所示:
<img src="assets/15100432-33c84c121ca641cf.png" alt="image.png" /></p>
<p>在这里需要着重说明下事务id当我们开启一个事务并不会马上获得事务id哪怕我们在事务中执行select语句也是没有事务id的事务id为0只有执行insert/update/delete语句才能获得事务id这一点尤为重要。</p>
<p>其中和MVCC紧密相关的是transaction_id和roll_pointer两个字段在开发过程中我们无需关心但是要研究MVCC我们必须关心。</p>
<p>如果有类似这样的一行数据:
<img src="assets/15100432-38454275d5db4109.png" alt="image.png" />
代表这行数据是由transaction_id为9的事务创建出来的roll_pointer是空的因为这是一条新纪录。</p>
<p><em>实际上roll_pointer并不是空的如果真要解释需要绕一大圈理解成空的问题也不大。</em></p>
<p>当我们开启事务,对这条数据进行修改,会变成这样:
<img src="assets/15100432-5de4c1e3c96113a7.png" alt="image.png" /></p>
<p>有点感觉了吧这就像一个单向链表称之为“版本链”最上面的数据是这个数据的最新版本roll_pointer指向这个数据的旧版本给人的感觉就是一行数据有多个版本是不是符合“多版本并发控制”中的“多版本”这个概念
那么“并发控制”又是怎么做到的呢,别急,继续往下看。</p>
<h4>ReadView</h4>
<p>下面又要引出一个新的概念ReadView。</p>
<p>对于READ UNCOMMITTED来说可以读取到其他事务还没有提交的数据所以直接把这个数据的最新版本读出来就可以了对于SERIALIZABLE来说是用加锁的方式来访问记录。</p>
<p>剩下的就是READ COMMITTED和REPEATABLE READ这两个事务隔离级别都要保证读到的数据是其他事务已经提交的也就是不能无脑把一行数据的最新版本给读出来了但是这两个还是有一定的区别最核心的问题就在于“我到底可以读取这个数据的哪个版本”。</p>
<p>为了解决这个问题ReadView的概念就出现了ReadView包含四个比较重要的内容</p>
<ul>
<li>m_ids表示在生成ReadView时系统中活跃的事务id集合。</li>
<li>min_trx_id表示在生成ReadView时系统中活跃的最小事务id也就是 m_ids中的最小值。</li>
<li>max_trx_id表示在生成ReadView时系统应该分配给下一个事务的id。</li>
<li>creator_trx_id表示生成该ReadView的事务id。</li>
</ul>
<p>有了这个ReadView只要按照下面的判断方式就可以解决“我到底可以读取这个数据的哪个版本”这个千古难题了</p>
<ul>
<li>如果被访问的版本的trx_id和ReadView中的creator_trx_id相同就意味着当前版本就是由你“造成”的可以读出来。</li>
<li>如果被访问的版本的trx_id小于ReadView中的min_trx_id表示生成该版本的事务在创建ReadView的时候已经提交了所以该版本可以读出来。</li>
<li>如果被访问版本的trx_id大于或等于ReadView中的max_trx_id值说明生成该版本的事务在当前事务生成ReadView后才开启所以该版本不可以被读出来。</li>
<li>如果生成被访问版本的trx_id在min_trx_id和max_trx_id之间那就需要判断下trx_id在不在m_ids中如果在说明创建ReadView的时候生成该版本的事务还是活跃的没有被提交该版本不可以被读出来如果不在说明创建ReadView的时候生成该版本的事务已经被提交了该版本可以被读出来。</li>
</ul>
<p>如果某个数据的最新版本不可以被读出来就顺着roll_pointer找到该数据的上一个版本继续做如上的判断以此类推如果第一个版本也不可见的话代表该数据对当前事务完全不可见查询结果就不包含这条记录了。</p>
<p>看完上面的描述,是不是觉得“云里雾里”,“不知所云”,甚至“脑阔疼,整个人都不好了”。</p>
<p>我们换个方法来解释,看会不会更容易理解点:
<img src="assets/15100432-3c3f7dbe386391b2.png" alt="image.png" />
在事务启动的一瞬间执行CURD操作会创建出ReadView对于一个数据版本的trx_id来说有以下三种情况</p>
<ul>
<li>如果落在低水位,表示生成这个版本的事务已经提交了,或者是当前事务自己生成的,这个版本可见。</li>
<li>如果落在高水位,表示生成这个版本的事务是未来才创建的,这个版本不可见。</li>
<li>如果落在中间水位,包含两种情况:
a. 如果当前版本的trx_id在活跃事务列表中代表这个版本是由还没有提交的事务生成的这个版本不可见
b. 如果当前版本的trx_id不在活跃事务列表中代表这个版本是由已经提交的事务生成的这个版本可见。</li>
</ul>
<p>上面我比较简单的解释了下ReadView用了两种方式来说明如何判断当前数据版本是否可见不知道各位看官是不是有了一个比较模糊的概念有了ReadView的基本概念我们就可以具体看下READ COMMITTED、REPEATABLE READ这两个事务隔离级别为什么读到的数据是不同的以及上述规则是如何应用的。</p>
<h4>READ COMMITTED——每次读取数据都会创建ReadView</h4>
<p>假设现在系统只有一个活跃的事务T事务id是100事务中修改了数据但是还没有提交形成的版本链是这样的
<img src="assets/15100432-cae882ecd4fc0a0a.png" alt="image.png" /></p>
<p>现在A事务启动并且执行了select语句此时会创建出一个ReadViewm_ids是【100】min_trx_id是100 max_trx_id是101creator_trx_id是0。</p>
<p><em>为什么m_ids只有一个为什么creator_trx_id是0这里再次强调下只有在事务中执行insert/update/delete语句才能获得事务id。</em></p>
<p>那么A事务执行的select语句会读到什么数据呢</p>
<ol>
<li>判断最新的数据版本name是“梦境地底王”对应的trx_id是100trx_id在m_ids里面说明当前事务是活跃事务这个数据版本是由还没有提交的事务创建的所以这个版本不可见。</li>
<li>顺着roll_pointer找到这个数据的上一个版本name是“地底王”对应的trx_id是99而ReadView中的min_trx_id是100trx_id&lt;min_trx_id代表当前数据版本是由已经提交的事务创建的该版本可见。</li>
</ol>
<p>所以读到的数据的name是“地底王”。</p>
<p>我们把事务T提交了事务A再次执行select语句此时事务A再次创建出ReadViewm_ids是【】min_trx_id是0 max_trx_id是101creator_trx_id是0。</p>
<p><em>因为事务T已经提交了所以没有活跃的事务。</em></p>
<p>那么事务A第二次执行select语句又会读到什么数据呢</p>
<ol>
<li>判断最新的数据版本name是“梦境地底王”对应的trx_id是100不在m_ids里面说明这个数据版本是由已经提交的事务创建的该版本可见。</li>
</ol>
<p>所以读到的数据的name是“梦境地底王”。</p>
<h4>REPEATABLE READ ——首次读取数据会创建ReadView</h4>
<p>假设现在系统只有一个活跃的事务T事务id是100事务中修改了数据但是还没有提交形成的版本链是这样的
<img src="https://upload-images.jianshu.io/upload_images/15100432-cae882ecd4fc0a0a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="image.png" /></p>
<p>现在A事务启动并且执行了select语句此时会创建出一个ReadViewm_ids是【100】min_trx_id是100 max_trx_id是101creator_trx_id是0。</p>
<p>那么A事务执行的select语句会读到什么数据呢</p>
<ol>
<li>判断最新的数据版本name是“梦境地底王”对应的trx_id是100trx_id在m_ids里面说明当前事务是活跃事务这个数据版本是由还没有提交的事务创建的所以这个版本不可见。</li>
<li>顺着roll_ponit找到这个数据的上一个版本name是“地底王”对应的trx_id是99而ReadView中的min_trx_id是100trx_id&lt;min_trx_id代表当前数据版本是由已经提交的事务创建的该版本可见。</li>
</ol>
<p>所以读到的数据的name是“地底王”。</p>
<p><em>细心的你一定发现了这里我就是复制粘贴因为在REPEATABLE READ事务隔离级别下事务A首次执行select语句创建出来的ReadView和在READ COMMITTED事务隔离级别下事务A首次执行select语句创建出来的ReadView是一样的所以判断流程也是一样的所以我就偷懒了copy走起。</em></p>
<p>随后事务T提交了事务由于REPEATABLE READ是首次读取数据才会创建ReadView所以事务A再次执行select语句不会再创建ReadView用的还是上一次的ReadView所以判断流程和上面也是一样的所以读到的name还是“地底王”。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/文章/MySQL 的半同步是什么?.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/文章/MySQL事务_事务隔离级别详解.md.html">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"70998006cb708b66","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>