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

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

[[215809]]

幻读 (间隙锁)

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查看隔离级别及初始数据

  1. mysql> SELECT @@SESSION.tx_isolation; 
  2. +------------------------+ 
  3. | @@SESSION.tx_isolation | 
  4. +------------------------+ 
  5. REPEATABLE-READ        | 
  6. +------------------------+ 
  7. 1 row in set (0.00 sec) 
  8.   
  9. mysql> select * from test_transaction; 
  10. +----+-----------+-----+--------+--------------------+ 
  11. | id | user_name | age | gender | desctiption        | 
  12. +----+-----------+-----+--------+--------------------+ 
  13. |  1 | 金刚狼 | 127 |      1 | 我有一双铁爪 | 
  14. |  2 | 钢铁侠 | 120 |      1 | 我有一身铁甲 | 
  15. |  3 | 绿巨人 |   0 |      2 | 我有一身肉    | 
  16. +----+-----------+-----+--------+--------------------+ 
  17. rows in set (0.00 sec) 
  18.   
  19. mysql>  
  • 打开客户端2查看隔离级别及初始数据
  1. mysql> SELECT @@SESSION.tx_isolation; 
  2. +------------------------+ 
  3. | @@SESSION.tx_isolation | 
  4. +------------------------+ 
  5. REPEATABLE-READ        | 
  6. +------------------------+ 
  7. 1 row in set (0.00 sec) 
  8.   
  9. mysql> select * from test_transaction; 
  10. +----+-----------+-----+--------+--------------------+ 
  11. | id | user_name | age | gender | desctiption        | 
  12. +----+-----------+-----+--------+--------------------+ 
  13. |  1 | 金刚狼 | 127 |      1 | 我有一双铁爪 | 
  14. |  2 | 钢铁侠 | 120 |      1 | 我有一身铁甲 | 
  15. |  3 | 绿巨人 |   0 |      2 | 我有一身肉    | 
  16. +----+-----------+-----+--------+--------------------+ 
  17. rows in set (0.00 sec) 
  18.   
  19. mysql>  

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

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

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

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

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

  1. mysql> begin
  2. Query OK, 0 rows affected (0.00 sec) 
  3.   
  4. mysql> select * from test_transaction; 
  5. +----+-----------+-----+--------+--------------------+ 
  6. | id | user_name | age | gender | desctiption        | 
  7. +----+-----------+-----+--------+--------------------+ 
  8. |  1 | 金刚狼 | 127 |      1 | 我有一双铁爪 | 
  9. |  2 | 钢铁侠 | 120 |      1 | 我有一身铁甲 | 
  10. |  3 | 绿巨人 |   0 |      2 | 我有一身肉    | 
  11. +----+-----------+-----+--------+--------------------+ 
  12. rows in set (0.00 sec) 
  13.  
  14. mysql> select * from test_transaction; 
  15. +----+-----------+-----+--------+--------------------+ 
  16. | id | user_name | age | gender | desctiption        | 
  17. +----+-----------+-----+--------+--------------------+ 
  18. |  1 | 金刚狼 | 127 |      1 | 我有一双铁爪 | 
  19. |  2 | 钢铁侠 | 120 |      1 | 我有一身铁甲 | 
  20. |  3 | 绿巨人 |   0 |      2 | 我有一身肉    | 
  21. +----+-----------+-----+--------+--------------------+ 
  22. rows in set (0.00 sec) 
  23.  
  24. mysql> insert into test_transaction (`id`,`user_name`,`age`,`gender`,`desctiption`) values (4, '死侍', 18, 0, 'A bad boy'); 
  25. 1062 - Duplicate entry '4' for key 'PRIMARY' 
  26. mysql>  

那么这是什么问题呢?

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

责任编辑:武晓燕 来源: Segmentfault
相关推荐

2022-04-27 07:32:02

脏读幻读不可重复读

2024-04-24 08:26:35

事务数据InnoDB

2024-04-19 08:18:47

MySQLSQL隔离

2019-03-21 09:06:00

数据库复读幻读

2022-01-03 07:18:05

脏读幻读 MySQL

2023-11-01 14:13:00

MySQL事务隔离级别

2022-06-29 11:01:05

MySQL事务隔离级别

2021-08-02 09:01:05

MySQL 多版本并发数据库

2023-02-02 07:06:10

2023-10-26 00:41:46

脏读数据幻读

2024-05-13 11:46:33

MySQL数据库

2019-05-28 13:50:27

MySQL幻读数据库

2024-07-16 08:19:46

MySQL数据InnoDB

2021-04-20 19:21:50

脏读MySQL幻读

2019-12-24 14:50:01

MySQL可重复读数据库

2023-12-26 08:08:02

Spring事务MySQL

2021-09-07 10:33:42

MySQL事务隔离性

2024-03-11 00:00:00

mysqlInnoDB幻读

2022-09-21 09:00:10

MySQL幻读隔离级别

2023-08-09 17:22:30

MVCCMySQL数据
点赞
收藏

51CTO技术栈公众号