本文首发于公众号:Hunter后端

原文链接:MySQL面试必备三之事务

这一篇笔记介绍一下 MySQL 的事务,面试中常被问到关于事务的几个问题如下:

  1. 事务是什么
  2. 为什么需要事务,事务有什么作用
  3. 事务的特点
  4. 事务可能带来哪些问题
  5. 事务有哪些隔离级别,这些隔离级别都可以解决哪些问题
  6. 可重复读隔离级别下能否解决幻读问题
  7. 如何解决幻读问题

以下是本篇笔记目录:

  1. 什么是事务
  2. 事务的特性
  3. 事务执行的示例
  4. 并发事务可能带来的问题
  5. 事务的隔离级别
  6. 可重复读隔离级别为什么不可以解决幻读的问题

1、什么是事务

所谓事务,就是一系列的 SQL 组合,这些 SQL 操作要么全部执行,要么都不执行,是一个不可分割的工作单位。

比如我们在支付系统中想要完成一个转账功能,比如从 A 账户转账一百元给 B 账户,那么从 A 账户的总额中减去 100,然后在 B 账户上加上 100,这两个就需要全部执行才算是这个转账操作的实现。

这个过程就包含了 A 账户的减少 100,B 账户的加上 100,这两个操作加起来就是一个完整的事务。

2、事务的特性

事务的特性有四个,为ACID,分别是 A(Atomicity)、C(Consistency)、I(Isolation) 和 D(Duration),分别表示原子性、一致性、隔离性和持久性。

1. 原子性

事务的原子性指的是一个事务中的所有操作要么全部完成,要么全部失败,如果在执行事务的过程中,某个 SQL 执行失败,那么这个事务中之前执行操作全部回滚,恢复到执行事务之前的状态。

2. 一致性

一致性指的是事务执行前后,数据库的状态应该保持一致,即数据库的完整性不会被破坏。

这个一致性的理解为在执行事务前后数据库应该符合事务的约束条件,从而保证数据的正确性。

比如我们设置了某个字段的属性应当大于或等于 0,但在某个操作过程中如果更新该字段的值小于 0,那么则属于破坏了数据的一致性,事务会回滚到执行前,从而保证数据库状态的一致性。

3. 隔离性

隔离性指的是多个事务并发执行时,每个事务都应该独立于其他事务,互不干扰,从而避免数据并发访问引起的问题。

事务的隔离分为多个级别,这个在后面再介绍。

4. 持久性

事务的持久性指的是事务执行完毕之后,对数据的修改就是永久的,即便是系统故障,修改的数据也不会丢失。

3、事务执行的示例

事务的执行过程会包含几个步骤,事务的开始、SQL 操作、提交或者回滚。

1. 事务执行示例

比如我们想要给 id=1 的账号减去一百元,然后给 id=2 的账号加上一百元,使用事务来操作的示例如下:

START TRANSACTION;

UPDATE user_account set money = money - 100 WHERE id = 1;
UPDATE user_account set money = money + 100 WHERE id = 2; COMMIT;

这里,我们通过 START TRANSACTION 开启一个事务,中间执行 SQL 操作,以 COMMIT 提交事务为结束。

2. 回滚操作

如果我们想执行回滚操作,可以直接使用 ROLLBACK

START TRANSACTION;

UPDATE user_account set money = money - 100 WHERE id = 1;
UPDATE user_account set money = money + 100 WHERE id = 2; ROLLBACK;

这里的回滚操作会回滚到事务执行前。

3. 保存点

如果我们的事务包含的 SQL 很长,我们并不想直接回滚到事务开启前,而是事务中间的某个步骤,我们可以使用保存点来进行回滚操作:

-- 开始事务
START TRANSACTION; -- 执行 SQL 操作
UPDATE accounts SET balance = balance - 100 WHERE account_id = 123;
SAVEPOINT before_insert;
INSERT INTO transaction_log (account_id, amount, type) VALUES (123, 100, 'debit'); -- 检查条件
IF some_condition THEN
-- 回滚到保存点
ROLLBACK TO before_insert;
ELSE
-- 提交事务
COMMIT;
END IF;

在这里,我们通过 SAVEPOINT 来创建保存点,并在后面的代码里通过 IF 条件进行判断,选择性的回到该保存点。

一个事务是可以包含多个保存点的。

4. 单条 SQL 的事务

前面几条介绍的都是使用 START TRANSACTION 显式地开始一个事务,而至于单条 SQL 语句,比如 INSERT 或者 UPDATE 这种,在默认情况下是自动提交的,所以不用手动进行 COMMIT 操作,它们也属于单独的事务。

4、并发事务可能带来的问题

在我们访问数据库时,可能同一时刻有多个事务在访问操作数据库,那么这样可能会导致一些问题。

1. 脏读

所谓脏读就是在在某个事务的执行过程中可以读到其他事务未提交的数据,这个现象就是脏读。

因为一个事务的执行是可能包含多个 SQL 的,在某种事务隔离级别下就可能存在 A 事务执行了 SQL 但是还未提交,这时候 B 事务执行过程中就读取到了 A 事务更改的数据。

2. 不可重复读

不可重复读的现象指的是在同一个事务中,有两个读取数据的 SQL,这两次读取的数据内容都不一样,这种现象就称为不可重复读。

这个现象产生的原因在于这两次读取 SQL 的过程中,有其他事务更新了这条数据并提交了。

不可重复读偏重的点在于对数据的修改。

3. 幻读

幻读的现象指的是在同一个事务中,两次查询数据返回的结果的条数不一样,它产生的原因同样是两次查询期间有其他事务提交了,但它的侧重点是其他事务是对数据的插入或者删除。

5、事务的隔离级别

事务的隔离级别分别是读未提交(Read Uncommited)、读已提交(Read Commited)、可重复读(Repeatable Read)、串行化(Serializable)。

1. 读未提交

读未提交指的是一个事务可以读取到其他事务未提交的内容。

在这个隔离级别下,如果有一个事务 A,包含多条 SQL 操作,执行到其中某条 SQL,但是还没有执行 COMMIT 操作,这个时候另一个事务 B 读取事务 A 操作过的 SQL 数据,就可以读取到对应内容,这个过程就是读未提交。

读未提交这个隔离级别可能会造成数据的脏读问题。

2. 读已提交

读已提交指的是事务可以读取到其他事务已经提交的数据,这个隔离级别可以解决脏读问题,但是不可以解决不可重复读和幻读的问题。

比如一个事务 A,在其执行过程中先读取了某条数据,这个时候另一个事务 B 开启一个事务并提交,事务 B 修改了事务 A 前面读取的数据内容,这个时候事务 A 在后面的操作又读取了这条数据,会发现和第一次读的时候数据不一致,这个就是读已提交可能造成的问题。

3. 可重复读

可重复读则是在事务开始的时候会先获取一个当前时刻数据的快照,并且在整个事务的过程中都会从这个快照中读取数据,这个就是可重复读。

可重复读可以解决不可重复读的问题,因为不可重复读针对的是某条数据本身,而可重复读会对数据本身做快照处理,所以可以解决不可重复读的问题。

而为什么不可以解决幻读的问题呢?

这个我们后面再介绍。

4. 串行化

串行化是最高的隔离级别,在这个隔离级别下,会将分别对读操作和写操作加锁,当一个事务正在执行,其他事务必须等前一个事务执行完毕之后才能执行。

在这个隔离级别下,可以解决前面并发事务带来的所有问题,包括不可重复读和幻读,但同时,这种方式也会降低数据库的并发性能,因为事务需要按照其他事务释放锁才能执行。

注意:MySQL 默认的隔离级别是可重复读。

6、可重复读隔离级别为什么不可以解决幻读的问题

接着来说一说为什么可重复读隔离级别不可以解决幻读的问题。

在说明这个问题的原因之前,先来介绍一下快照读和当前读。

1. 快照读和当前读

1) 快照读

快照读指的是在事务开始的时候,事务会创建一个数据的快照,在接下来这个事务的整个过程中,都会使用这个快照来读取数据。

2) 当前读

当前读则是指在读取数据时,直接读取库里最新的数据,而不使用事务开启时创建的快照数据。

2. 使用快照读和当前读的场景

在 MySQL 中,除了普通的 SELECT 查询语句是快照读,UPDATE、INSERT、DELETE 操作都是当前读,也就是对数据进行更新、插入和删除的时候都是会查询到数据库最新的数据然后进行操作。

除此之外,对 SELECT 操作进行加锁操作也是当前读,比如共享锁 select ... lock in share mode 和排他锁 select ... for update,这个我们后面再介绍。

3. 可重复读隔离级别下的幻读操作

我们可以通过下面一个例子来进行阐述,在可重复读隔离级别下幻读操作是如何产生的。

比如有两个事务,分别是事务 A 和事务 B,A 事务开启后,查询数据库中的数据,这个时候事务 B 开启,并且使用 INSERT 插入一条数据并提交,在这之后,A 事务对数据库中的数据进行一个 UPDATE 全量数据的操作,之后再进行一个 SELECT 的操作。

下面的代码示例,我们用前面的 t 序号作为执行的时间:

create table users (
id int not null auto_increment primary key,
name varchar(20) not null
); INSERT INTO users (id, name) values(1, "张三"); -- t1 开启事务A
START TRANSACTION; -- t2 查询数据
SELECT * from users; -- t6 查询数据
SELECT * from users; -- t7 更新全部数据
UPDATE users SET name = "王五" WHERE id >= 1; -- t8 查询数据
SELECT * FROM users; -- t9 提交事务A
COMMIT; -- t3 开启事务B
START TRANSACTION; -- t4 事务B插入一条数据
INSERT INTO users (id, name) values(2, "李四"); -- t5 提交事务B
COMMIT;

对于上面的代码,我们分别开启两个 MySQL 终端然后按照时间顺序执行,可以看到以下输出:

事务 A 的整体操作如下:

事务 B 的整体操作如下:

可以看到事务 A 在第二次进行查询的时候数据就会新增一条,和第一次查询的时候数据不一致了,这个过程就产生了幻读。

4. 如何解决幻读问题

前面介绍了即便是可重复读隔离级别下,也还是会可能产生幻读问题,那么如何解决幻读问题呢,本质上还是加锁,比如在查询数据的时候使用 select for update 操作对查询的数据加上间隙锁,这样就可以避免其他事务插入新的数据,关于锁的概念和使用,我们在后面再详细介绍。

如果想获取更多相关文章,可扫码关注阅读:

MySQL面试必备三之事务的更多相关文章

  1. [转帖]MySQL的又一神器-锁,MySQL面试必备

    MySQL的又一神器-锁,MySQL面试必备 https://segmentfault.com/a/1190000020762791 lock 低一级的是 latch   原文链接:blog.ouya ...

  2. MySQL 系列(三)事务

    MySQL 系列(三)事务 一组要么同时执行成功,要么同时执行失败的 SQL 语句.是数据库操作的一个执行单元! 事务开始于: 连接到数据库上,并执行条 DML 语句(INSERT. UPDATE 或 ...

  3. MySQL 面试必备:又一神器“锁”,不会的在面试都挂了

    1 什么是锁 1.1 锁的概述 在生活中锁的例子多的不能再多了,从古老的简单的门锁,到密码锁,再到现在的指纹解锁,人脸识别锁,这都是锁的鲜明的例子,所以,我们理解锁应该是非常简单的. 再到MySQL中 ...

  4. MySQL 学习(三)事务学习

    事务隔离级别     SQL标准的事务隔离级别包括:读未提交(read uncommitted).读提交(read committed).可重复读(repeatable read)和串行化(seria ...

  5. MySQL数据库之大厂面试必备技能v8.0.27

    概述 **本人博客网站 **IT小神 www.itxiaoshen.com 定义 MySQL官方地址 https://www.mysql.com/ MySQL 8系列最新版本为8.0.27,5系列的最 ...

  6. 可能是全网最好的MySQL重要知识点 | 面试必备

    可能是全网最好的MySQL重要知识点 | 面试必备  mp.weixin.qq.com 点击蓝色“程序猿DD”关注我 回复“资源”获取独家整理的学习资料! 标题有点标题党的意思,但希望你在看了文章之后 ...

  7. 面试必备的10道MySQL题

    MySQL 事务,是我们去面试中高级开发经常会被问到的问题,很多人虽然经常使用 MySQL,SQL 语句也写得很溜,但是面试的时候,被问到这些问题,总是不知从何说起.下面我们先来了解一下什么是 MyS ...

  8. Spring事务专题(三)事务的基本概念,Mysql事务处理原理

    前言 本专题大纲: 我重新整理了大纲,思考了很久,决定单独将MySQL的事务实现原理跟Spring中的事务示例分为两篇文章,因为二者毕竟没有什么实际关系,实际上如果你对MySQL的事务原理不感兴趣也可 ...

  9. MySQL 系列(三)你不知道的 视图、触发器、存储过程、函数、事务、索引、语句

    第一篇:MySQL 系列(一) 生产标准线上环境安装配置案例及棘手问题解决 第二篇:MySQL 系列(二) 你不知道的数据库操作 第三篇:MySQL 系列(三)你不知道的 视图.触发器.存储过程.函数 ...

  10. MySQL学习(三)MySQL锁与事务

    本章我们着重讨论MySQL锁机制的特点,常见的锁问题,以及解决MySQL锁问题的一些方法或建议. 一.MySQL锁概述 相对其他数据库而言,MySQL的锁机制比较简单,其最显著的特点是不同的存储引擎支 ...

随机推荐

  1. Java 包和 API 深度解析:组织代码,避免命名冲突

    Java 包和 API Java 中的包 用于将相关的类分组在一起.可以将其视为文件目录中的一个文件夹.我们使用包来避免名称冲突,并编写更易于维护的代码. 包分为两类: 内置包(来自 Java API ...

  2. 驾考宝典携手HMS Core统一扫码服务,构建复杂场景中的流畅扫码体验

    "驾考宝典"是一款颇具人气的互联网综合驾照考试学习应用,通过强大的驾考功能,在手机移动端为学车学员提供从报名.学习到拿本的全方位驾考服务.作为一个专业的驾培平台,"驾考宝 ...

  3. std::thread 二:互斥量(多个互斥量的解决方法)

    // *:这里的lock是函数模板,最少传两个互斥量 // 第一种,使用 lock 和 unlock std::mutex m_mutex1; std::mutex m_mutex2; std::lo ...

  4. HarmonyOS音频开发指导:使用OpenSL ES开发音频播放功能

      OpenSL ES全称为Open Sound Library for Embedded Systems,是一个嵌入式.跨平台.免费的音频处理库.为嵌入式移动多媒体设备上的应用开发者提供标准化.高性 ...

  5. css 文字溢出省略号

    前言 css 文字溢出后显示省略号,这是一个非常常规的操作,但是你会发现在网上很多给出的例子两行之后显示省略号,却没有用. 这是为什么呢?please look follow. 正文 在一行省略的: ...

  6. leetcode:655. 输出二叉树

    655. 输出二叉树 在一个 m*n 的二维字符串数组中输出二叉树,并遵守以下规则: 1> 行数 m 应当等于给定二叉树的高度. 2> 列数 n 应当总是奇数. 3> 根节点的值(以 ...

  7. oracle SQL 进行时间冲突判断

    oracle SQL 进行时间冲突判断 背景:写一个预约模块,主要的限制就是时间限制,有冲突的时间段就不能进行预约 设数据库中的时间为A开始,A结束 设要判断的时间为B开始,B结束 则判断有在B开始时 ...

  8. 力扣412(java)-Fizz Buzz(简单)

    题目: 给你一个整数 n ,找出从 1 到 n 各个整数的 Fizz Buzz 表示,并用字符串数组 answer(下标从 1 开始)返回结果,其中: answer[i] == "FizzB ...

  9. Dragonfly 基于 P2P 的文件和镜像分发系统

    简介: 业界软件生态在优化 HTTPS 的性能上也做了诸多探索,传统的软件优化方案在软件层面的优化无法满足流量日益增长的速度,CPU 硬件加速成为业界一个通用的解决方案. 作者:孙景文.吴迪   背景 ...

  10. iLogtail 与Filebeat 性能对比

    ​简介:前段时间, iLogtail 阿里千万实例可观测采集器开源,其中介绍了iLogtail采集性能可以达到单核100MB/s,相比开源采集Agent有5-10倍性能优势.很多小伙伴好奇iLogta ...