MySQL的InnoDB存储引擎是支持多个事物并发执行的,这虽然提高了性能,但同时也带来了一些并发的问题。具体来说就是:脏读不可重复读幻读。本文来解释这几种现象是什么意思。

1. 脏读

脏读就是在事务A中可以读到未提交的事务B中的数据。这些数据由于是未提交的,那么也可能会回滚。因此事务A可能会读到一些不存在的数据,这就是脏读。

事务A 事务B
开始事务 开始事务
更新数据x
查询到未提交的x数据
回滚数据

2. 不可重复读

不可重复读指的是在一个事务内,最开始读到的数据和事务结束前的任意时刻读到的同一批数据出现不一致的情况。举个例子:

事务A 事务B
开始事务 开始事务
第一次查询数据x
修改数据x
提交事务
第二次查询数据x

这种情况下,就会出现在同一个事务内,多次查询同一条数据结果不一致的情况。

3. 幻读

幻读侧重的方面是某一次的 select 操作得到的结果所表征的数据状态无法支撑后续的业务操作。例如:select 某记录是否存在,不存在,准备插入此记录,但执行 insert 时发现此记录已存在,无法插入,此时就发生了幻读。

现在我们复现一下这个问题:

1
2
3
-- 1、首先将事务等级设置为REPEATABLE-READ可重复读。排除其他干扰。
-- 由于MySQL默认就是REPEATABLE-READ。可以查看其隔离等级验证
SELECT @@transaction_isolation;

输出如下就说明可以开始实验了。

1
2
3
4
5
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| REPEATABLE-READ |
+-------------------------+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
-- 准备两个终端连接msyql,模拟并发场景

-- 事务A
begin;

-- 事务B
begin;
-- 结果为空,没有id=2的数据
select * from tb_record where id = 2;


-- 事务A
-- 插入id为2的数据
insert into tb_record values(2, 2, 2, '2023-07-17 10:16:39', '2023-07-17 10:16:39');
-- 查询,有数据
select * from tb_record where id = 2;

-- 事务B
-- 查询,结果依然为空
select * from tb_record where id = 2;
-- 插入id为2的数据
insert into tb_record values(2, 2, 2, '2023-07-17 10:16:39', '2023-07-17 10:16:39');
-- ERROR 1062 (23000): Duplicate entry '2' for key 'tb_record.PRIMARY'

流程如下:

事务A 事务B
开始事务 开始事务
查询id=2的行 查询id=2的行
插入id=2的行
查询id=2的行
插入id=2的行

在事务B中,明明查询id=2的结果都为空,但是插入id=2的数据时,会报已经存在id=2的行呢?就像是幻觉一样。这就是幻读。

4. 事务隔离级别

为了解决上述的几个并发带来的问题,数据库提出了事务隔离机制的方案来解决。一共有四种隔离级别,分别是:
READ UNCOMMITTEDREAD COMMITTEDREPEATABLE READSERIALIZABLE。接下来看每种隔离级别下并发问题发生的情况。

隔离级别 脏读 不可重复读 幻读
READ UNCOMMITTED
READ COMMITTED X
REPEATABLE READ X X 可在邻键锁下解决
SERIALIZABLE X X X

其中代表可能发生,X代表不发生。