likes
comments
collection
share

解读Mysql8中的事务隔离级别

作者站长头像
站长
· 阅读数 20

事务的隔离级别与几个关键词有关: 脏数据,不可重复读,幻读,一致性读

假设有一个 accounts 表:

+----+---------+
| id | balance |
+----+---------+
|  1 | 1000.00 |
|  2 |  0.00 |
+----+---------+

READ UNCOMMITED (读未提交模式)

Shell AShell B
set session transaction isolation level read uncommitted;set session transaction isolation level read uncommitted;
start transaction;start transaction;
select * from accounts where id=1;select * from accounts where id=1;
结果是: | 1 | 1000.00 |结果是: | 1 | 1000.00 |
update accounts set balance=balance+100 where id=1;
结果是: Query OK, 1 row affected (0.01 sec)
select * from accounts where id=1;select * from accounts where id=1;
结果是: | 1 | 1100.00 |结果是: | 1 | 1100.00 |
rollback
select * from accounts where id=1;select * from accounts where id=1;
结果是: | 1 | 1000.00 |结果是: | 1 | 1000.00 |

这个案例中, Shell B 执行 rollback 之前, Shell A 就读取到了 Shell B 存入的数据, 此时账户中的 100 就叫 脏数据, 如果 Shell B 在真实业务中执行了 rollback, 那此时 Shell A 中读取到的就是错误数据, 如果是在银行业务中出现了此类问题, 将会产生严重的后果

READ COMMITTED (读已提交模式)

Shell AShell B
set session transaction isolation level read committed;set session transaction isolation level read committed;
start transaction;start transaction;
select * from accounts where id=1;select * from accounts where id=1;
结果是: | 1 | 1000.00 |结果是: | 1 | 1000.00 |
update accounts set balance=balance+100 where id=1;
结果是: Query OK, 1 row affected (0.01 sec)
select * from accounts where id=1;select * from accounts where id=1;
结果是: | 1 | 1000.00 |结果是: | 1 | 1100.00 |
commit;
select * from accounts where id=1;select * from accounts where id=1;
结果是: | 1 | 1100.00 |结果是: | 1 | 1100.00 |

这个模式中, 在 Shell B 的事务提交之前, Shell A 已经读取不到脏数据了, 只有在 Shell B 提交后才能读取到 update 后的值

但是这还有一个问题: Shell A 在同一个事务中读取同一行的数据, 出现了不同的结果, 这是因为 Shell A 在执行事务期间, Shell B 的事务可以照常修改数据, 这种现象就叫 (Shell A)不可重复读

REPEATABLE READ (可重复读模式)

Shell AShell B
set session transaction isolation level repeatable read;set session transaction isolation level repeatable read;
start transaction;start transaction;
select * from accounts where id=1;select * from accounts where id=1;
结果是: | 1 | 1000.00 |结果是: | 1 | 1000.00 |
update accounts set balance=balance+100 where id=1;
结果是: Query OK, 1 row affected (0.01 sec)
select * from accounts where id=1;select * from accounts where id=1;
结果是: | 1 | 1000.00 |结果是: | 1 | 1100.00 |
commit;
select * from accounts where id=1;select * from accounts where id=1;
结果是: | 1 | 1000.00 |结果是: | 1 | 1100.00 |

READ COMMITTED模式不同的是, 即便是 Shell B 中的提交了事务, Shell A 中查询出来的依然是1000的余额, 解决了 不可重复读 问题, 这是因为该模式下,一次性读机制让事务中的每次 select 都返回事务中第一次select所建立的快照

另外, 在这个模式下, 即便其他事务对该事务所影响的行执行了删除操作, 在当前事务中, 依然能够看到被删除的数据

由于该模式还是允许其他事务修改数据, 所以可能出现一些非预期的情况, 下面这是官网的例子:

Shell AShell B
set session transaction isolation level repeatable read;set session transaction isolation level repeatable read;
start transaction;start transaction;
select COUNT(c2) from t1 where c2 = 'abc';
结果: Returns 0: no rows match.
插入 10 条数据insert t1 values (...)
commit;
update t1 set c2 = 'cba' where c2 = 'abc';
select COUNT(c2) from t1 where c2 = 'cba';
结果: Returns 10

在这个例子中,Shell A 在 Shell B 提交后执行了update语句, 导致把 Shell B 提交的数据也更新了, 这个现象就叫做 幻读(Shell A 更新了事务开始时不存在的数据), 在实际开发中也是很危险的行为

SERIALIZABLE (串行模式)

Shell AShell B
set session transaction isolation level serializable;set session transaction isolation level serializable;
start transaction;start transaction;
select count(id) from accounts';
结果: Returns 2
插入 1 条数据insert accounts values (...)
暂停执行, 等待 Shell A 提交
commit;自动执行 insert 语句

从过程中可以看到, Shell B 中的 insert 语句被暂停执行了, 这是啥因为 serializable 模式下, 所有普通的select语句都被隐式添加了for share的共享锁: 当前事务开启后, 其他事务只能读取被锁定的行, 不能修改; 这个案例中由于执行了count()函数, 相当于是该表整个被锁定了, 所以 Shell B 也无法执行 insert语句

这个模式就解决了幻读的问题

一致性读(consistent read)

一致性读是 InnoDB 在 READ COMMITTEDREPEATABLE READ 隔离级别下处理 select 语句的默认模式。一致性读不会对其访问的表设置任何锁,因此在对表执行一致性读的同时,其他会话可以自由地修改这些表。

READ COMMITTED模式下, 一致性读保证事务中每次 select 都会读取并设置其自己的最新快照

而在REPEATABLE READ模式下, 一致性读保证事务中每次 select 的结果都是相同的, 这是因为该模式下, 当发出一致读取(即普通的 SELECT 语句)时,InnoDB 会为所在事务提供一个时间点,后续事务内的查询将根据该时间点查看数据库

转载自:https://juejin.cn/post/7297049953229799458
评论
请登录