一文浅析MySQL怎么解决幻读问题
时间:2023-02-06 20:14
MySQL是如何解决幻读问题的?下面本篇文章就来带大家聊聊这个问题,下面就来带着问题一起看看文章吧! 金不三,银不四的高频面试题中,MySQL的事务特性,隔离级别等问题也是非常经典八股文之一,面对此种问题,估计绝大数小伙伴也是信手拈来的事情: 事务特性(ACID): 隔离级别: 而每一种隔离级别导致的问题有: 对于MySQL InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读),从上面的SQL标准的四种隔离级别定义可知, 在进入主题之前,我们先大致了解一下什么是行格式,这样有助于我们理解下面的MVCC,行格式是表中的行记录在磁盘的存放方式, 隐藏列的值不用我们操心, 假设有一条记录如下:插入该记录的 假设之后两个 每次对记录进行改动,都会记录一条 对该记录每次更新后,都会将旧值放到一条 对于数据库的四种隔离级别:1) 有了这个 注:读事物的事物id为0 在 下面我们通过详细例子来说明,两者有何不同: 在时间④中,因事务 在时间⑥中,因事务 在时间⑤,事务 在时间⑧中,如果事务的隔离级别是 在时间⑧中,如果事务的隔离级别是 通过分析MVCC详解部分,可以得出,基于MVCC,在RR隔离级别下,很好解决了 对于间隙锁是如何在当前读的情况下解决幻读问题的,感兴趣朋友可加个关注,点个赞 【相关推荐:mysql视频教程】 以上就是一文浅析MySQL怎么解决幻读问题的详细内容,更多请关注gxlsystem.com其它相关文章!原子性
(Atomicity
)、隔离性
(Isolation
)、一致性
(Consistency
)和持久性
读取未提交
(READ UNCOMMITTED
),读取已提交
(READ COMMITTED
),可重复读
(REPEATABLE READ
),可串行化
(SERIALIZABLE
)READ UNCOMMITTED
隔离级别下,可能发生脏读
、不可重复读
和幻读
问题READ COMMITTED
隔离级别下,可能发生不可重复读
和幻读
问题,但是不可以发生脏读
问题REPEATABLE READ
隔离级别下,可能发生幻读
问题,但是不可以发生脏读
和不可重复读
的问题SERIALIZABLE
隔离级别下,各种问题都不可以发生REPEATABLE-READ(可重复读)
是不可以防止幻读的,但是我们都知道,MySQL InnoDB存储引擎是解决了幻读问题发生的,那他又是如何解决的呢?1. 行格式
Innodb
存储引擎总共有4种不同类型的行格式:compact
、redundant
、dynamic
、compress
;虽然很很多行格式,但是在原理上,大体都相同,如下,为compact
行格式: 从图中可以看出来,一条完整的记录其实可以被分为记录的额外信息
和记录的真实数据
两大部分,记录的额外信息
分别是变长字段长度列表
、NULL值列表
和记录头信息
,而记录的真实数据
除了我们自己定义的列之外,MySQL会为每个记录添加一些默认列,这些默认列又称为隐藏列
,具体列如下:列名 长度 描述 row_id 6个字节 行ID,唯一标识一条记录 transaction_id 6个字节 事务ID roll_pointer 7个字节 回滚指针 InnoDB
存储引擎会自己帮我们生成的,画得再详细一点,compact
行格式如下:然后将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
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
,这样在访问某条记录时,只需要按照下边的步骤判断记录的某个版本是否可见:creator_trx_id
的时候,说明当前事物正在访问自己修改的记录,所以该版本可见min_trx_id
的时候,则说明,在创建read view
的时候,该事物已经提交,该版本,对当前事物可读max_trx_id
,则说明创建该read view
的时候,该说明生成该版本记录的事物id在生成Read view
之后才开启,所以该版本不能被当前事物可读transaction_id
在m_ids
集合中,说明生成Read view
的时候,该事物还是活跃的,还没有被提交,则该版本不可以被访问;如果不在,则说明创建ReadView
时生成该版本的事务已经被提交,可以被访问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. 总结
幻读
问题,但是我们知道,select for update
是产生当前读,不再是快照读,那么此种情况,MySQL又是怎么解决幻读
问题的呢?基于时间问题(整理画图的确需要花比较多的时间),此处先给结论,后面再分析在当前读的情况下,MySQL是怎么解决幻读
问题: