假设当前数据库里有下面这张表。
user表数据库原始状态
老规矩,以下内容还是默认发生在innodb引擎的可重复读隔离级别下。
都是select结果却不同
大家可以看到,线程1,同样都是读 age >= 3
的数据。第一次读到1条数据,这个是原始状态。这之后线程2将id=2的age字段也改成了3。
线程1此时再读两次,一次读到的结果还是原来的1条,另一次读的结果却是2条,区别在于加没加for update。
为什么同样条件下,都是读,读出来的数据却不一样呢?
可重复读不是要求每次读出来的内容要一样吗?
要回答这个问题。
我需要从盘古是怎么开天辟地这个话题开始聊起。
不好意思。
失态了。
那就从事务是怎么回滚的开始聊起吧。
我们在执行事务的时候,一般都是下面这样的格式
begin;
操作1;
操作2;
操作3;
xxxxx
....
commit;
在提交事务之前,会执行各种操作,里面可以包含各种逻辑。
只要是执行逻辑,那就有可能会报错。
回想下事务的ACID
里有个A
,原子性,整个事务就是个整体,要么一起成功,要么一起失败。
ACID
如果失败了的话,那就要让执行到一半的事务有能力回到没执行事务前的状态,这就是回滚。
执行事务的代码就类似写成下面这样。
begin;
try:
操作1;
操作2;
操作3;
xxxxx
....
commit;
except Exception:
rollback;
如果执行rollback
能回到事务执行前的状态的话,那说明mysql需要知道某些行,执行事务前的数据长什么样子。
那数据库是怎么做到的呢?
这就要提到undo日志了,它记录了某一行数据,在执行事务前是怎么样的。
比如id=1
那行数据,name
字段从"小白"更新成了"小白debug",那就会新增一个undo日志,用于记录之前的数据。
undo日志会记录之前的数据
由于同时并发执行的事务可以有很多,于是可能会有很多undo日志,日志里加入事务的id(trx_id
)字段,用于标明这是哪个事务下产生的undo日志。
同时将它们用链表的形式组织起来,在undo日志里加入一个指针(roll_pointer
),指向上一个undo日志,于是就形成了一条版本链。
undo日志版本链
有了这个版本链,当某个事务执行到一半发现失败时,就直接回滚,这时候就可以顺着这个版本链,回到执行事务前的状态。
有了上面的undo日志版本链之后,我们可以看到最新的数据在表头,在这之后的都是一个个旧的数据版本。不管是最新的,还是旧的数据版本,我们都叫它数据快照。
当前读,读的就是版本链的表头,也就是最新的数据。
快照读,读的就是版本链里的其中一个快照,当然如果这个快照正好就是表头,那此时快照读和当前读的结果一样。
当前读和快照读
我们平时执行的普通select语句,比如下面这种,就是快照读。
select * from user where phone_no=2;
而特殊的select语句,比如在select
后面加上lock in share mode
或for update
,都属于当前读。
除此之外insert,update,delete
操作都属于写操作,既然写,那必然是写最新的数据,所以都会引发当前读。
那么问题来了。
当前读,读的是版本链的表头,那么执行当前读的时候,有没有可能恰好有其他事务,生成更加新的快照,替代当前表头,成为新的表头呢,那这时候岂不是读的不是最新数据了?
答案是不会,不管是select … for update这些(特殊的)读操作,还是insert、update这些写操作,都会对这行数据加锁。而生成undo日志快照,也是在写操作的情况下生成的,执行写操作前也需要获得锁。所以写操作需要阻塞等待当前读完成后,获得锁后才能更新版本链。
数据库里可以同时并发执行非常多的事务, 每个事务都会被分配一个事务ID, 这个 ID 是递增的,越新的事务,ID 越大。
而数据表里某行数据的undo日志版本链,每个undo日志上面也有一个事务id (trx_id
),它是创建这个undo日志的事务id。
并不是所有事务都会生成undo日志,也就是说某行数据的undo日志版本链上只有部分事务的id。但是,所有事务都有可能会访问这行数据对应的版本链。而且版本链上虽然有很多undo日志快照,但也不是所有undo日志都能被读,毕竟有些undo日志,创建它们的事务还没提交呢,人家随时可能失败并回滚。
现在的问题就成了,现在有一个事务,通过快照读的方式去读undo日志版本链,那它能读哪些快照?并且它应该读哪个快照?
这里就要引入一个read view的概念。它就像是一个有上下边界的滑动窗口。
整个数据库里有那么多事务,这些事务分为已经提交(commit)的,和没提交的。没提交的,意味着这些事务还在进行中,也就是所谓的活跃事务。所有的活跃事务的id,组成m_ids。而这其中最小的事务id就是read view的下边界,叫min_trx_id。
产生read view的那一刻,所有事务里最大的事务id,加个1,就是这个read view的上边界,叫max_trx_id。
概念太多,有点乱?没事的,继续往下看,后面会有例子的。
有了这些基础信息之后,我们先看下事务在read view下,他能读哪些快照呢?
记住一个大前提:事务只能读到自己产生的undo日志数据(事务提不提交都行),或者是其他事务已经提交完成的数据。
现在事务(假设就叫事务A吧)有了read view之后,不管看哪个undo日志版本链,我们都可以把read view往版本链上一放。版本链就被分成了好几部分。
readview
版本链快照的trx_id < read view的min_trx_id
从上面的描述中,我们可以知道read view的m_ids来源于数据库所有活跃事务的id,而最小的min_trx_id就是read view的下边界,因为事务id是根据时间递增的,所以如果版本链快照的trx_id比 min_trx_id 还要小,那这些肯定都是非活跃(已经提交)的事务id,这些快照都能被事务A读到。
版本链快照的trx_id >= read view的max_trx_id
max_trx_id是在事务A创建read view的那一刻产生的,它比那时候所有数据库已知的事务id都还要大。所以如果undo日志版本链上的某个快照上含有比 max_trx_id 还要大的 trx_id,那说明这个快照已经超出事务A的"理解范围了",它不该被读到。
read view的min_trx_id <= 版本链快照的trx_id < read view的max_trx_id
如果版本链快照的trx_id正好就是事务A的id,那正好是它自己生成的undo日志快照,那不管有没有提交,都能读。
如果版本链快照的trx_id正好在活跃事务m_ids中, 那这些事务数据都还没提交,所以事务A不能读到它们
除了上面两种情况外,剩下的都是已经提交的事务数据,可以放心读。
上面提到,事务在read view的可见范围里,有机会能读到N多快照。但那么多快照版本,事务具体会读哪个快照呢?
事务会从表头开始遍历这个undo日志版本链,它会拿每个undo日志里的trx_id去跟自己的read view的上下边界去做判断。第一个出现的小于max_trx_id的快照。
比如下图,undo日志1
正好小于max_trx_id
,且事务已经提交,那么就读它了。
readview与undo版本链
像上面这种,维护一个多快照的undo日志版本链,事务根据自己的read view
去决定具体读那个undo日志快照,最理想的情况下是每个事务都读自己的一份快照,然后在这个快照上做自己的逻辑,只有在写数据的时候,才去操作最新的行数据,这样读和写就被分开了,比起单行数据没有快照的方式,它能更好的解决读写冲突,所以数据库并发性能也更好。其实这就是面试里常问的MVCC,全称Multi-Version Concurrency Control,即多版本并发控制。
MVCC
之前的写的[一篇文章] 最后留了个问题,四个隔离级别是怎么实现的。
知道了undo日志版本链和MVCC之后,我们再回过头来看下这个问题。
四层隔离级别
读未提交,每次读到的都是最新的数据,也不管数据行所在的事务是否提交。实现也很简单,只需要每次都读undo日志版本链的链表头(最新的快照)就行了。
与读未提交不同,读提交和可重复读隔离级别都是基于MVCC的read view实现的,反过来说, MVCC也只会出现在这两个隔离级别里。
读已提交隔离级别,每次执行普通select,都会重新生成一个新的read view,然后拿着这个最新的read view到某行数据的版本链上挨个遍历,找到第一个合适的数据。这样就能做到每次都读到其他事务最新已提交的数据。
可重复读隔离级别下的事务只会在第一次执行普通select时生成read view
,后续不管执行几次普通select,都会复用这个 read view。这样就能保持每次读的时候都是在同一标准下进行读取,那读到的数据也会是一样的。
串行化目的就是让并发事务看起来就像单线程执行一样,那实现也很简单,和读未提交隔离级别一样,串行化隔离界别下事务只读undo日志链的链表头,也就是最新版本的快照,并且就算是普通select,也会在版本链的最新快照上加入读锁。这样其他事务想写,也得等这个读锁释放掉才行。所有对这行数据进行操作的事务,都老老实实地阻塞等待加锁,一个接一个进行处理,从效果上看就跟单线程处理一样。
我们用上面提到的概念,重新回到文章开头的例子,梳理一遍。
user表数据库原始状态
我们假设数据库一开始的三条数据,都是由trx_id=1
的事务insert
生成的。
于是数据表一开始长下面这样。每行数据只有一个快照。注意快照里,trx_id
填的是创建它们的事务id,也就是刚刚提到的事务1
。roll_pointer
原本应该指向insert产生的undo日志,为了简化,这里写为null
(insert undo日志在事务提交后可以被清理掉)。
user表数据库原始trx信息
下面这个图,还是文章开头的图,这里放出来是为了方便大家,不用划回去看了。
都是select结果却不同在
线程1启动事务,我们假设它的事务trx_id=2
,第一次执行普通select,是快照读,在可重复读隔离级别,会生成一个read view
。当前这个数据库,活跃事务只有它一个,那m_ids =[2]
。m_ids里最小的id,也就是min_trx_id=2
。max_trx_id是当前最大数据库事务id(只有它自己,所以也是2),加个1,也就是max_trx_id=3
事务1的readview
此时线程1的事务,拿着这个read view去读数据库表。
因为这三条数据的trx_id=1都小于min_trx_id=2,都属于可见范围,因此能读到这三条数据的所有快照,最后返回符合条件(age>=3)的数据,有1条。
这时候事务2,假设它的事务trx_id=3
,执行更新操作,生成新的undo日志快照。
user表数据库加入undo日志
此时线程1第二次执行普通select,还是快照读,由于是可重复读,会复用之前的read view,再执行一次读操作,这里重点关注id=2的那行数据,从版本链表头开始遍历,第一个快照trx_id=3>=
read view的max_trx_id=3,因此不可读,遍历下一个快照trx_id=1<
min_trx_id=2,可读。于是id=2的那行数据,还是拿到age=2,而不是更新后的age=3,因此快照读结果还是只有1条数据符合age>=3。
但是线程1第三次读,执行select for update,就成了当前读了,直接读undo日志版本链里最新的那行快照,于是能读到id=2,age=3,所以最终结果返回符合age>=3的数据有2条。
总的来说就是,由于快照读和当前读,读数据的规则不同,我们看到了不一样的结果。
看到这里,大家应该理解了,所谓的可重复读每次读都要读到一样的数据,这里头的"读",指的是快照读。
如果下次面试官问你,可重复读隔离级别下每次读到的数据都是一样的吗?
你该知道怎么回答了吧?
本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/SR0ZL1zAc_0QIQvpDCOANw
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。