mysql笔记:21. 演示脏读、不可重复读和幻读现象

news/2024/12/29 3:04:44/

文章目录

  • 脏读
    • 1. 设置窗口B中事务的隔离级别
    • 2. 演示脏读
    • 3. 重新设置窗口B中事务的隔离级别
    • 4. 再次验证脏读
  • 不可重复读
    • 1. 演示不可重复读
    • 2. 设置窗口B中事务的隔离级别
    • 3. 再次验证不可重复读
  • 幻读
    • 1. 设置窗口B中事务的隔离级别
    • 2. 演示幻读
    • 3. 重新设置窗口B中事务的隔离级别
    • 4. 再次验证幻读
  • 串行化插入问题
    • 1. 设置窗口B中事务的隔离级别
    • 2. 演示串行化插入问题

在MySQL中,事务有4种隔离级别,分别为READ UNCOMMITTED(读未提交)、READ COMMITTED(读已提交)、REPEATABLE READ(可重复读)和SERIALIZABLE(串行化)。

脏读

READ UNCOMMITTED是事务隔离级别中最低的级别,该级别下的事务可以读取其他事务中未提交的数据,这种读取方式也被称为脏读。
下面以示例数据库employees.departments表为例演示脏读。首先,开启两个终端窗口,分别称为窗口A和窗口B。两个窗口都登录到MySQL数据库并切换到employees库。

1. 设置窗口B中事务的隔离级别

MySQL默认隔离级别是REPEATABLE READ,该级别可以避免脏读。为演示需求,将其隔离级别设置为READ UNCOMMITTED。

mysql> SHOW VARIABLES LIKE 'transaction_isolation';
+-----------------------+-----------------+
| Variable_name         | Value           |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
+-----------------------+-----------------+
1 row in set (0.00 sec)# 设置隔离级别
mysql> SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
Query OK, 0 rows affected (0.00 sec)mysql> SHOW VARIABLES LIKE 'transaction_isolation';
+-----------------------+------------------+
| Variable_name         | Value            |
+-----------------------+------------------+
| transaction_isolation | READ-UNCOMMITTED |
+-----------------------+------------------+
1 row in set (0.00 sec)

2. 演示脏读

在窗口A中开启事务,并更新Sales部门名称为Sales2。

mysql> SELECT * FROM departments WHERE dept_no='d007';
+---------+-----------+
| dept_no | dept_name |
+---------+-----------+
| d007    | Sales     |
+---------+-----------+
1 row in set (0.01 sec)mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)mysql> UPDATE departments SET dept_name='Sales2' WHERE dept_no='d007';
Query OK, 1 row affected (0.02 sec)
Rows matched: 1  Changed: 1  Warnings: 0mysql> SELECT * FROM departments WHERE dept_no='d007';
+---------+-----------+
| dept_no | dept_name |
+---------+-----------+
| d007    | Sales2    |
+---------+-----------+
1 row in set (0.00 sec)

此时不要提交事务,否则无法演示脏读现象。
切换到窗口B,查询该部门信息。

mysql> SELECT * FROM departments WHERE dept_no='d007';
+---------+-----------+
| dept_no | dept_name |
+---------+-----------+
| d007    | Sales2    |
+---------+-----------+
1 row in set (0.01 sec)

从结果看,窗口B中可以看到修改后的部门名称,这是由于窗口B的事务隔离级别比较低,因此读取了窗口A中还没有提交的内容,出现了脏读情况。
脏读演示完毕,可以在窗口A中执行ROLLBACK命令回滚事务,让数据恢复初始状态。

3. 重新设置窗口B中事务的隔离级别

为防止脏读的发生,将B中事务的隔离级别设置为READ COMMITTED,该隔离级别可以避免脏读。

mysql> SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
Query OK, 0 rows affected (0.00 sec)mysql> SHOW VARIABLES LIKE 'transaction_isolation';
+-----------------------+----------------+
| Variable_name         | Value          |
+-----------------------+----------------+
| transaction_isolation | READ-COMMITTED |
+-----------------------+----------------+
1 row in set (0.01 sec)

4. 再次验证脏读

在窗口B中查询部门d007的信息。

mysql> SELECT * FROM departments WHERE dept_no='d007';
+---------+-----------+
| dept_no | dept_name |
+---------+-----------+
| d007    | Sales     |
+---------+-----------+
1 row in set (0.00 sec)

然后在窗口A中开启事务并修改部门d007的信息。

mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)mysql> UPDATE departments SET dept_name='Sales3' WHERE dept_no='d007';
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0mysql> SELECT * FROM departments WHERE dept_no='d007';
+---------+-----------+
| dept_no | dept_name |
+---------+-----------+
| d007    | Sales3    |
+---------+-----------+
1 row in set (0.00 sec)

修改数据后再次查询d007的部门信息已更新,但数据未提交。此时在窗口B中查询。

mysql> SELECT * FROM departments WHERE dept_no='d007';
+---------+-----------+
| dept_no | dept_name |
+---------+-----------+
| d007    | Sales     |
+---------+-----------+
1 row in set (0.00 sec)

结果显示窗口B中并没有查询到窗口A中未提交的内容,说明READ COMMITTED隔离级别可以避免脏读。
验证演示完毕,可以在窗口A中执行ROLLBACK命令回滚事务,让数据恢复初始状态。

不可重复读

在MySQL的READ COMMITTED级别下,事务只能读取其他事务已经提交的内容,可以避免脏读现象,但是会出现不可重复读和幻读的情况。
不可重复读是指在事务内重复读取数据,由于多次查询期间,其他事务更新了相关数据,因此出现多次读取结果不一致的现象。

1. 演示不可重复读

在窗口B中设置事务的隔离级别为READ COMMITTED,然后开启事务查询d007的部门信息。

mysql> SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
Query OK, 0 rows affected (0.00 sec)mysql> SHOW VARIABLES LIKE 'transaction_isolation';
+-----------------------+----------------+
| Variable_name         | Value          |
+-----------------------+----------------+
| transaction_isolation | READ-COMMITTED |
+-----------------------+----------------+
1 row in set (0.00 sec)mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)mysql> SELECT * FROM departments WHERE dept_no='d007';
+---------+-----------+
| dept_no | dept_name |
+---------+-----------+
| d007    | Sales     |
+---------+-----------+
1 row in set (0.00 sec)

此时,在窗口A中使用UPDATE语句更新d007的部门信息。

mysql> UPDATE departments SET dept_name='Sales4' WHERE dept_no='d007';
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0mysql> SELECT * FROM departments WHERE dept_no='d007';
+---------+-----------+
| dept_no | dept_name |
+---------+-----------+
| d007    | Sales4    |
+---------+-----------+
1 row in set (0.00 sec)

修改成功后,再次回到窗口B中查询d007的部门信息。

mysql> SELECT * FROM departments WHERE dept_no='d007';
+---------+-----------+
| dept_no | dept_name |
+---------+-----------+
| d007    | Sales4    |
+---------+-----------+
1 row in set (0.00 sec)

结果显示在一个事务中,相同的查询语句两次查询结果不一致。其实不可重复读并不算错误,但在有些情况下却不符合实际需求。
不可重复读演示完毕,可以在窗口B中执行COMMIT命令提交事务。

2. 设置窗口B中事务的隔离级别

为了防止不可重复读的情况出现,修改窗口B中事务的隔离级别为REPEATABLE READ。

mysql> SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;Query OK, 0 rows affected (0.00 sec)mysql> SHOW VARIABLES LIKE 'transaction_isolation';
+-----------------------+-----------------+
| Variable_name         | Value           |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
+-----------------------+-----------------+
1 row in set (0.00 sec)

3. 再次验证不可重复读

在窗口B中开启事务,并查询d007的部门信息。

mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)mysql> SELECT * FROM departments WHERE dept_no='d007';
+---------+-----------+
| dept_no | dept_name |
+---------+-----------+
| d007    | Sales4    |
+---------+-----------+
1 row in set (0.00 sec)

在窗口A中使用UPDATE语句更新d007的部门信息:

mysql> UPDATE departments SET dept_name='Sales5' WHERE dept_no='d007';
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0mysql> SELECT * FROM departments WHERE dept_no='d007';
+---------+-----------+
| dept_no | dept_name |
+---------+-----------+
| d007    | Sales5    |
+---------+-----------+
1 row in set (0.00 sec)

然后在窗口B中查询d007的部门信息:

mysql> SELECT * FROM departments WHERE dept_no='d007';
+---------+-----------+
| dept_no | dept_name |
+---------+-----------+
| d007    | Sales4    |
+---------+-----------+
1 row in set (0.00 sec)

对比窗口B的两次查询结果,发现窗口B中事务的隔离级别修改为REPEATABLE READ后,查询结果是一致的,并没有出现不同的数据,说明此该级别可以避免不可重复读的情况。
验证演示完毕,可以在窗口B中执行COMMIT命令提交事务。

幻读

幻读又被称为虚读,是指在一个事务内两次查询中的数据条数不一致。幻读和不可重复读类似,都是在两次查询过程中。区别是,幻读是由于其他事务插入记录导致记录数变化而引起的。
理论上,REPEATABLE READ级别可以避免脏读、不可重复读,但会出现幻读。不过MySQL的存储引擎通过多版本并发控制机制解决了该问题,所以当事务隔离级别为REPEATABLE READ时可以避免幻读。

1. 设置窗口B中事务的隔离级别

REPEATABLE READ级别可以避免幻读,所以将事务的隔离级别设置得更低。

mysql> SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
Query OK, 0 rows affected (0.00 sec)mysql> SHOW VARIABLES LIKE 'transaction_isolation';
+-----------------------+----------------+
| Variable_name         | Value          |
+-----------------------+----------------+
| transaction_isolation | READ-COMMITTED |
+-----------------------+----------------+
1 row in set (0.00 sec)

2. 演示幻读

在窗口B中开启事务,并查询部门名称以字母D开头的信息:

mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)mysql> SELECT * FROM departments WHERE dept_name LIKE 'D%';
+---------+-------------+
| dept_no | dept_name   |
+---------+-------------+
| d005    | Development |
+---------+-------------+
1 row in set (0.00 sec)

在窗口A中插入新成立的部门Design:

mysql> INSERT INTO departments value('d010', 'Design');
Query OK, 1 row affected (0.01 sec)mysql> SELECT * FROM departments WHERE dept_no='d010';
+---------+-----------+
| dept_no | dept_name |
+---------+-----------+
| d010    | Design    |
+---------+-----------+
1 row in set (0.01 sec)

切换到窗口B中,再次查询部门名称以字母D开头的信息:

mysql> SELECT * FROM departments WHERE dept_name LIKE 'D%';
+---------+-------------+
| dept_no | dept_name   |
+---------+-------------+
| d010    | Design      |
| d005    | Development |
+---------+-------------+
2 rows in set (0.00 sec)

删除数据和新增数据类似,也会出现幻读现象。

幻读演示完毕,可以在窗口B中执行COMMIT命令提交事务。

3. 重新设置窗口B中事务的隔离级别

为避免幻读现象,将窗口B中事务的隔离级别设置为REPEATABLE READ。

mysql> SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;Query OK, 0 rows affected (0.00 sec)mysql> SHOW VARIABLES LIKE 'transaction_isolation';
+-----------------------+-----------------+
| Variable_name         | Value           |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
+-----------------------+-----------------+
1 row in set (0.00 sec)

4. 再次验证幻读

在窗口B中开启事务,并查询部门名称以字母D开头的信息:

mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)mysql> SELECT * FROM departments WHERE dept_name LIKE 'D%';
+---------+-------------+
| dept_no | dept_name   |
+---------+-------------+
| d010    | Design      |
| d005    | Development |
+---------+-------------+
2 rows in set (0.00 sec)

在窗口A中插入新成立的部门Design2:

mysql> INSERT INTO departments value('d011', 'Design2');
Query OK, 1 row affected (0.00 sec)mysql> SELECT * FROM departments WHERE dept_no='d011';
+---------+-----------+
| dept_no | dept_name |
+---------+-----------+
| d011    | Design2   |
+---------+-----------+
1 row in set (0.00 sec)

切换到窗口B中,再次查询部门名称以字母D开头的信息:

mysql> SELECT * FROM departments WHERE dept_name LIKE 'D%';
+---------+-------------+
| dept_no | dept_name   |
+---------+-------------+
| d010    | Design      |
| d005    | Development |
+---------+-------------+
2 rows in set (0.00 sec)

对比窗口B中的两次查询结果,发现窗口B设置事务的隔离级别为REPEATABLE READ后,同一个事务的两次查询结果是一致的,并没有读取其他事务新插入的记录。说明REPEATABLE READ可以避免幻读。
验证演示完毕,可以在窗口B中执行COMMIT命令提交事务。

串行化插入问题

事务的隔离级别设置为SERIALIZABLE可以避免脏读、不可重复读和幻读,但也会导致大量的超时和锁竞争,数据库性能极差。

1. 设置窗口B中事务的隔离级别

mysql> SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
Query OK, 0 rows affected (0.00 sec)mysql> SHOW VARIABLES LIKE 'transaction_isolation';
+-----------------------+--------------+
| Variable_name         | Value        |
+-----------------------+--------------+
| transaction_isolation | SERIALIZABLE |
+-----------------------+--------------+
1 row in set (0.01 sec)

2. 演示串行化插入问题

在窗口B中开启事务,并查询表中数据。

mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)mysql> SELECT COUNT(1) FROM departments;
+----------+
| COUNT(1) |
+----------+
|       11 |
+----------+
1 row in set (0.00 sec)

切换到窗口A,并插入新成立的部门Design3:

mysql> INSERT INTO departments value('d012', 'Design3');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

窗口A中执行插入语句后,不会立即执行成功,而是光标闪烁,一直等待。此时提交窗口B中的事务,窗口A中的插入操作会立即执行;否则A中的操作一直等待,直到最后超时提示信息。
默认超时时间是50秒。


http://www.ppmy.cn/news/1387910.html

相关文章

文心一言赋能问卷生成,打造高效问卷调研工具

当前,各种大语言模型(LLM,Large Language Model)井喷式发展,基于LLM的应用也不断涌现。但是,当开发者基于LLM开发下游应用时,LLM直接生成的结果在格式、内容等方面都存在许多不确定因素&#xf…

面试经典150题(114-118)

leetcode 150道题 计划花两个月时候刷完之未完成后转,今天完成了5道(114-118)150 gap 了一周,以后就不记录时间了。。 114.(70. 爬楼梯) 题目描述: 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不…

【数据结构和算法初阶(C语言)】二叉树的顺序结构--堆的实现/堆排序/topk问题详解---二叉树学习日记②

目录 ​编辑 1.二叉树的顺序结构及实现 1.1 二叉树的顺序结构 2 堆的概念及结构 3 堆的实现 3.1堆的代码定义 3.2堆插入数据 3.3打印堆数据 3.4堆的数据的删除 3.5获取根部数据 3.6判断堆是否为空 3.7 堆的销毁 4.建堆以及堆排序 4.1 升序建大堆,降序建小堆 4.2堆…

Unity构建详解(1)——SBP介绍

【前言】 Unity的资源工作流程分为导入、创建、构建、分发、加载。我们说的是其中的构建步骤。 构建是指将项目工程中的资源文件和代码整合程可执行文件的过程,构建的结果是生成可执行文件,在win平台上是exe,在Android平台上是apk&#xff…

Mac玩《幻兽帕鲁》为什么打不开D3DMetal?d3d错误怎么办 d3dxl error

我之前发了一篇讲Mac电脑玩Steam热门新游《幻兽帕鲁》的文章(没看过的点这里),后来也看到很多朋友去尝试了,遇到了一些问题,无法进入《幻兽帕鲁》游戏,或者是玩的时候卡顿以及出现黑屏,通过我的…

华为设备配置命令大全

目录 一、华为设备常用命令视图 二、返回命令和保存命令 三、设置设备名称 四、关闭泛洪信息 五、设置设备接口的IP地址和子网掩码 六、交换机的登录 6.1、设置Consile接口密码 6.2、设置Telent接口密码 七、VLAN配置 7.1、创建VLAN 7.2、进入vlan视图 7.3、把端口…

汽车信息安全--安全调试功能在量产后是否必须禁用(1)

目录 1.芯片生命周期与调试接口 2. 安全调试安全在哪里 2.1 常见安全调试解锁手段

最细致最简单的 Arm 架构搭建 Harbor

更好的阅读体验:点这里 ( www.doubibiji.com ) ARM离线版本安装 官方提供了一个 arm 版本,但是好久都没更新了,地址:https://github.com/goharbor/harbor-arm 。 也不知道为什么不更新,我看…