幻读 (间隙锁)
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>
那么这是什么问题呢?
个人认为, 如果你的表中真的会出现两条完全相同的记录, 考虑一下, 最起码的表规范(第二范式)是否先满足一下?