昨天接到阿里的电话面试, 对方问了一个在 MySQL 当中, 什么是幻读. 当时一脸懵逼, 凭着印象和对方胡扯了几句. 面试结束后, 赶紧去查资料, 才发现之前对幻读的理解完全错误. 下面, 我们就聊聊幻读.
要说幻读, 就要从 MySQL 的隔离级别说起. MySQL 的 4 钟隔离级别分别是:
Read Uncommitted(读取未提交内容)
在该隔离级别, 所有事务都可以看到其他未提交事务的执行结果. 本隔离级别很少用于实际应用, 因为它的性能也不比其他级别好多少. 读取未提交的数据, 也被称之为脏读 (Dirty Read).
脏读的具体示例如下:
时间点 | 事务 A | 事务 B |
---|---|---|
1 | 开启事务 | |
2 | 开启事务 | |
3 | 查询数据为 100 条 | |
4 | insert 一条数据 | |
5 | 再查询,结果为 101 条 |
在时间点 5, 事务 A 再次查询数据时, 事务 B 并没有提交事务, 但是, 新的数据也被事务 A 查出来了. 这就是脏读.
Read Committed(读取提交内容)
这是大多数数据库系统的默认隔离级别 (但不是 MySQL 默认的). 它满足了隔离的简单定义: 一个事务只能看见已经提交事务所做的改变. 这种隔离级别 也支持所谓的不可重复读 (Nonrepeatable Read), 因为同一事务的其他实例在该实例处理其间可能会有新的 commit, 所以同一 select 可能返回不同结果.
时间点 | 事务 A | 事务 B |
---|---|---|
1 | 开启事务 | |
2 | 开启事务 | |
3 | 查询数据为 100 条 | |
4 | insert 一条数据 | |
5 | 查询数据为 100 条 | |
6 | 提交事务 | |
7 | 查询数据为 101 条 |
我们可以看到, 事务 B 在提交事务之前, 事务 A 的两次查询结果是一致的. 事务 B 提交事务以后, 事务 A 再次查询, 查询到了新增的这条数据. 在事务 A 中, 多次查询的结果不一致, 这就是我们说的 "不可重复读".
Repeatable Read(可重读)
这是 MySQL 的默认事务隔离级别, 它确保同一事务的多个实例在并发读取数据时, 会看到同样的数据行. 不过理论上, 这会导致另一个棘手的问题: 幻读 (Phantom Read). 简单的说, 幻读指当用户读取某一范围的数据行时, 另一个事务又在该范围内插入了新行, 当用户再读取该范围的数据行时, 会发现有新的 "幻影" 行.
上面这一段是 MySQL 官方给出的解释, 听着云里雾里."可重读" 这种隔离级别解决了上面例子中的问题, 保证了同一事务内, 多次查询的结果是一致的. 也就是说, 事务 B 插入数据提交事务后, 事务 A 的查询结果也是 100 条, 因为事务 A 在开启事务时, 事务 B 插入的数据还没有提交.
但是, 这又引出了另外一个情况,"幻读". 这个幻读我之前理解是有问题的, 在面试时, 被对方一顿质疑. 现在我们就看看幻读的正确理解:
时间点 | 事务 A | 事务 B |
---|---|---|
1 | 开启事务 | |
2 | 开启事务 | |
3 | 查询数据 “张三”,不存在 | |
4 | 插入数据 “张三” | |
5 | 提交事务 | |
6 | 查询数据 “张三”,不存在 | |
7 | 插入数据 “张三”,不成功 |
事务 A 查询 "张三", 查询不到, 插入又不成功,"张三" 这条数据就像幻觉一样出现. 这就是所谓的 "幻读". 网上对 "幻读" 还是其他的解释, 都是错误的. 比如像 "幻读" 和 "不可重复读" 是一样, 只不过 "幻读" 是针对数据的个数. 这些理解都是错误的.
Serializable(可串行化)
这是最高的隔离级别, 它通过强制事务排序, 使之不可能相互冲突, 从而解决幻读问题. 简言之, 它是在每个读的数据行上加上共享锁. 在这个级别, 可能导致大量的超时现象和锁竞争. 这种隔离级别很少使用, 不给大家做过多的介绍了.
来源: https://www.cnblogs.com/boboooo/p/12370770.html