【MySQL】事务:事务的隔离级别


MySQL是一个 客户端/服务器 架构的软件,对于同一个服务器来说,可以有若干个客户端与之连接,每 个客户端与服务器连接上之后,就可以称为一个会话( Session )。

每个客户端都可以在自己的会话中, 向服务器发出请求语句,一个请求语句可能是某个事务的一部分,也就是对于服务器来说可能同时处理多个事务。

事务有 隔离性 的特性,理论上在某个事务 对某个数据进行访问 时,其他事务应该进行排队 ,当该事务提交之后,其他事务才可以继续访问这个数据。但是这样对 性能影响太大 ,我们既想保持事务的隔离性,又想让服务器在处理访问同一数据的多个事务时, 性能尽量高些 ,那就看二者如何权衡取 舍了。

复制代码
===== 🌟 青柠来相伴,代码更简单。🌟 =====
📚 本文所有内容,我都整理在了博客合集里。👇
🎯 搜索关注【青柠代码录】,即可查看所有博客文章 
===== 🌟 =================  🌟 =====

3.1 数据准备

复制代码
CREATE TABLE student (
    studentno INT,
    name VARCHAR(20),
    class varchar(20),
    PRIMARY KEY (studentno)
) Engine=InnoDB CHARSET=utf8;

然后向这个表里插入一条数据:

INSERT INTO student VALUES(1, '小谷', '1班');

现在表里的数据就是这样的:

复制代码
mysql> select * from student;
+-----------+--------+-------+
| studentno | name   | class |
+-----------+--------+-------+
|      1    |   小谷  | 1班   |
+-----------+--------+-------+
1 row in set (0.00 sec)

3.2 数据并发问题

针对事务的隔离性和并发性,我们怎么做取舍呢?

先看一下访问相同数据的事务,在 不保证串行执行 (也 就是执行完一个再执行另一个)的情况下,可能会出现哪些问题:

1. 脏写( Dirty Write )

四种隔离级别都已经解决了脏写问题。

对于两个事务 Session A、Session B,如果事务Session A 修改了 另一个 未提交 事务Session B 修改过 的数据,那就意味着发生了 脏写,示意图如下:
img

Session A 和 Session B 各开启了一个事务,Sesssion B 中的事务,先将studentno列为1的记录的name列更新为'李四',然后Session A中的事务接着又把这条studentno列为1的记录的name列更新为'张三'。如果之后Session B中的事务进行了回滚,那么Session A中的更新也将不复存在,这种现象称之为脏写。

这时Session A中的事务就没有效果了,明明把数据更新了,最后也提交事务了,最后看到的数据什么变化也没有。

这里大家对事务的隔离性比较了解的话,会发现默认隔离级别下,上面Session A中的更新语句会处于等待状态,这里只是跟大家说明一下会出现这样的现象。

2. 脏读( Dirty Read )

对于两个事务 Session A、Session B,Session A 读取 了已经被 Session B 更新, 但还 没有被提交 的字段。 之后若 Session B 回滚 ,Session A 读取 的内容就是 临时且无效 的。
img

Session A和Session B各开启了一个事务,Session B中的事务,先将studentno列为1的记录的name列更新 为'张三',然后Session A中的事务再去查询这条studentno为1的记录,如果读到列name的值为'张三',而 Session B中的事务稍后进行了回滚,那么Session A中的事务相当于读到了一个不存在的数据,这种现象就称之为 脏读 。

3. 不可重复读( Non-Repeatable Read )

对于两个事务Session A、Session B,Session A 读取****了一个字段,然后 Session B 更新****了该字段。 之后 Session A 再次读取 同一个字段, 值就不同 **了。**那就意味着发生了不可重复读。
img

我们在Session B中提交了几个 隐式事务 (注意是隐式事务,意味着语句结束事务就提交了),这些事务 都修改了studentno列为1的记录的列name的值,每次事务提交之后,如果Session A中的事务都可以查看到最新的值,这种现象也被称之为 不可重复读 。

4. 幻读( Phantom )

对于两个事务Session A、Session B, Session A 从一个表中 读取 了一个字段, 然后 Session B 在该表中 插 入 了一些新的行。 之后, 如果 Session A 再次读取 **同一个表, 就会多出几行。**那就意味着发生了幻读。
img

Session A中的事务先根据条件 studentno > 0这个条件查询表student,得到了name列值为'张三'的记录; 之后Session B中提交了一个 隐式事务 ,该事务向表student中插入了一条新记录;之后Session A中的事务 再根据相同的条件 studentno > 0查询表student,得到的结果集中包含Session B中的事务新插入的那条记 录,这种现象也被称之为 幻读 。我们把新插入的那些记录称之为 幻影记录 。

注意1:

有的同学会有疑问,那如果 Session B 中删除了 一些符合 studentno > 0的记录而不是插入新记录,那Session A之后再根据 studentno > 0的条件读取的 记录变少了,这种现象算不算幻读呢?

这种现象 不属于幻读,幻读强调的是一个事务按照某个 相同条件多次读取 记录时,后读取时读到了之前 没有读到的记录

注意2:

那对于先前已经读到的记录,之后又读取不到这种情况,算啥呢?

这相当于对每一条记录都发生了 不可重复读的现象。幻读只是重点强调了读取到了之前读取没有获取到的记录

3.3 SQL中的四种隔离级别

上面介绍了几种并发事务执行过程中,可能遇到的一些问题,这些问题有轻重缓急之分,我们给这些问题 按照严重性来排一下序:

脏写 > 脏读 > 不可重复读 > 幻读

我们愿意舍弃一部分隔离性,来换取一部分性能在这里就体现在:设立一些隔离级别,隔离级别越低,并发问题发生的就越多。

SQL标准 中设立了4个 隔离级别 :

  • READ UNCOMMITTED :读未提交,在该隔离级别,所有事务都可以看到其他未提交事务的执行结 果。不能避免脏读、不可重复读、幻读。
  • READ COMMITTED :读已提交,它满足了隔离的简单定义:一个事务只能看见已经提交事务所做 的改变。这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。可以避免脏读,但不可 重复读、幻读问题仍然存在。
  • REPEATABLE READ :可重复读,事务A在读到一条数据之后,此时事务B对该数据进行了修改并提 交,那么事务A再读该数据,读到的还是原来的内容。可以避免脏读、不可重复读,但幻读问题仍 然存在。这是MySQL的默认隔离级别。
  • SERIALIZABLE :可串行化,确保事务可以从一个表中读取相同的行。在这个事务持续期间,禁止 其他事务对该表执行插入、更新和删除操作。所有的并发问题都可以避免,但性能十分低下。能避 免脏读、不可重复读和幻读。

SQL标准 中规定,针对不同的隔离级别,并发事务可以发生不同严重程度的问题,具体情况如下:
img

脏写 怎么没涉及到?因为脏写这个问题太严重了,不论是哪种隔离级别,都不允许脏写的情况发生。

不同的隔离级别有不同的现象,并有不同的锁和并发机制,隔离级别越高,数据库的并发性能就越差,4 种事务隔离级别与并发性能的关系如下:
img

3.4 四种隔离级别

MySQL支持的四种隔离级别

不同的数据库厂商,对SOL标准中规定的四种隔离级别支持不一样。比如,Oracle 就只支持 READ COMMITTED(默认隔离级别)和 SERIALIZABLE隔离级别。

MySOL虽然支持4种隔离级别,但与SQL标准中所规定的各级隔离级别允许发生的问题却有些出入,**MySOL在REPEATABLE READ隔离级别下,是可以禁止幻读问题的发生的,**禁止幻读的原因我们在第16章讲解。

MySQL的默认隔离级别为REPEATABLE READ,我们可以手动修改一下事务的隔离级别。

复制代码
# 查看隔离级别,MySQL 5.7.20的版本及之后:
mysql> SHOW VARIABLES LIKE 'transaction_isolation';
+-----------------------+-----------------+
| Variable_name         | Value           |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
+-----------------------+-----------------+
1 row in set (0.02 sec)

#或者不同MySQL版本中都可以使用的:
SELECT @@transaction_isolation;

3、隔离级别

1)对于同时运行的多个事务, 当这些事务访问数据库中相同的数据时, 如果没有采取必要的隔离机制, 就会导致各种并发问题:

  • 脏读: 对于两个事务T1, T2, T1读取了已经被T2更新,但还没有被提交的字段。之后,若T2回滚,T1读取的内容就是临时且无效的.
  • 不可重复读: 对于两个事务T1, T2, T1读取了一个字段, 然后T2更新了该字段。之后,T1再次读取同一个字段,值就不同了.
  • 幻读: 对于两个事务T1, T2, T1从一个表中读取了一个字段, 然后T2在该表中插入了一些新的行之后,如果T1再次读取同一个表, 就会多出几行.

2)数据库事务的隔离性

数据库系统必须具有隔离并发运行各个事务的能力, 使它们不会相互影响, 避免各种并发问题

3)一个事务与其他事务隔离的程度称为隔离级别。 数据库规定了多种事务隔离级别, 不同隔离级别对应不同的干扰程度, 隔离级别越高, 数据一致性就越好, 但并发性越弱

4)4 种事务隔离级别:
img img

Mysql 默认的事务隔离级别为: REPEATABLE READ

3.5 设置隔离级别

通过下面的语句修改事务的隔离级别:

复制代码
SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL 隔离级别;
#其中,隔离级别格式:
> READ UNCOMMITTED
> READ COMMITTED
> REPEATABLE READ
> SERIALIZABLE

或者:

复制代码
SET [GLOBAL|SESSION] TRANSACTION_ISOLATION = '隔离级别'
#其中,隔离级别格式:
> READ-UNCOMMITTED
> READ-COMMITTED
> REPEATABLE-READ
> SERIALIZABLE

关于设置时使用GLOBAL或SESSION的影响:

使用 GLOBAL 关键字(在全局范围影响):

复制代码
SET GLOBAL TRANSACTION ISOLATION LEVEL SERIALIZABLE;
#或
SET GLOBAL TRANSACTION_ISOLATION = 'SERIALIZABLE';

则:

  • 当前已经存在的会话无效
  • 只对执行完该语句之后产生的会话起作用

使用 SESSION 关键字(在会话范围影响):

复制代码
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
#或
SET SESSION TRANSACTION_ISOLATION = 'SERIALIZABLE';

则:

  • 对当前会话的所有后续的事务有效
  • 如果在事务之间执行,则对后续的事务有效
  • 该语句可以在已经开启的事务中间执行,但不会影响当前正在执行的事务

如果在服务器启动时,想改变事务的默认隔离级别,可以修改启动参数transaction_isolation的值。

比如,在启动服务器时指定了transaction_isolation=SERIALIZABLE,那么事务的默认隔离级别,就从原来的REPEATABLE-READ变成了SERIALIZABLE。

小结:

数据库规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。

5)设置隔离级别

每启动一个 mysql 程序, 就会获得一个单独的数据库连接. 每个数据库连接都有一个全局变量 @@tx_isolation, 表示当前的事务隔离级别.

查看当前的隔离级别: SELECT @@tx_isolation;

设置当前 mySQL 连接的隔离级别: set transaction isolation level read committed;

设置数据库系统的全局的隔离级别: set global transaction isolation level read committed;

3.6 隔离级别举例

初始化数据:

复制代码
TRUNCATE TABLE account;
INSERT INTO account VALUES (1,'张三','100'), (2,'李四','0');

img

演示1. 读未提交之脏读

设置隔离级别为未提交读:
img

脏读就是指当前事务就在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问了这个数据,然后使用了这个数据。

演示2:读已提交
img

演示3. 不可重复读

设置隔离级别为可重复读,事务的执行流程如下:
img

当我们将当前会话的隔离级别设置为可重复读的时候,当前会话可以重复读,就是每次读取的结果集都相同,而不管其他事务有没有提交。但是在可重复读的隔离级别上会出现幻读的问题。

演示4:幻读
img img

4. 事务的常见分类

从事务理论的角度来看,可以把事务分为以下几种类型:

  • 扁平事务(Flat Transactions)
  • 带有保存点的扁平事务(Flat Transactions with Savepoints)
  • 链事务(Chained Transactions)
  • 嵌套事务(Nested Transactions)
  • 分布式事务(Distributed Transactions)

本文由mdnice多平台发布

相关推荐
分享牛2 小时前
Operaton入门到精通22-Operaton 2.0 升级指南:Spring Boot 4 核心变更详解
java·spring boot·后端
jinanmichael2 小时前
SpringBoot 如何调用 WebService 接口
java·spring boot·后端
深蓝轨迹2 小时前
吃透 Spring Boot dataSource与Starter
java·spring boot·笔记·后端
spring2997922 小时前
springboot和springframework版本依赖关系
java·spring boot·后端
yuhaiqiang2 小时前
为什么这道初中数学题击溃了所有 AI
前端·后端·面试
面向Google编程2 小时前
从零学习Kafka:副本机制
大数据·后端·kafka
超级大福宝3 小时前
用买火车票的例子讲解Java反射的作用
java·开发语言·后端
程序员爱钓鱼3 小时前
Go高性能缓冲IO详解: bufio包深度指南
后端·面试·go
熙胤3 小时前
Spring Boot 3.x 引入springdoc-openapi (内置Swagger UI、webmvc-api)
spring boot·后端·ui