详解MySQL 幻读及如何消除

这是一篇数据库隔离级别的科普文章,旨在了解数据库中著名的幻读现象,为了专注,对脏读、不可重复读不作讨论。

事务隔离级别

MySQL有四级事务隔离级别:

读未提交 READ-UNCOMMITTED: 存在脏读,不可重复读,幻读的问题
读已提交 READ-COMMITTED:不存在脏读,但存在不可重复读,幻读问题
可重复读 REPEATABLE-READ:不存在脏读,不可重复读问题,但存在幻读问题
序列化SERIALIZABLE:解决脏读,不可重复读,幻读问题,但完全串行执行,性能最低

什么是幻读

幻读错误的理解:说幻读是事务A 执行两次 select 操作得到不同的数据集,即 select 1 得到10条记录,select 2 得到11条记录。这其实并不是幻读,这是不可重复读的一种,只会在 R-U R-C 级别下出现,而在 mysql 默认的 RR 隔离级别是不会出现的。

这里给出我对幻读的理解:

幻读,并不是说事务中多次读取获取的结果集不同,幻读更重要的是某次的 select 操作得到的结果集所表征的数据状态无法支撑后续的业务操作。更为具体一些:select 记录不存在,准备插入此记录,但执行 insert 时发现此记录已存在,无法插入,如同产生了幻觉

举个例子可能会简化理解:

mysql> show create table user\G
*************************** 1. row ***************************
    Table: user
Create Table: CREATE TABLE `user` (
 `id` int(11) NOT NULL,
 `name` varchar(32) DEFAULT NULL,
 PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

分别开启两个事务T1 & T2,并设置其隔离级别为Reaptable-Read:

T1:

mysql> set global transaction isolation level repeatable read;
​
mysql> begin;
mysql> select * from user;
mysql> insert into user values (1, 'jeff');
ERROR 1062 (23000): Duplicate entry '1' for key 'PRIMARY'
​
mysql> select * from user;

T2:

mysql> set global transaction isolation level repeatable read;
​
mysql> begin;
mysql> insert into user values (1, 'jeff');
mysql> commit;

T1 事务检测表中是否有 id 为 1 的记录,没有则插入

T2 插入干扰记录,造成T1出现幻读。

上例中需要确保T1事务执行begin后才开始执行事务T2。

上例中T1就发生了幻读,因为 T1读取的数据状态与后面的动作发生了语义上的冲突:查询的时候明明提示记录不存在,插入的时候去提示主键重复,类似于出现幻影,因而称之为幻读。

如何消除幻读

MySQL当前有两种方式可以消除幻读:

1. 通过对select操作手动加行X锁(SELECT ... FOR UPDATE )。原因是InnoDB中行锁锁定的
是索引,纵然当前记录不存在,当前事务也会获得一把记录锁(记录存在就加行X锁,不
存在就加next-key lock间隙X锁),这样其他事务则无法插入此索引的记录,杜绝幻
读。
2. 进一步提升隔离级别为SERIALIZABLE
测试一下效果

mysql> begin;
​
mysql> select * from user where id = 2 for update;
mysql> insert into user values (2, 'tony');

mysql> commit;

T2:

mysql> begin;
​
mysql> insert into user values (2, 'jimmy');
ERROR 1062 (23000): Duplicate entry '2' for key 'PRIMARY'

现在T1查询时携带了for update,在Innodb内会对该索引加锁(即使当前不存在),于是事务T2的insert会被阻塞直到T1显示提交,这样T1成功了,对于T1来说,幻读确实被消除了,但T2的插入会报主键重复,这也符合预期。

至于另外一种提升隔离级别消除幻读的方式感兴趣的可以自己尝试,这里不再重复,其本质是类似的,只是让系统代替了手工加锁。

总结

RR作为 mysql 事务默认隔离级别,是事务安全与性能的折中,正确认识幻读后,开发者便可以根据需求自行决定是否需要防止幻读。

SERIALIZABLE则是悲观的认为幻读时刻都会发生,故会自动的隐式的对事务所需资源加排它锁,其他事务访问此资源会被阻塞等待,故事务是安全的,但需要认真考虑性能。

InnoDB的锁是针对索引,这点需要引起注意。对行记录加锁,如果存在,加X锁,否则会加 next-key lock / gap 锁 / 间隙锁,故InnoDB可以实现事务对某记录的预先占用,只要本事务还在,其他事务就别想占有它。关于锁,后面还会再有专门的文章讨论。

以上就是详解MySQL 幻读及如何消除的详细内容,更多关于MySQL 幻读及消除的资料请关注我们其它相关文章!

(0)

相关推荐

  • 详解MySQL是如何解决幻读的

    一.什么是幻读 在一次事务里面,多次查询之后,结果集的个数不一致的情况叫做幻读. 而多出来或者少的哪一行被叫做 幻行 二.为什么要解决幻读 在高并发数据库系统中,需要保证事务与事务之间的隔离性,还有事务本身的一致性. 三.MySQL 是如何解决幻读的 如果你看到了这篇文章,那么我会默认你了解了 脏读 .不可重复读与可重复读. 1. 多版本并发控制(MVCC)(快照读) 多数数据库都实现了多版本并发控制,并且都是靠保存数据快照来实现的. 以 InnoDB 为例,每一行中都冗余了两个字断.一个是行的

  • MySQL可重复读级别能够解决幻读吗

    引言 之前在深入了解数据库理论的时候,了解到事物的不同隔离级别可能存在的问题.为了更好的理解所以在MySQL数据库中测试复现这些问题.关于脏读和不可重复读在相应的隔离级别下都很容易的复现了.但是对于幻读,我发现在可重复读的隔离级别下没有出现,当时想到难道是MySQL对幻读做了什么处理? 测试: 创建一张测试用的表dept: CREATE TABLE `dept` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(20) DEFAULT

  • 详解MySQL 幻读及如何消除

    这是一篇数据库隔离级别的科普文章,旨在了解数据库中著名的幻读现象,为了专注,对脏读.不可重复读不作讨论. 事务隔离级别 MySQL有四级事务隔离级别: 读未提交 READ-UNCOMMITTED: 存在脏读,不可重复读,幻读的问题 读已提交 READ-COMMITTED:不存在脏读,但存在不可重复读,幻读问题 可重复读 REPEATABLE-READ:不存在脏读,不可重复读问题,但存在幻读问题 序列化SERIALIZABLE:解决脏读,不可重复读,幻读问题,但完全串行执行,性能最低 什么是幻读

  • 详解MySQL幻读及如何消除

    目录 事务隔离级别 什么是幻读 如何消除幻读 总结 这是一篇数据库隔离级别的科普文章,旨在了解数据库中著名的幻读现象,为了专注,对脏读.不可重复读不作讨论. 事务隔离级别 MySQL有四级事务隔离级别: 读未提交 READ-UNCOMMITTED: 存在脏读,不可重复读,幻读的问题 读已提交 READ-COMMITTED:不存在脏读,但存在不可重复读,幻读问题 可重复读 REPEATABLE-READ:不存在脏读,不可重复读问题,但存在幻读问题 序列化SERIALIZABLE:解决脏读,不可重复

  • mysql幻读详解实例以及解决办法

    目录 事务隔离级别(tx_isolation) 幻读 RR级别下防止幻读 SERIALIZABLE级别杜绝幻读 总结 脏读/不可重复读的概念都比较容易理解和掌握,这里不在讨论 事务隔离级别(tx_isolation) mysql 有四级事务隔离级别 每个级别都有字符或数字编号 级别 symbol 值 描述 读未提交 READ-UNCOMMITTED 0 存在脏读.不可重复读.幻读的问题 读已提交 READ-COMMITTED 1 解决脏读的问题,存在不可重复读.幻读的问题 可重复读 REPEAT

  • 详解 Mysql 事务和Mysql 日志

    事务特性 1.原子性(Atomicity):事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节. 2.一致性(Consistency):事务开始前和结束后,数据库的完整性约束没有被破坏 .比如A向B转账,不可能A扣了钱,B却没收到. 3.隔离性(Isolation):同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰.比如A正在从一张银行卡中取钱,在A取钱的过程结束前,B不能向这张卡转账. 4.持久性(Durability):事务完成后,事务对数据库的所有更新

  • 详解mysql 中的锁结构

    Mysql 支持3中锁结构 表级锁,开销小,加锁快,不会出现死锁,锁定的粒度大,冲突概率高,并发度最低 行级锁,开销小,加锁慢,会出现死锁,锁定粒度小,冲突概率最低,并发度最高 页面锁,开销和加锁处于表锁和行锁之间,会出现死锁,锁粒度基于表和行之间,并发一般 InnoDB锁问题 InnoDB与MyISAM的最大不同有两点:一是支持事务(TRANSACTION):二是采用了行级锁.  行级锁和表级锁本来就有许多不同之处,另外,事务的引入也带来了一些新问题. InnoDB的行锁模式及加锁方法 Inn

  • 详解MySQL中事务隔离级别的实现原理

    前言 说到数据库事务,大家脑子里一定很容易蹦出一堆事务的相关知识,如事务的ACID特性,隔离级别,解决的问题(脏读,不可重复读,幻读)等等,但是可能很少有人真正的清楚事务的这些特性又是怎么实现的,为什么要有四个隔离级别. 今天我们就先来聊聊MySQL中事务的隔离性的实现原理,后续还会继续出文章分析其他特性的实现原理. 当然MySQL博大精深,文章疏漏之处在所难免,欢迎批评指正. 说明 MySQL的事务实现逻辑是位于引擎层的,并且不是所有的引擎都支持事务的,下面的说明都是以InnoDB引擎为基准.

  • 详解MySQL中事务的持久性实现原理

    前言 说到数据库事务,大家脑子里一定很容易蹦出一堆事务的相关知识,如事务的ACID特性,隔离级别,解决的问题(脏读,不可重复读,幻读)等等,但是可能很少有人真正的清楚事务的这些特性又是怎么实现的,为什么要有四个隔离级别. 在之前的文章我们已经了解了MySQL中事务的隔离性的实现原理,今天就继续来聊一聊MySQL持久性的实现原理. 当然MySQL博大精深,文章疏漏之处在所难免,欢迎批评指正. 说明 MySQL的事务实现逻辑是位于引擎层的,并且不是所有的引擎都支持事务的,下面的说明都是以InnoDB

  • 详解MySQL 数据库隔离级别与MVCC

    Mysql是我们日常生产与学习中最常接触到的数据库之一,今天讲一讲在Mysql(或者说其他类似的数据库)中存在的隔离级别以及用来提高效率的多版本并发控制(MVCC). 一.隔离级别 首先我们需要提到一个概念:事务.什么是事务?事务就是完成一个基础操作的一系列操作语句的一个集合.例如我要将200元从账户A转移到账户B,那么我可能会进行一下的操作: a.验证账户A中的余额是否大于200元. b.将账户A中的余额减200元. c.将账户B中的余额加200元. 我们就将上面的abc三个操作成为一个事务.

  • 详解MySQL事务的隔离级别与MVCC

    事务隔离级别 事务并发执行遇到的问题 脏写 如果一个事务修改了另一个未提交事务修改过的数据,那就意味着发生了脏写 脏读 如果一个事务读到了另一个未提交事务修改过的数据,那就意味着发生了脏读 不可重复读 如果一个事务只能读到另一个已经提交的事务修改过的数据,并且其他事务每对该数据进行一次修改并提交后,该事务都能查询到最新值,那就意味着发生了不可重复读 幻读 如果一个事务先根据某些条件查询出一些记录,之后另一个事务又向表中插入了符合这些条件的记录,原先的事务再次按照该条件查询时,能把另一个事务插入的

  • 详解MySql中InnoDB存储引擎中的各种锁

    目录 什么是锁 InnoDB存储引擎中的锁 锁的算法 行锁的3种算法 幻像问题 锁的问题 脏读 不可重复读 丢失更新 死锁 什么是锁 现实生活中的锁是为了保护你的私有物品,在数据库中锁是为了解决资源争抢的问题,锁是数据库系统区别于文件系统的一个关键特性.锁机制用于管理对共享资源的并发访. 数据库系统使用锁是为了支持对共享资源进行并发访问,提供数据的完整性和一致性 InnoDB存储引擎区别于MyISAM的两个重要特征就是:InnoDB存储引擎支持事务和行级别的锁,MyISAM只支持表级别的锁 In

随机推荐