当前位置: 首页 > news >正文

03 | 事务隔离:为什么你改了我还看不见?

提到事务,你肯定不陌生,和数据库打交道的时候,我们总是会用到事务。最经典的例子就是转账,你要给朋友小王转 100 块钱,而此时你的银行卡只有 100 块钱。

转账过程具体到程序里会有一系列的操作,比如查询余额、做加减法、更新余额等,这些操作必须保证是一体的,不然等程序查完之后,还没做减法之前,你这 100 块钱,完全可以借着这个时间差再查一次,然后再给另外一个朋友转账,如果银行这么整,不就乱了么?这时就要用到“事务”这个概念了。

简单来说,事务就是要保证一组数据库操作,要么全部成功,要么全部失败。在 MySQL 中,事务支持是在引擎层实现的。你现在知道,MySQL 是一个支持多引擎的系统,但并不是所有的引擎都支持事务。比如 MySQL 原生的 MyISAM 引擎就不支持事务,这也是 MyISAM 被 InnoDB 取代的重要原因之一。

今天的文章里,我将会以 InnoDB 为例,剖析 MySQL 在事务支持方面的特定实现,并基于原理给出相应的实践建议,希望这些案例能加深你对 MySQL 事务原理的理解。

隔离性与隔离级别

提到事务,你肯定会想到 ACID(Atomicity、Consistency、Isolation、Durability,即原子性、一致性、隔离性、持久性),今天我们就来说说其中 I,也就是“隔离性”。

当数据库上有多个事务同时执行的时候,就可能出现脏读(dirty read)、不可重复读(non-repeatable read)、幻读(phantom read)的问题,为了解决这些问题,就有了“隔离级别”的概念。

在谈隔离级别之前,你首先要知道,你隔离得越严实,效率就会越低。因此很多时候,我们都要在二者之间寻找一个平衡点。SQL 标准的事务隔离级别包括:读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(serializable )。下面我逐一为你解释:

  • 读未提交是指,一个事务还没提交时,它做的变更就能被别的事务看到。
  • 读提交是指,一个事务提交之后,它做的变更才会被其他事务看到。
  • 可重复读是指,一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。
  • 串行化,顾名思义是对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。

其中“读提交”和“可重复读”比较难理解,所以我用一个例子说明这几种隔离级别。假设数据表 T 中只有一列,其中一行的值为 1,下面是按照时间顺序执行两个事务的行为。

mysql> create table T(c int) engine=InnoDB;
insert into T(c) values(1);

 我们来看看在不同的隔离级别下,事务 A 会有哪些不同的返回结果,也就是图里面 V1、V2、V3 的返回值分别是什么。

  • 若隔离级别是“读未提交”,则 V1 的值就是 2。这时候事务 B 虽然还没有提交,但是结果已经被 A 看到了。因此,V2、V3 也都是 2。
  • 若隔离级别是“读提交”,则 V1 是 1,V2 的值是 2。事务 B 的更新在提交后才能被 A 看到。所以, V3 的值也是 2。
  • 若隔离级别是“可重复读”,则 V1、V2 是 1,V3 是 2。之所以 V2 还是 1,遵循的就是这个要求:事务在执行期间看到的数据前后必须是一致的。
  • 若隔离级别是“串行化”,则在事务 B 执行“将 1 改成 2”的时候,会被锁住。直到事务 A 提交后,事务 B 才可以继续执行。所以从 A 的角度看, V1、V2 值是 1,V3 的值是 2。

在实现上,数据库里面会创建一个视图,访问的时候以视图的逻辑结果为准。在“可重复读”隔离级别下,这个视图是在事务启动时创建的,整个事务存在期间都用这个视图。在“读提交”隔离级别下,这个视图是在每个 SQL 语句开始执行的时候创建的。这里需要注意的是,“读未提交”隔离级别下直接返回记录上的最新值,没有视图概念;而“串行化”隔离级别下直接用加锁的方式来避免并行访问。

我们可以看到在不同的隔离级别下,数据库行为是有所不同的。Oracle 数据库的默认隔离级别其实就是“读提交”,因此对于一些从 Oracle 迁移到 MySQL 的应用,为保证数据库隔离级别的一致,你一定要记得将 MySQL 的隔离级别设置为“读提交”。

配置的方式是,将启动参数 transaction-isolation 的值设置成 READ-COMMITTED。你可以用 show variables 来查看当前的值。

mysql> show variables like 'transaction_isolation';

+-----------------------+----------------+

| Variable_name | Value |

+-----------------------+----------------+

| transaction_isolation | READ-COMMITTED |

+-----------------------+----------------+

总结来说,存在即合理,每种隔离级别都有自己的使用场景,你要根据自己的业务情况来定。我想你可能会问那什么时候需要“可重复读”的场景呢?我们来看一个数据校对逻辑的案例。 

假设你在管理一个个人银行账户表。一个表存了账户余额,一个表存了账单明细。到了月底你要做数据校对,也就是判断上个月的余额和当前余额的差额,是否与本月的账单明细一致。你一定希望在校对过程中,即使有用户发生了一笔新的交易,也不影响你的校对结果。

这时候使用“可重复读”隔离级别就很方便。事务启动时的视图可以认为是静态的,不受其他事务更新的影响。

事务隔离的实现

理解了事务的隔离级别,我们再来看看事务隔离具体是怎么实现的。这里我们展开说明“可重复读”。

在 MySQL 中,实际上每条记录在更新的时候都会同时记录一条回滚操作。记录上的最新值,通过回滚操作,都可以得到前一个状态的值。

假设一个值从 1 被按顺序改成了 2、3、4,在回滚日志里面就会有类似下面的记录。

当前值是 4,但是在查询这条记录的时候,不同时刻启动的事务会有不同的 read-view。如图中看到的,在视图 A、B、C 里面,这一个记录的值分别是 1、2、4,同一条记录在系统中可以存在多个版本,就是数据库的多版本并发控制(MVCC)。对于 read-view A,要得到 1,就必须将当前值依次执行图中所有的回滚操作得到。 

同时你会发现,即使现在有另外一个事务正在将 4 改成 5,这个事务跟 read-view A、B、C 对应的事务是不会冲突的。

你一定会问,回滚日志总不能一直保留吧,什么时候删除呢?答案是,在不需要的时候才删除。也就是说,系统会判断,当没有事务再需要用到这些回滚日志时,回滚日志会被删除。

什么时候才不需要了呢?就是当系统里没有比这个回滚日志更早的 read-view 的时候。

基于上面的说明,我们来讨论一下为什么建议你尽量不要使用长事务。

长事务意味着系统里面会存在很老的事务视图。由于这些事务随时可能访问数据库里面的任何数据,所以这个事务提交之前,数据库里面它可能用到的回滚记录都必须保留,这就会导致大量占用存储空间。

在 MySQL 5.5 及以前的版本,回滚日志是跟数据字典一起放在 ibdata 文件里的,即使长事务最终提交,回滚段被清理,文件也不会变小。我见过数据只有 20GB,而回滚段有 200GB 的库。最终只好为了清理回滚段,重建整个库。

除了对回滚段的影响,长事务还占用锁资源,也可能拖垮整个库

事务的启动方式

如前面所述,长事务有这些潜在风险,我当然是建议你尽量避免。其实很多时候业务开发同学并不是有意使用长事务,通常是由于误用所致。MySQL 的事务启动方式有以下几种:

1. 显式启动事务语句, begin 或 start transaction。配套的提交语句是 commit,回滚语句是 rollback。

2. set autocommit=0,这个命令会将这个线程的自动提交关掉。意味着如果你只执行一个 select 语句,这个事务就启动了,而且并不会自动提交。这个事务持续存在直到你主动执行 commit 或 rollback 语句,或者断开连接。

有些客户端连接框架会默认连接成功后先执行一个 set autocommit=0 的命令。这就导致接下来的查询都在事务中,如果是长连接,就导致了意外的长事务。

因此,我会建议你总是使用 set autocommit=1, 通过显式语句的方式来启动事务。

小结

这篇文章里面,我介绍了 MySQL 的事务隔离级别的现象和实现,根据实现原理分析了长事务存在的风险,以及如何用正确的方式避免长事务。希望我举的例子能够帮助你理解事务,并更好地使用 MySQL 的事务特性。

相关文章:

03 | 事务隔离:为什么你改了我还看不见?

提到事务,你肯定不陌生,和数据库打交道的时候,我们总是会用到事务。最经典的例子就是转账,你要给朋友小王转 100 块钱,而此时你的银行卡只有 100 块钱。 转账过程具体到程序里会有一系列的操作,比如查询余…...

Jmeter读取与使用Redis数据

Jmeter 作为当前非常受欢迎的接口测试和性能测试的工具,在企业中得到非常广泛的使用,而 Redis 作为缓存数据库,也在企业中得到普遍使用, Redis服务和客户端安装 windows下安装 默认端口:6379 下载地址: …...

flask 支持跨域访问 非常简单的方式 flask_cors

安装 pip install -U flask-cors from flask import Flask from flask_cors import CORSapp Flask(__name__) CORS(app)app.route("/") def helloWorld():return "Hello, cross-origin-world!"参考 https://www.cnblogs.com/anxminise/p/9814326.html …...

Hololens 2应用开发系列(1)——使用MRTK在Unity中设置混合现实场景并进行程序模拟

Hololens 2应用开发系列(1)——使用MRTK在Unity中进行程序模拟 一、前言二、创建和设置MR场景三、MRTK输入模拟的开启 一、前言 在前面的文章中,我介绍了Hololens 2开发环境搭建和项目生成部署等相关内容,使我们能生成一个简单Ho…...

Newtonsoft.Json

目录 引言 1、简单使用 1.1、官方案例 1.2、JsonConvert 2、特性 2.1、默认模式[JsonObject(MemberSerialization.OptIn/OptOut)] 2.2、序列化为集合JsonArrayAttribute/JsonDictionaryAttribute 2.3、序列化该元素JsonProperty 2.4、忽略元素JsonIgnoreAttribute 2.5、…...

速卖通平台的API返回结果有哪些数据字段?

速卖通(AliExpress)作为阿里巴巴旗下的国际电商平台,提供了API接口供开发者使用,以获取商品、订单、物流等各种信息。然而,速卖通API返回的具体数据字段可能会随着API版本、接口类型以及时间的变化而有所不同。 在编写…...

C++ 标准模板库(STL)

1、vector 动态数组,可随时添加删除元素,在堆空间开辟内存。 方法含义front() 返回第一个元素O(1) back()返回最后一个元素O(1)pop_back()删除最后一个元素O(1)push_back(ele)在末尾插入元素O(1)size()返回实际元素个数O(1)clear()清除所有元素O(N)resi…...

【Javascript】设计模式之发布订阅模式

文章目录 1、现实中的发布-订阅模式2、DOM 事件3、简单的发布-订阅模式4、通用的发布-订阅模式5、先发布再订阅6、小结 发布—订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于…...

DataLoader

import torchvision from torch.utils.data import DataLoader from torch.utils.tensorboard import SummaryWriter# 准备的测试数据集 数据放在了CIFAR10文件夹下test_data torchvision.datasets.CIFAR10("./CIFAR10",trainFalse, transformtorchvision.transfor…...

持续集成(CICD)- Jenkins+Git+gogs综合实战(笔记二)

文章目录 七、拉取代码方法一:直接填写命令进行拉取(不建议用这种)方法二:使用源码管理拉取代码步骤一:确认环境(检查自己是否有Git插件)步骤二:构建项目时对项目的源码管理选择 Git步骤三:输入你仓库的SSH地址或者https地址,并且添加gitee的用户名和密方法一和方法二…...

VUE:key属性的作用

在 Vue.js 中,key属性的主要作用是帮助 Vue 在进行 DOM 更新时,能够更准确地识别哪些节点可以复用。 当key值发生变化时,Vue 会执行以下步骤: 1.查找旧节点:Vue 会查找虚拟 DOM 中具有旧key值的节点。 2.匹配新节点…...

linux的通信方案(SYSTEM V)

文章目录 共享内存(Share Memory)信号队列(Message Queue)信号量(semaphore) 进程间通信的核心理念:让不同的进程看见同一块资源 linux下的通信方案: SYSTEM V 共享内存(Share Memory) 特点:1.共享内存是进程见通信最…...

VUE 入门及应用 ( 路由 router )

6.前端路由 router Vue Router | Vue.js 的官方路由 (vuejs.org) 官方地址 : https://router.vuejs.org/zh/ 6.1.基本配置 6.1.0.准备 MyPage.vue 创建 用于测试 vue文件 ../views/MyPage.vue <template><div><h1>MyPage</h1></div> </…...

SpringBoot集成RocketMQ

RocketMQ是一个纯Java、分布式、队列模型的开源消息中间件&#xff0c;前身是MetaQ&#xff0c;是阿里参考Kafka特点研发的一个队列模型的消息中间件&#xff0c;后开源给apache基金会成为了apache的顶级开源项目&#xff0c;具有高性能、高可靠、高实时、分布式特点。 环境搭…...

【Web】关于FastJson反序列化开始前的那些前置知识

目录 FastJson介绍 FJ序列化与反序列化方法 关于反序列化三种方式的关系与区别 FastJson反序列化漏洞原理通识 关于getter&setter FastJson介绍 FastJson&#xff08;快速JSON&#xff09;是一个Java语言编写的高性能、功能丰富且易于使用的JSON解析和序列化库。它由…...

工业镜头的重要参数之视场、放大倍率、芯片尺寸--51camera

今天来简单介绍下工业镜头中常用的参数中的三个&#xff1a; 1、视场 视场&#xff08;FOV&#xff09;也称视野,是指能被视觉系统观察到的物方可视范围。 对于镜头而言&#xff0c;可观察到的视场跟镜头放大倍率及相机芯片选择有关。因此需要根据被观察物体的尺寸&#xff…...

基于java springboot+redis网上水果超市商城设计和实现以及文档

基于java springbootredis网上水果超市商城设计和实现以及文档 博主介绍&#xff1a;多年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 央顺技术团队 Java毕设项目精品实战案例《1000套》 欢迎点赞 收藏 ⭐留…...

3. 在Go语言项目中使用Zap日志库

文章目录 一、介绍二、 默认的Go Logger1. 实现Go Logger2. 设置Logger3. 使用Logger4. Logger的运行5. Go Logger的优势和劣势 三、Uber-go Zap1. 为什么选择Uber-go zap2. 安装3. 配置Zap Logger4. 定制logger4.1 将日志写入文件而不是终端4.2 将JSON Encoder更改为普通的Log…...

想要节省成本,哪个品牌的https证书值得考虑?

为了确保网站数据传输安全&#xff0c;启用HTTPS加密是关键步骤。在众多SSL证书供应商中&#xff0c;如何找到价格合理且品质优良的HTTPS加密证书呢&#xff1f;本文将探讨这个问题&#xff0c;并重点关注具有高性价比优势的沃通CA。 沃通CA作为业内知名的SSL证书服务商&#x…...

R语言及其开发环境简介

R语言及其开发环境简介 R 语言历史 R 语言来自 S 语言&#xff0c;是 S 语言的一个变种。S语言由贝尔实验室研究开发&#xff0c;著名的 C 语言、Unix 系统也是贝尔实验室开发的。R 属于 GNU 开源软件&#xff0c;最初发布于1997年&#xff0c;实现了与 S 语言基本相同的功能…...

JavaSec-RCE

简介 RCE(Remote Code Execution)&#xff0c;可以分为:命令注入(Command Injection)、代码注入(Code Injection) 代码注入 1.漏洞场景&#xff1a;Groovy代码注入 Groovy是一种基于JVM的动态语言&#xff0c;语法简洁&#xff0c;支持闭包、动态类型和Java互操作性&#xff0c…...

【JavaEE】-- HTTP

1. HTTP是什么&#xff1f; HTTP&#xff08;全称为"超文本传输协议"&#xff09;是一种应用非常广泛的应用层协议&#xff0c;HTTP是基于TCP协议的一种应用层协议。 应用层协议&#xff1a;是计算机网络协议栈中最高层的协议&#xff0c;它定义了运行在不同主机上…...

React Native 开发环境搭建(全平台详解)

React Native 开发环境搭建&#xff08;全平台详解&#xff09; 在开始使用 React Native 开发移动应用之前&#xff0c;正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南&#xff0c;涵盖 macOS 和 Windows 平台的配置步骤&#xff0c;如何在 Android 和 iOS…...

Go 语言接口详解

Go 语言接口详解 核心概念 接口定义 在 Go 语言中&#xff0c;接口是一种抽象类型&#xff0c;它定义了一组方法的集合&#xff1a; // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的&#xff1a; // 矩形结构体…...

涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战

“&#x1f916;手搓TuyaAI语音指令 &#x1f60d;秒变表情包大师&#xff0c;让萌系Otto机器人&#x1f525;玩出智能新花样&#xff01;开整&#xff01;” &#x1f916; Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制&#xff08;TuyaAI…...

Angular微前端架构:Module Federation + ngx-build-plus (Webpack)

以下是一个完整的 Angular 微前端示例&#xff0c;其中使用的是 Module Federation 和 npx-build-plus 实现了主应用&#xff08;Shell&#xff09;与子应用&#xff08;Remote&#xff09;的集成。 &#x1f6e0;️ 项目结构 angular-mf/ ├── shell-app/ # 主应用&…...

招商蛇口 | 执笔CID,启幕低密生活新境

作为中国城市生长的力量&#xff0c;招商蛇口以“美好生活承载者”为使命&#xff0c;深耕全球111座城市&#xff0c;以央企担当匠造时代理想人居。从深圳湾的开拓基因到西安高新CID的战略落子&#xff0c;招商蛇口始终与城市发展同频共振&#xff0c;以建筑诠释对土地与生活的…...

C#中的CLR属性、依赖属性与附加属性

CLR属性的主要特征 封装性&#xff1a; 隐藏字段的实现细节 提供对字段的受控访问 访问控制&#xff1a; 可单独设置get/set访问器的可见性 可创建只读或只写属性 计算属性&#xff1a; 可以在getter中执行计算逻辑 不需要直接对应一个字段 验证逻辑&#xff1a; 可以…...

【JVM面试篇】高频八股汇总——类加载和类加载器

目录 1. 讲一下类加载过程&#xff1f; 2. Java创建对象的过程&#xff1f; 3. 对象的生命周期&#xff1f; 4. 类加载器有哪些&#xff1f; 5. 双亲委派模型的作用&#xff08;好处&#xff09;&#xff1f; 6. 讲一下类的加载和双亲委派原则&#xff1f; 7. 双亲委派模…...

GitHub 趋势日报 (2025年06月06日)

&#x1f4ca; 由 TrendForge 系统生成 | &#x1f310; https://trendforge.devlive.org/ &#x1f310; 本日报中的项目描述已自动翻译为中文 &#x1f4c8; 今日获星趋势图 今日获星趋势图 590 cognee 551 onlook 399 project-based-learning 348 build-your-own-x 320 ne…...