MySQL 的事务必须满足 A(原子性)C(一致性)I(隔离性)D(持久性) 原则。其中,隔离性是为了尽量减少并发事务彼此之间的影响,最高的隔离级别可以保证并发事务之间互不影响。
在实际应用过程中,为了适应不同的应用场景,隔离级别往往无法设置为最高级别。这就导致并发执行的事务之间或多或少都会相互影响,这些影响导致了各种读操作异常。
⒈ 四种读操作异常
- 脏读
事务 T1 与 T2 同时执行,T1 读到了 T2 写入但还没有提交的数据。如果最终 T2 执行事务的回滚,则 T1 最终返回了错误的结果集。
- 不可重复读
事务 T1 和 T2 同时执行,在 T1 中的 select 语句执行完之后,T2 对某条特定的数据进行了修改并提交,当再次在 T1 中执行 select 操作时,得到的同一条记录的结果与上一次不同。
- 幻读
幻读的情况与不可重复读类似,只不过幻读针对的是查询多条记录的场景。在 T1 中执行 select 语句之后,T2 中执行了 insert/update/delete 操作,导致在 T1 中再次执行 select 得到的结果集与上一次不同。
- 串行化异常
多个事务在没有解决冲突的前提下同时对相同数据进行修改并同时提交,导致最终的结果无法预测。
⒉ 四种隔离级别
- 读未提交(read uncommitted)
事务中可以看到其他事务写入但未提交的记录
- 读提交(read committed)
事务中可以看到其他事务写入并提交的记录
- 可重复读(repeatable read)
保证同一事务中的 select 多次执行得到相同的结果集,无论在此期间是否有别的事务对数据进行了修改
- 串行化(serializable)
最高的事务隔离级别,并发事务按照某种顺序串行执行并且相互之间没有交叠
⒊ 读操作异常与事务隔离级别的对应关系
脏读 | 不可重复读 | 幻读 | 串行化异常 | |
---|---|---|---|---|
读未提交 | Y | Y | Y | Y |
读提交 | N | Y | Y | Y |
可重复读 | N | N | N | Y |
串行化 | N | N | N | N |
⒋ 验证
MySQL 的事务隔离级别有全局的,也有针对当前会话的。分别使用以下方式查看:
# 查看当前会话的事务隔离级别
select @@transaction_isolation;
# 查看全局的事务隔离级别
select @@global.transaction_isolation;
复制代码
同样,修改 MySQL 的事务隔离级别也有只修改当前会话和修改全局两种情况:
# 只修改当前会话
set session transaction isolation level repeatable read;
# 修改全局
set global transaction isolation level repeatable read;
复制代码
验证用到的表以及数据:
drop database if exists bank;
create database bank;
use bank;
drop table if exists accounts;
create table accounts(
id bigint unsigned auto_increment primary key comment '主键',
owner char(32) not null default '' comment '账户名称',
balance decimal(10,2) not null comment '账户余额',
created_at datetime not null default current_timestamp comment '创建时间',
updated_at datetime not null default current_timestamp on update current_timestamp comment '更新时间'
)engine=innodb charset=utf8 collate=utf8_general_ci;
insert into accounts(owner, balance) values
('A', 100),
('B', 100),
('C', 100);
复制代码
- 读未提交
首先修改 session 的事务隔离级别:
# T1
mysql> set session transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.00 sec)
mysql> select @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| READ-UNCOMMITTED |
+-------------------------+
1 row in set (0.00 sec)
# T2
mysql> set session transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.00 sec)
mysql> select @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| READ-UNCOMMITTED |
+-------------------------+
1 row in set (0.00 sec)
复制代码
分别在两个 session 中执行事务,在 T2 中对 ID 为 1 记录进行修改:
# T1
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from accounts where id = 1;
+----+-------+---------+---------------------+---------------------+
| id | owner | balance | created_at | updated_at |
+----+-------+---------+---------------------+---------------------+
| 1 | A | 100.00 | 2021-07-24 16:21:58 | 2021-07-24 16:53:07 |
+----+-------+---------+---------------------+---------------------+
1 row in set (0.00 sec)
# T2
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from accounts where id = 1;
+----+-------+---------+---------------------+---------------------+
| id | owner | balance | created_at | updated_at |
+----+-------+---------+---------------------+---------------------+
| 1 | A | 100.00 | 2021-07-24 16:21:58 | 2021-07-24 16:53:07 |
+----+-------+---------+-------------