MySQL面试必备三之事务

mysql · 浏览次数 : 0

小编点评

**脏读** 脏读是一种在多个事务中,一个事务读取到其他事务未提交的数据现象。由于一个事务的执行是可能包含多个 SQL 的,在某种事务隔离级别下就可能存在 A 事务执行了 SQL 但是还未提交,这时候 B 事务执行过程中就读取到了 A 事务更改的数据。 **不可重复读不可重复读的现象** 不可重复读指的是在同一个事务中,有两个读取数据的 SQL,这两次读取的数据内容都不一样。这种现象产生的原因在于这两次读取 SQL 的过程中,有其他事务更新了这条数据并提交了。 **幻读** 幻读指的是在同一个事务中,两次查询数据返回的结果的条数不一样,它产生的原因同样是两次查询期间有其他事务提交了,但它的侧重点是其他事务是对数据的插入或者删除。 **隔离级别** * **读未提交(Read Uncommited)**: 一个事务可以读取到其他事务未提交的内容。 * **读已提交(Read Commited)**: 一事务可以读取到其他事务已经提交的数据。 * **可重复读(Repeatable Read)**: 在事务开始的时候会先获取一个当前时刻数据的快照,并且在整个事务的过程中都会从这个快照中读取数据。 * **串行化(Serializable)**: 在串行化隔离级别下,会将分别对读操作和写操作加锁,当一个事务正在执行,其他事务必须等前一个事务执行完毕之后才能执行。 **可重复读隔离级别的问题** 虽然可重复读隔离级别可以解决不可重复读和幻读的问题,但它也无法解决幻读问题。这是因为可重复读隔离级别下的幻读操作,本质上还是加锁,而加锁的目的是为了防止其他事务并发修改数据,而无法解决幻读问题。

正文

本文首发于公众号: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 的整体操作如下:

image

事务 B 的整体操作如下:
image

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

4. 如何解决幻读问题

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

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

与MySQL面试必备三之事务相似的内容:

MySQL面试必备三之事务

本文首发于公众号:Hunter后端 原文链接:MySQL面试必备三之事务 这一篇笔记介绍一下 MySQL 的事务,面试中常被问到关于事务的几个问题如下: 事务是什么 为什么需要事务,事务有什么作用 事务的特点 事务可能带来哪些问题 事务有哪些隔离级别,这些隔离级别都可以解决哪些问题 可重复读隔离级别

MySQL面试必备二之binlog日志

本文首发于公众号:Hunter后端 原文链接:MySQL面试必备二之binlog日志 关于 binlog,常被问到几个面试问题如下: binlog 是什么 binlog 都记录什么数据 binlog 都有哪些类型,都有什么特点 如何使用 binlog 恢复数据 binlog 都有哪些作用 binlo

面试必问:MySQL死锁 是什么,如何解决?(史上最全)

MySQL死锁接触少,但面试又经常被问到怎么办? 最近有小伙伴在面试的时候,被问了MySQL死锁,如何解决? 虽然也回答出来了,但是不够全面体系化, 所以,小北给大家做一下系统化、体系化的梳理,帮助大家在面试过程中能够脱颖而出,拿到自己心仪的Offer 插播一条:如果你近期准备面试跳槽,建议在htt

字节面试:MySQL自增ID用完会怎样?

在一些中小型项目开发中,我们通常会使用自增 ID 来作为主键的生成策略,但随着时间的推移,数据库的信息也会越来越多,尤其是使用自增 ID 作为日志表的主键生成策略时,可能很快就会遇到 ID 被用完的情况,那么如果发生了这种情况,MySQL 又会怎样执行呢? PS:当然,在分库分表的场景中,我们通常会

MySQL面试题全解析:准备面试所需的关键知识点和实战经验

本次种子题目主要涵盖了MySQL的存储引擎和索引结构,如B+树索引和哈希索引,以及覆盖索引和回表的概念。此外,还包含了MySQL事务的ACID特性和隔离级别。另外,对MySQL主从集群中的binlog日志的执行顺序和作用进行了讨论。最后,还涉及了分库分表和读写分离的概念。这些内容涵盖了MySQL数据库的核心知识和重要技术,不仅在面试中起到关键作用还对于优化数据库性能和应用开发都具有重要意义。

为什么MySQL单表不能超过2000万行?

摘要:MySQL一张表最多能存多少数据? 本文分享自华为云社区《为什么MySQL单表不能超过2000万行?》,作者: GaussDB 数据库 。 最近看到一篇《我说MySQL每张表最好不要超过2000万数据,面试官让我回去等通知》的文章,非常有趣。 文中提到,他朋友在面试的过程中说,自己的工作就是把

mini-centos7 环境安装部署,各种踩坑。。。

最小Linux系统,安装Java环境 想想就生气,去面试个运维,面试官让我上机装个centos7,还是个最小安装包连界面都没有,只有命令行模式,我都哭了,然后让把一些环境装一下,然后再部署个springboot项目,我他妈都多久没用没有界面的东西了,最后卡在安装MySQL上,真想扇自己个

MySQL面试题——隔离级别相关面试题

隔离级别相关面试题 MySQL事务隔离级别 未提交读——可以读到其他事务未提交的数据(最新的版本) 错误现象:脏读、不可重复读、幻读的现象 提交读(RC)——可以读到其他事务已提交的数据(最新已提交的版本) 错误现象:不可重复读、幻读现象 使用场景:希望看到最新的有效值 可重复读(RR)——在事务范

[转帖]MySQL优化的5个维度

面试官如果问你:你会从哪些维度进行MySQL性能优化?你会怎么回答?所谓的性能优化,一般针对的是MySQL查询的优化。既然是优化查询,我们自然要先知道查询操作要经过哪些环节,然后思考可以在哪些环节进行优化。我之前写过一条SQL查询语句是如何执行的?,感兴趣的朋友可以阅读一下,我用其中的一张图展示查询

MySQL到TiDB:Hive Metastore横向扩展之路

本文介绍了vivo在大数据元数据服务横向扩展道路上的探索历程,由实际面临的问题出发,对当前主流的横向扩展方案进行了调研及对比测试,通过多方面对比数据择优选择TiDB方案。其次分享了整个扩展方案流程、实施遇到的问题及解决方案,对于在大数据元数据性能上面临同样困境的开发者本篇文章具有非常高的参考借鉴价值...