开发者社区> 我是咔咔> 正文

幻读:听说有人认为我是被MVCC干掉的(2)

简介: 幻读:听说有人认为我是被MVCC干掉的
+关注继续查看

四、再聊当前读、快照读

在上一回合中快照读、当前读已经被消化了,为了防止消化不良这里再简单说明一下。


当前读

所有操作都加了锁,并且锁之间除了共享锁都是互斥的,如果想要增、删、改、查时都需要等待锁释放才可以,所以读取的数据都是最新的记录。


简单来说,当前读就是加了锁的,增、删、改、查,不管锁是共享锁、排它锁均为当前读。


在MySQL的Innodb存储引擎下,增、删、改操作都会默认加上锁,所以增、删、改操作默认就为当前读。


快照读

快照读的出现旨在提高事务并发性,实现基于我的敌人MVCC


简单来说快照读就是不加锁的非阻塞读,即简单的select操作(select * from user)


在Innodb存储引擎下执行简单的select操作时,会记录下当前的快照读数据,之后的select会沿用第一次快照读的数据,即使有其它事务提交也不会影响当前的select结果,这就解决了不可重复读问题。


快照读读取的数据虽然是一致的,但有可能不是最新的数据而是历史数据。


五、告诉你们吧!当前读的情况下我是被next-key locks干掉的

第二小节中得知在快照读下由于我引发的问题已经被MVCC消灭了。


但是在小节三进行案例测试发现在当前读下我又满血复活了。


我要是那么容易被干掉还怎么被称为打不死的小强,这不是闹笑话呢!


说归说,闹归闹如果MVCC把它的小弟next-key locks带上那我就完了,就不再像灰太狼说经典语录“我一定会回来的”


此时就要思考一个问题,在Innodb存储引擎下,是默认给快照读加next-key locks,还是说需要手动加锁。


通过官方文档对于next-key locks的解释。


To prevent phantoms, InnoDB uses an algorithm called next-key locking that combines index-row locking with gap locking. InnoDB performs row-level locking in such a way that when it searches or scans a table index, it sets shared or exclusive locks on the index records it encounters. Thus, the row-level locks are actually index-record locks. In addition, a next-key lock on an index record also affects the “gap” before that index record. That is, a next-key lock is an index-record lock plus a gap lock on the gap preceding the index record. If one session has a shared or exclusive lock on record R in an index, another session cannot insert a new index record in the gap immediately before R in the index order.


大致意思,为了防止幻读,Innodb使用next-key lock算法,将行锁(record lock)和间隙锁(gap lock)结合在一起。Innodb行锁在搜索或者扫描表索引时,会在遇到的索引记录上设置共享锁或者排它锁,因此行锁实际是索引记录锁。另外, 在索引记录上设置的锁同样会影响索引记录之前的“间隙(gap)”。即next-key lock是索引记录行加上索引记录之前的“gap”上的间隙锁定。


并且还给了一个案例SELECT * FROM child WHERE id > 100 FOR UPDATE;


当Innodb扫描索引时,会将id大于100地上锁,阻止任何大于100的数据添加。


到这里就回答了上边问题,在Innodb下解决当前读产生的幻读问题需要手动加锁来解决。


再来看一个案例


下图为此时的数据情况


image.png


下图的这个案例就解决了在第三节中第一个案例的幻读问题。


image.png


step事务1:开启事务

step事务2:开启事务

step事务1:查询ID为4的这条数据并且加上排它锁

step事务2:添加ID为4的数据,并且等待事务1释放锁

step事务1:添加ID为4的数据,添加成功

step事务1:查询当前数据

step事务1:提交事务

step事务2:报错,返回主键重复问题。

这个案例查询的索引列是主键并且是唯一的,此时Innodb引擎会对next-key lock做降级处理,也就是只锁定当前查询的索引记录行,而不是范围锁定。


案例二


还是使用上边的数据,但是这次我们进行一次范围查找。


image.png


此时的数据为1,3,5,查找的范围为大于3。


从下图可以看出当事务2执行添加ID为2的是可以添加成功的。


但是当添加 ID 6时需要等待。


此时若事务1不提交事务,事务2添加ID为6的这条数据就执行不成功。


image.png


对于上述的SQL语句select * from user where id > 3 for update;执行返回的只有5这一行数据。


此时锁定的范围为(3,5],(5,∞),所以说id为2的可以插入,ID为4或者大于5的都是插入不了的。


以上就是在Innodb中解决幻读问题最终方案。


六、幻读解决方案

为了方便大家直观了解幻读的解决方案,这里咔咔进行简单的总结。


通过MVCC解决了快照读下的幻读问题,为什么能解决?在第一次执行简单的select语句就生成了一个快照,并且在后边的select查询都是沿用第一次快照读的结果。所以说快照读查询到的数据有可能是历史数据。


通过next-key lock解决当前读的幻读问题,next-key lock是record lock和gap lock的结合,锁定的是一个范围,如果查询数据为索引记录行,则只会锁定当前行,也就是说降级为record lock。若为范围查找时就会锁定一个范围,例如上例中ID为1,3,5查询大于3的数据,则会把(3,5],(5,∞)进行范围锁定,其它事务在锁未释放之前是无法插入的。


从官方文档还可得知如果需要验证数据唯一性只需要给查询加上共享锁即可,也就是给select 语句加上 in lock share mode,如果返回结果为空,则可以进行插入,并且插入的这个值肯定是唯一的。同样也可以添加next key lock防止其他人同时插入相同数据,小节5的所有案例就是使用的next-key lock,从这一点可以得知next-key lock是可以锁定表内不存在的索引。


根据上述结论来看,如果想要检测数据唯一性使用共享锁,那么多个事务同时开启共享锁,又同时添加相同的数据怎么办,会不会出现问题呢?明确地说明是不会的,如果多个事务同时插入相同数据只会有一个事务添加成功,其它事务会抛出错误,这个就是一个新的概念“死锁”。


七、扩展

事务ID是在何时分配的?

在本文或者其它资料中都能得到一个信息就是当执行一条简单的select语句同时也会生成read-view。


虽然快照读、read-view都是基于事务启动的前提下,但是read-veiw是通过未提交事务ID组成的。


那么到底是在何时分配事务ID的呢?


事务的启动方式有两种,分别为显示启动、另一种是设置autocommit=0后执行select就会启动事务。


在显示启动中最简单的就是以begin语句开始,也可以使用start transaction开启事务。


若使用start trancaction开启事务也可以选择开始只读事务还是读写事务。


看了很多资料都说当开启一个事务时会分配一个事务ID,那么来验证一下是这个样子的吗?


image.png


通过上图可以看到当执行一个begin语句之后查询事务ID是空的,也就说当执行begin后并没有分配trx_id。


那么当执行begin后在支持DML语句呢!


image.png


根据文档得知


执行begin命令并不是真正开启一个事务,仅仅是为当前线程设定标记,表示为显式开启的事务。


所以要明白对数据进行了增、删、改、查等操作后才算真正开启了一个事务,此时会去引擎层开启事务。


为什么事务ID差异特别大?

image.png


上图中查询了当前活跃的事务ID,但是两个事务ID的差异特别大。


相信很多小伙伴都遇到过这个问题,有问题不害怕,害怕的是没有问题。


事实上在这两条数据中只有20841是真正的事务ID,那么第二条数据中的ID是什么呢!


想知道这个数字是什么的前提是知道是怎么来的。


image.png


从上图可以看出,当执行select语句后会产生一个非常大的事务ID,那能不能理解为这种差异非常大的事务ID是通过快照读的方式才会生成的。


接着再这个事务下面在执行一个insert语句,然后再查看一下事务ID的状态


image.png


不可思议的是在事务中先执行select语句,然后执行insert语句,事务ID发生了变化,这是什么原因呢?


经过资料查询得知当执行一个简单的select语句时,被称之为只读事务,为了避免给只读事务分配trx_id带来不必要的开销就没有对其分配事务ID。只读事务没有分配undo segment也不会分配LOCK锁结构,本质上只读事务的trx_id的值就是0,但是为了执行select * from information_schema.INNODB_TRX或者show engine innodb status时就会通过reinterpret_cast(trx) | (max_trx_id + 1)将指针转换为一个64字节非负整数然后位或(max_trx_id + 1) 就是这么个值。


关于这个值的生成过程就不用再去深究了,只需要知道在只读事务下是不会分配事务ID,而查询出来的这个值只是为了显示而存在的没有实际意义。


但是当你执行select * from information_schema.INNODB_TRX查询出来的事务ID,再通过show engine innodb status查询是查不到的。在Innodb下如果事务为只读事务则不会在Innodb数据结构中显示,因此你是看不到的。


版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,阿里云优惠总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系.
29172 0
阿里云服务器ECS登录用户名是什么?系统不同默认账号也不同
阿里云服务器Windows系统默认用户名administrator,Linux镜像服务器用户名root
16455 0
阿里云服务器端口号设置
阿里云服务器初级使用者可能面临的问题之一. 使用tomcat或者其他服务器软件设置端口号后,比如 一些不是默认的, mysql的 3306, mssql的1433,有时候打不开网页, 原因是没有在ecs安全组去设置这个端口号. 解决: 点击ecs下网络和安全下的安全组 在弹出的安全组中,如果没有就新建安全组,然后点击配置规则 最后如上图点击添加...或快速创建.   have fun!  将编程看作是一门艺术,而不单单是个技术。
20700 0
腾讯云服务器 设置ngxin + fastdfs +tomcat 开机自启动
在tomcat中新建一个可以启动的 .sh 脚本文件 /usr/local/tomcat7/bin/ export JAVA_HOME=/usr/local/java/jdk7 export PATH=$JAVA_HOME/bin/:$PATH export CLASSPATH=.
14900 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,云吞铺子总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系统盘、创建快照、配置安全组等操作如何登录ECS云服务器控制台? 1、先登录到阿里云ECS服务器控制台 2、点击顶部的“控制台” 3、通过左侧栏,切换到“云服务器ECS”即可,如下图所示 通过ECS控制台的远程连接来登录到云服务器 阿里云ECS云服务器自带远程连接功能,使用该功能可以登录到云服务器,简单且方便,如下图:点击“远程连接”,第一次连接会自动生成6位数字密码,输入密码即可登录到云服务器上。
36450 0
阿里云服务器ECS远程登录用户名密码查询方法
阿里云服务器ECS远程连接登录输入用户名和密码,阿里云没有默认密码,如果购买时没设置需要先重置实例密码,Windows用户名是administrator,Linux账号是root,阿小云来详细说下阿里云服务器远程登录连接用户名和密码查询方法
22359 0
阿里云服务器怎么设置密码?怎么停机?怎么重启服务器?
如果在创建实例时没有设置密码,或者密码丢失,您可以在控制台上重新设置实例的登录密码。本文仅描述如何在 ECS 管理控制台上修改实例登录密码。
23581 0
+关注
1039
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
JS零基础入门教程(上册)
立即下载
性能优化方法论
立即下载
手把手学习日志服务SLS,云启实验室实战指南
立即下载