您当前的位置:首页 > 电脑百科 > 数据库 > MYSQL

MySQL是如何解决幻读问题的?

时间:2023-02-06 12:21:32  来源:今日头条  作者:Java架构学习指南

金不三,银不四的高频面试题中,MySQL的事务特性,隔离级别等问题也是非常经典八股文之一,面对此种问题,估计绝大数小伙伴也是信手拈来的事情:

事物特性(ACID):原子性(Atomicity)、隔离性(Isolation)、一致性(Consistency)和持久性

隔离级别:读取未提交(READ UNCOMMITTED),读取已提交(READ COMMITTED),可重复读(REPEATABLE READ),可串行化(SERIALIZABLE)

而每一种隔离级别导致的问题有:

  • READ UNCOMMITTED隔离级别下,可能发生脏读、不可重复读和幻读问题
  • READ COMMITTED隔离级别下,可能发生不可重复读和幻读问题,但是不可以发生脏读问题
  • REPEATABLE READ隔离级别下,可能发生幻读问题,但是不可以发生脏读和不可重复读的问题
  • SERIALIZABLE隔离级别下,各种问题都不可能发生

对于MySQL InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读),从上面的SQL标准的四种隔离级别定义可知,REPEATABLE-READ(可重复读)是不可以防止幻读的,但是我们都知道,MySQL InnoDB存储引擎是解决了幻读问题发生的,那他又是如何解决的呢?

1. 行格式

  在进入主题之前,我们先大致了解一下什么是行格式,这样有助于我们理解下面的MVCC,行格式是表中的行记录在磁盘的存放方式,Innodb存储引擎总共有4种不同类型的行格式:compact、redundant、dynamic、compress;虽然很很多行格式,但是在原理上,大体都相同,如下,为compact行格式:

 

  从图中可以看出来,一条完整的记录其实可以被分为记录的额外信息和记录的真实数据两大部分,记录的额外信息分别是变长字段长度列表、NULL值列表和记录头信息,而记录的真实数据除了我们自己定义的列之外,MySQL会为每个记录添加一些默认列,这些默认列又称为隐藏列,具体列如下:

列名

长度

描述

row_id

6个字节

行ID,唯一标识一条记录

transaction_id

6个字节

事务ID

roll_pointer

7个字节

回滚指针

隐藏列的值不用我们操心,InnoDB存储引擎会自己帮我们生成的,画得再详细一点,compact行格式如下:

 

  • transaction_id :事物id,当事物对行记录进行修改时,都会将本事物的事物id赋值到该列
  • roll_pointer:每次在对行记录进行改动的时候,都会把旧版本的数据写入undolog日志,然后将roll_pointer 指向该undolog,所以该列相当于一个指针,通过该列,可以找到修改之前的信息

2. MVCC详解

2.1 版本链

假设有一条记录如下:

 

插入该记录的事务id为80,roll_pointer 指针为NULL(为了便于理解,读者可理解为指向为NULL,实际上roll_pointer第一个比特位就标记着它指向的undo日志的类型,如果该比特位的值为1时,就代表着它指向的undo日志类型为insert undo)

假设之后两个事务id分别为100、200的事务对这条记录进行UPDATE操作:

 -- 事务id=100
 update person set grade =20 where id =1;
 update person set grade =40 where id =1;
 -- 事务id=200
 update person set grade =70 where id =1;

  每次对记录进行改动,都会记录一条undo日志,每条undo日志也都有一个roll_pointer属性(INSERT操作对应的undo日志没有该属性,因为该记录并没有更早的版本),可以将这些undo日志都连起来,串成一个链表,所以现在的情况就像下图一样:

 

  对该记录每次更新后,都会将旧值放到一条undo日志中,就算是该记录的一个旧版本,随着更新次数的增多,所有的版本都会被roll_pointer属性连接成一个链表,我们把这个链表称之为版本链,版本链的头节点就是当前记录最新的值。另外,每个版本中还包含生成该版本时对应的事务id

2.2 ReadView

  对于数据库的四种隔离级别:1)read uncommitted;2) read committed;3) REPEATABLE READ; 4)SERIALIZABLE;来说,READ UNCOMMITTED,每次读取版本链的最新数据即可;SERIALIZABLE,主要是通过加锁控制;而read committed和REPEATABLE READ都是读取已经提交了的事物,所以对于这两个隔离级别,核心问题是版本链中,哪些事物是对当前事物可见;为了解决这个问题,MySQL提出了read view 概念,其包含四个核心概念:

  • m_ids:生成read view 时候,活跃的事物id集合
  • min_trx_id:m_ids的最小值,既生成read view的时候,活跃事物的最小值
  • max_trx_id:表示生成read view的时候,系统应该分配下一个事物id值
  • creator_trx_id:创建read view的事物id,即当前事物id。

有了这个ReadView,这样在访问某条记录时,只需要按照下边的步骤判断记录的某个版本是否可见:

 

  • 当记录的事物id等于creator_trx_id的时候,说明当前事物正在访问自己修改的记录,所以该版本可见
  • 如果被访问的版本事物id小于min_trx_id的时候,则说明,在创建read view的时候,该事物已经提交,该版本,对当前事物可读
  • 如果被访问的版本事物id大于或等于max_trx_id,则说明创建该read view的时候,该说明生成该版本记录的事物id在生成Read view之后才开启,所以该版本不能被当前事物可读
  • 如果被访问的版本事物transaction_id在m_ids集合中,说明生成Read view的时候,该事物还是活跃的,还没有被提交,则该版本不可以被访问;如果不在,则说明创建ReadView时生成该版本的事务已经被提交,可以被访问

注:读事物的事物id为0

在MySQL中,READ COMMITTED和REPEATABLE READ隔离级别的的一个非常大的区别就是它们生成ReadView的时机不同:

  • READ COMMITTED —— 每次读取数据前都生成一个ReadView
  • REPEATABLE READ —— 在第一次读取数据时生成一个ReadView

下面我们通过详细例子来说明,两者有何不同:

时间编号

 

trx 100

trx 200

BEGIN;

 

 

 

BEGIN;

BEGIN;

 

update person set grade =20 where id =1;

 

 

update person set grade =40 where id =1;

 

SELECT * FROM person WHERE id = 1;

 

 

 

COMMIT;

 

 

 

update person set grade =70 where id =1;

SELECT * FROM person WHERE id = 1;

 

 

 

 

COMMIT;

 

COMMIT;

 

 

在时间④中,因事务trx 100 执行了事务的提交,id=1行记录的版本链如下:

 

在时间⑥中,因事务trx 200 执行了事务的提交,id=1行记录的版本链如下:

 

在时间⑤,事务trx 100执行select语句时会先生成一个ReadView,ReadView的m_ids列表的内容就是[100, 200],min_trx_id为100,max_trx_id为201,creator_trx_id为0,此时,从版本链中选可见的记录,版本链从上到下遍历:因为grade=40,trx_id值为100,在m_ids里,所以该记录不可见,同理,grade=20的也不见。继续往下遍历,grade=20,trx_id值为80,小于小于ReadView中的min_trx_id值100,所以这个版本符合要求,返回给用户的是等级为10的记录。

在时间⑧中,如果事务的隔离级别是READ COMMITTED,会单独又生成一个ReadView,该ReadView的m_ids列表的内容就是[200],min_trx_id为200,max_trx_id为201,creator_trx_id为0,此时,从版本链中选可见的记录,版本链从上到下遍历:因为grade=70,trx_id值为200,在m_ids里,所以该记录不可见,继续往下遍历,grade=40,trx_id值为100,小于ReadView中的min_trx_id值200,所以这个版本是符合要求的,返回给用户的是是等级为40的记录。

在时间⑧中,如果事务的隔离级别是 REPEATABLE READ,在时间⑧中,不会单独生成一个ReadView,而是沿用时间5的ReadView,所以返回给用户的等级是10。前后两次select得到的是一样的,这就是可重复读的含义。

3. 总结

  通过分析MVCC详解部分,可以得出,基于MVCC,在RR隔离级别下,很好解决了幻读问题,但是我们知道,select for update是产生当前读,不再是快照读,那么此种情况,MySQL又是怎么解决幻读问题的呢?基于时间问题(整理画图的确需要花比较多的时间),此处先给结论,后面再分析在当前读的情况下,MySQL是怎么解决幻读问题:

  • 当前读 : 使用 Next-Key Lock(间隙锁) 进行加锁来保证不出现幻读


Tags:   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
无出境记录!警方披露“猫一杯炮制秦朗事件”详情
今年春节假期,“秦朗巴黎丢寒假作业”事件曾占据多个热搜榜单,众多自媒体竞相跟进炒作,衍生出“秦朗舅舅”“西场小学”等多个话题。近日,警方通报“在巴黎拾到小学生秦朗作业本...【详细内容】
2024-04-15  Search:   点击:(1)  评论:(0)  加入收藏
SU7限时4月19日可改配置!小米:会显著推迟交付时间
快科技4月15日消息,小米SU7自从上市以来,已经积累了无数订单,如今下单交车时间超过半年。需要注意的是,因为当天晚上很多车主都在赶时间抢购,希望早日提车,所以下定的配置可能并没...【详细内容】
2024-04-15  Search:   点击:(1)  评论:(0)  加入收藏
梁朝伟六夺金像影帝!网友褒贬不一
4月14日,第42届香港电影金像奖举行颁奖典礼,梁朝伟凭借《金手指》第六次拿到影帝!此前五次分别是:第14届《重庆森林》(1995年),第17届《春光乍泄》(1998年),第20届《花样年华》(2001年),...【详细内容】
2024-04-15  Search:   点击:(2)  评论:(0)  加入收藏
希望你好 但不希望你太好:我们为什么会嫉妒别人
我们似乎无时无刻不在与身边的人进行比较:读书时比成绩,上班时比工作;年少比家境,长大后晒娃.....社会心理学家利昂·费斯廷格认为:个体的内心深处,有一种强烈的驱动力,驱使...【详细内容】
2024-04-15  Search:   点击:(2)  评论:(0)  加入收藏
华为宣布P系列品牌升级为Pura
【环球网科技综合报道】4月15日,华为官方宣布,其经典P系列手机将进行全面升级,更名为全新的Pura系列。自2012年华为P系列手机首次亮相以来,历经12年的技术积淀和市场打磨,今日该...【详细内容】
2024-04-15  Search:   点击:(2)  评论:(0)  加入收藏
大额存单“量价”齐降,银行负债端压力仍存
压降负债端成本是其中一步。近期,银行为缓解负债端压力,对存款产品进行持续调整,一边下架长期限大额存单,一边调降存款利率。但第一财经记者注意到,部分区域性银行仍在发行大额存...【详细内容】
2024-04-15  Search:   点击:(2)  评论:(0)  加入收藏
多期限美债收益率快速攀升 “全球资产定价之锚”再掀波澜
◎记者 陈佳怡降息预期收敛,正在改写全球资本市场叙事。近期,受美国通胀、就业等经济基本面数据超预期影响,市场对美联储年内降息预期退坡。“全球资产定价之锚”再掀波澜,10年...【详细内容】
2024-04-15  Search:   点击:(2)  评论:(0)  加入收藏
王健林停不下卖资产步伐,“险资”接盘北京万达实业
拉来“中东金主”投资后,王健林变卖资产的步伐依然未止。4月12日,天眼查信息显示,北京万达广场实业有限公司(简称“北京万达实业”)日前发生工商变更,原全资股东大连万达商业管理...【详细内容】
2024-04-15  Search:   点击:(2)  评论:(0)  加入收藏
3年花600万美刀 和儿子换血的富豪公布对比照:被嘲还不如以前
还记得那个每年花200万美元,组建了一个30几人的专家团队,为其一人服务,制定了一套非常严格的生活起居计划,只为了能“逆转衰老”的美国富豪布莱恩·约翰逊Bryan Johnson吗...【详细内容】
2024-04-15  Search:   点击:(2)  评论:(0)  加入收藏
放弃百万年薪,大厂人跳到国企后悔了吗?
当互联网行业的高速发展告一段落,不少大厂人主动或被迫地离开了“高速公路”,转而投奔更稳定的国企。他们也管这叫做“上岸”。在这个时代,人们似乎有上不完的岸,幼升小、小升初...【详细内容】
2024-04-15  Search:   点击:(2)  评论:(0)  加入收藏
▌简易百科推荐
MySQL 核心模块揭秘
server 层会创建一个 SAVEPOINT 对象,用于存放 savepoint 信息。binlog 会把 binlog offset 写入 server 层为它分配的一块 8 字节的内存里。 InnoDB 会维护自己的 savepoint...【详细内容】
2024-04-03  爱可生开源社区    Tags:MySQL   点击:(7)  评论:(0)  加入收藏
MySQL 核心模块揭秘,你看明白了吗?
为了提升分配 undo 段的效率,事务提交过程中,InnoDB 会缓存一些 undo 段。只要同时满足两个条件,insert undo 段或 update undo 段就能被缓存。1. 关于缓存 undo 段为了提升分...【详细内容】
2024-03-27  爱可生开源社区  微信公众号  Tags:MySQL   点击:(14)  评论:(0)  加入收藏
MySQL:BUG导致DDL语句无谓的索引重建
对于5.7.23之前的版本在评估类似DDL操作的时候需要谨慎,可能评估为瞬间操作,但是实际上线的时候跑了很久,这个就容易导致超过维护窗口,甚至更大的故障。一、问题模拟使用5.7.22...【详细内容】
2024-03-26  MySQL学习  微信公众号  Tags:MySQL   点击:(13)  评论:(0)  加入收藏
从 MySQL 到 ByteHouse,抖音精准推荐存储架构重构解读
ByteHouse是一款OLAP引擎,具备查询效率高的特点,在硬件需求上相对较低,且具有良好的水平扩展性,如果数据量进一步增长,可以通过增加服务器数量来提升处理能力。本文将从兴趣圈层...【详细内容】
2024-03-22  字节跳动技术团队    Tags:ByteHouse   点击:(28)  评论:(0)  加入收藏
MySQL自增主键一定是连续的吗?
测试环境:MySQL版本:8.0数据库表:T (主键id,唯一索引c,普通字段d)如果你的业务设计依赖于自增主键的连续性,这个设计假设自增主键是连续的。但实际上,这样的假设是错的,因为自增主键不...【详细内容】
2024-03-10    dbaplus社群  Tags:MySQL   点击:(12)  评论:(0)  加入收藏
准线上事故之MySQL优化器索引选错
1 背景最近组里来了许多新的小伙伴,大家在一起聊聊技术,有小兄弟提到了MySQL的优化器的内部策略,想起了之前在公司出现的一个线上问题,今天借着这个机会,在这里分享下过程和结论...【详细内容】
2024-03-07  转转技术  微信公众号  Tags:MySQL   点击:(31)  评论:(0)  加入收藏
MySQL数据恢复,你会吗?
今天分享一下binlog2sql,它是一款比较常用的数据恢复工具,可以通过它从MySQL binlog解析出你要的SQL,并根据不同选项,可以得到原始SQL、回滚SQL、去除主键的INSERT SQL等。主要...【详细内容】
2024-02-22  数据库干货铺  微信公众号  Tags:MySQL   点击:(53)  评论:(0)  加入收藏
如何在MySQL中实现数据的版本管理和回滚操作?
实现数据的版本管理和回滚操作在MySQL中可以通过以下几种方式实现,包括使用事务、备份恢复、日志和版本控制工具等。下面将详细介绍这些方法。1.使用事务:MySQL支持事务操作,可...【详细内容】
2024-02-20  编程技术汇    Tags:MySQL   点击:(54)  评论:(0)  加入收藏
MySQL数据库如何生成分组排序的序号
经常进行数据分析的小伙伴经常会需要生成序号或进行数据分组排序并生成序号。在MySQL8.0中可以使用窗口函数来实现,可以参考历史文章有了这些函数,统计分析事半功倍进行了解。...【详细内容】
2024-01-30  数据库干货铺  微信公众号  Tags:MySQL   点击:(55)  评论:(0)  加入收藏
mysql索引失效的场景
MySQL中索引失效是指数据库查询时无法有效利用索引,这可能导致查询性能显著下降。以下是一些常见的MySQL索引失效的场景:1.使用非前导列进行查询: 假设有一个复合索引 (A, B)。...【详细内容】
2024-01-15  小王爱编程  今日头条  Tags:mysql索引   点击:(87)  评论:(0)  加入收藏
相关文章
    无相关信息
站内最新
站内热门
站内头条