如何真正来区分一下 不可重复读和幻读

网友投稿 550 2023-05-05

如何真正来区分一下 不可重复读和幻读

如何真正来区分一下 不可重复读和幻读

幻读 (间隙锁)

1.由于很多人(当然也包括本人), 容易搞混 不可重复读 和 幻读 , 这两者确实非常相似。

但 不可重复读 的重点是针对 update, delete。而 幻读 的重点针对的是 insert。(可以参考MySQL官方文档对 Phantom Rows 的介绍)

2.虽然网上有不少资料提到幻读, 但是可能表达的都不太准确, 比如这样一段对幻读的解释 '同样的条件, ***次和第二次读出来的记录不一样' 在网络上随处可见, 但其实并不准确, 因为 delete 其实并不是幻读的范畴(MySQL官方文档对 Phantom Rows 的介绍)也一点都没涉及到delete)。

3.如果手动加锁来演示, 你便会看清他们的本质:

如果 insert, 则操作被阻塞, 并且可以看到具体加的是X锁+GAP锁:

如果 delete或者update, 则操作被阻塞, 但是可以看到具体加的只有X锁:

可以看到, 其他事务只有在 insert 的时候, 才会加GAP锁来防止幻读, 所以delete/update 和 insert 是要区分开的.

不过, 后面学到mvcc的时候, 你会知道加锁的低效性, 所以还有两种解决方案:

使用隔离性的***隔离级别SERIALIZABLE, 但该隔离级别在实际中很少使用;

其实 REPEATABLE READ 就可以防止幻读, 《高性能MySQL》中也说了, REPEATABLE READ 理论是是不能防止幻读的, 但是由于该隔离级别还使用了MVCC, 可以做到非锁定一致性读取, 所以, 只要你真的确定你明白幻读的意思, 你在 REPEATABLE READ隔离级别下是模拟不出幻读效果的;

至于网络上如下所谓的幻读现象, 本人觉得是误导, 刚开始本人也认为这就是REPEATABLE-READ隔离级别无法解决幻读的铁证, 后来发现错了, 幻读是指两次读操作发现记录增多导致的不一致, 而如下是多次insert, 虽然也是个问题, 但已经不是 幻读 问题了:

打开客户端1查看隔离级别及初始数据

mysql> SELECT @@SESSION.tx_isolation; +------------------------+ | @@SESSION.tx_isolation | +------------------------+ | REPEATABLE-READ        | +------------------------+ 1 row in set (0.00 sec)   mysql> select * from test_transaction; +----+-----------+-----+--------+--------------------+ | id | user_name | age | gender | desctiption        | +----+-----------+-----+--------+--------------------+ |  1 | 金刚狼 | 127 |      1 | 我有一双铁爪 | |  2 | 钢铁侠 | 120 |      1 | 我有一身铁甲 | |  3 | 绿巨人 |   0 |      2 | 我有一身肉    | +----+-----------+-----+--------+--------------------+ 3 rows in set (0.00 sec)   mysql>

打开客户端2查看隔离级别及初始数据

mysql> SELECT @@SESSION.tx_isolation; +------------------------+ | @@SESSION.tx_isolation | +------------------------+ | REPEATABLE-READ        | +------------------------+ 1 row in set (0.00 sec)   mysql> select * from test_transaction; +----+-----------+-----+--------+--------------------+ | id | user_name | age | gender | desctiption        | +----+-----------+-----+--------+--------------------+ |  1 | 金刚狼 | 127 |      1 | 我有一双铁爪 | |  2 | 钢铁侠 | 120 |      1 | 我有一身铁甲 | |  3 | 绿巨人 |   0 |      2 | 我有一身肉    | +----+-----------+-----+--------+--------------------+ 3 rows in set (0.00 sec)   mysql>

在客户端2中开启事务, 然后查询数据

mysql> begin; Query OK, 0 rows affected (0.00 sec)   mysql> select * from test_transaction; +----+-----------+-----+--------+--------------------+ | id | user_name | age | gender | desctiption        | +----+-----------+-----+--------+--------------------+ |  1 | 金刚狼 | 127 |      1 | 我有一双铁爪 | |  2 | 钢铁侠 | 120 |      1 | 我有一身铁甲 | |  3 | 绿巨人 |   0 |      2 | 我有一身肉    | +----+-----------+-----+--------+--------------------+ 3 rows in set (0.00 sec)   mysql>

在客户端1中插入一条id为4的新数据 (直接自动提交)

mysql> insert into test_transaction (`id`,`user_name`,`age`,`gender`,`desctiption`) values (4, '死侍', 18, 0, 'A bad boy'); Query OK, 1 row affected (0.00 sec) mysql> select * from test_transaction; +----+-----------+-----+--------+--------------------+ | id | user_name | age | gender | desctiption        | +----+-----------+-----+--------+--------------------+ |  1 | 金刚狼 | 127 |      1 | 我有一双铁爪 | |  2 | 钢铁侠 | 120 |      1 | 我有一身铁甲 | |  3 | 绿巨人 |   0 |      2 | 我有一身肉    | |  4 | 死侍    |  18 |      0 | A bad boy          | +----+-----------+-----+--------+--------------------+ 4 rows in set (0.00 sec)   mysql>

在客户端2事务中再次查询数据, 发现数据没有变化(表示可以重复读, 并且克服了幻读), 但是在客户端2事务中插入一条id为4的新数据, 发现提示数据已经存在, 注意, 虽然爆出问题来了, 但不是 幻读 范畴

mysql> begin; Query OK, 0 rows affected (0.00 sec)   mysql> select * from test_transaction; +----+-----------+-----+--------+--------------------+ | id | user_name | age | gender | desctiption        | +----+-----------+-----+--------+--------------------+ |  1 | 金刚狼 | 127 |      1 | 我有一双铁爪 | |  2 | 钢铁侠 | 120 |      1 | 我有一身铁甲 | |  3 | 绿巨人 |   0 |      2 | 我有一身肉    | +----+-----------+-----+--------+--------------------+ 3 rows in set (0.00 sec)  mysql> select * from test_transaction; +----+-----------+-----+--------+--------------------+ | id | user_name | age | gender | desctiption        | +----+-----------+-----+--------+--------------------+ |  1 | 金刚狼 | 127 |      1 | 我有一双铁爪 | |  2 | 钢铁侠 | 120 |      1 | 我有一身铁甲 | |  3 | 绿巨人 |   0 |      2 | 我有一身肉    | +----+-----------+-----+--------+--------------------+ 3 rows in set (0.00 sec)  mysql> insert into test_transaction (`id`,`user_name`,`age`,`gender`,`desctiption`) values (4, '死侍', 18, 0, 'A bad boy'); 1062 - Duplicate entry '4' for key 'PRIMARY' mysql>

那么这是什么问题呢?

个人认为, 如果你的表中真的会出现两条完全相同的记录, 考虑一下, 最起码的表规范(第二范式)是否先满足一下?

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:MySQL 数据库的四种隔离级别
下一篇:2018年,让你的数据库变更快的十个建议
相关文章