黄东旭解析 TiDB 的核心优势
679
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小时内删除侵权内容。