MySQL如何解决幻读问题

发布时间:2023-02-07 16:51:54 作者:iii
来源:亿速云 阅读:157

这篇文章主要介绍“MySQL如何解决幻读问题”,在日常操作中,相信很多人在MySQL如何解决幻读问题问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”MySQL如何解决幻读问题”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

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

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

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

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

1. 行格式

  在进入主题之前,我们先大致了解一下什么是行格式,这样有助于我们理解下面的MVCC,行格式是表中的行记录在磁盘的存放方式,Innodb存储引擎总共有4种不同类型的行格式:compactredundantdynamiccompress;虽然很很多行格式,但是在原理上,大体都相同,如下,为compact行格式:MySQL如何解决幻读问题  从图中可以看出来,一条完整的记录其实可以被分为记录的额外信息记录的真实数据两大部分,记录的额外信息分别是变长字段长度列表NULL值列表记录头信息,而记录的真实数据除了我们自己定义的列之外,MySQL会为每个记录添加一些默认列,这些默认列又称为隐藏列,具体列如下:

列名长度描述
row_id6个字节行ID,唯一标识一条记录
transaction_id6个字节事务ID
roll_pointer7个字节回滚指针

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

MySQL如何解决幻读问题

2. MVCC详解

2.1 版本链

假设有一条记录如下:MySQL如何解决幻读问题插入该记录的事务id80roll_pointer 指针为NULL(为了便于理解,读者可理解为指向为NULL,实际上roll_pointer第一个比特位就标记着它指向的undo日志的类型,如果该比特位的值为1时,就代表着它指向的undo日志类型为insert undo)

假设之后两个事务id分别为100200的事务对这条记录进行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日志都连起来,串成一个链表,所以现在的情况就像下图一样:

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

2.2 ReadView

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

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

注:读事物的事物id为0

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

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

时间编号
trx 100trx 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行记录的版本链如下:

MySQL如何解决幻读问题在时间⑥中,因事务trx 200 执行了事务的提交,id=1行记录的版本链如下:

MySQL如何解决幻读问题

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

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

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

到此,关于“MySQL如何解决幻读问题”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!

推荐阅读:
  1. 解决MySQL幻读方法简析
  2. MySQL解决幻读的方法

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

mysql

上一篇:jquery如何删除最后一个子元素

下一篇:linux nc和telnet的区别有哪些

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》