mysql幻读及MVCC实例解析

 

个人笔记复制过来的 懒得排版了,这里格式会好点

https://www.wolai.com/jtaGKJqoUusS5mmA5NqoG1

 

参考文章

网上有太多文章不能自圆其说了,也没时间看源码,只能基于他们的信息推断出一个能自圆其说的推断,欢迎指正,下面的变量命名以5.6为准,8.0的命名有些许修改,但是总体逻辑没改变

有基础的同学直接看下面的例子,看看实际结果是否符合预期,如果不符合再看分析会比较容易理解

其实个人觉得研究这种东西挺浪费时间的,学习深层次思想才是该做的东西

官方定义:

The so-called

phantom problem occurs within a transaction when the same query produces different sets of rows at different times. For example, if a SELECT is executed twice, but returns a row the second time that was not returned the first time, the row is a “phantom”row.

翻译成人话就是

两次查询,第二次查询的条数比第一次查询的多,这就是幻读

事务级别(SQL标准):

    
隔离级别 脏读 不可重复读 幻读
READ UNCOMMITTED Possible Possible Possible
READ COMMITTED Not Possible Possible Possible
REPEATABLE READ Not Possible Not Possible Possible
SERIALIZABLE Not Possible Not Possible Not Possible

MVCC

MySQL在REPEATABLE READ隔离级别下,是可以通过MVCC禁止幻读问题的发生的

但是也会存在特殊场景

快照读(无幻读)

在正常快照读下是可以禁止幻读发生

 

 

 

当前读(无幻读)

因为间隙锁的存在,所以也没有幻读发生

 

 

当前读+本事务更新(发生幻读)

 

 

其中一条记录被称为undo日志,这个链表被称为版本链

在此基础上,mysql有一个ReadView的概念

  • trx_ids:表示在生成ReadView时当前系统中活跃的读写事务的事务id列表。

这里也有个异议点,部分文章说的是trx_ids是不包含当前事务的id,但是包不包含其实都不影响判断,下面文章以包含来算

  • up_limit_id:表示在生成ReadView时当前系统中活跃的读写事务中最小的事务id,也就是trx_ids中的最小值。 如果trx_ids为空,则为low_limit_id
  • low_limit_id:表示生成ReadView时系统中应该分配给下一个事务的id值。
  • creator_trx_id:表示生成该ReadView的事务的事务id。

异议点来了,这里的creator_trx_id应该只能为指向trx_t结构体的trx_id的一个指针(或者也有可能直接调用的trx_get_id_for_print方法) 再简单点来说,这个值在发生读写事务切换时是会发生改变的,因为对于mysql5.7来说,开启事务(BEGIN,START TRANSACTION,非START TRANSACTION WITH CONSISTENT SNAPSHOT)的时候事务id为0,而这时候想当于有一个trx_t的结构体,只不过里面的trx_id为0,如果这时该事务变成了只读事务(执行INSERT、DELETE、UPDATE这些语句时),那么会为事务分配真实事务id,折时候的trx_id会变成真实值. 而通过SELECT TRX_ID FROM INFORMATION_SCHEMA.INNODB_TRX WHERE TRX_MYSQL_THREAD_ID = CONNECTION_ID();语句输出时,实际上是调用trx_get_id_for_print方法,当trx_id=0时,输出的是根据指针算出来的值

判断逻辑:

  • db_trx_id < up_limit_id|| db_trx_id == creator_trx_id(显示)

    如果数据事务ID小于read view中的最小活跃事务ID,则可以肯定该数据是在当前事务启之前就已经存在了的,所以可以显示

    或者数据的事务ID等于creator_trx_id ,那么说明这个数据就是当前事务自己生成的,自己生成的数据自己当然能看见,所以这种情况下此数据也是可以显示的。

  • db_trx_id >= low_limit_id(不显示)

    如果数据事务ID大于read view 中的当前系统的最大事务ID,则说明该数据是在当前read view 创建之后才产生的,所以数据不显示。如果小于则进入下一个判断

  • db_trx_id是否在活跃事务m_ids)中

    • 不存在:则说明read view产生的时候事务已经commit了,这种情况数据则可以显示
    • 已存在:则代表我Read View生成时刻,你这个事务还在活跃,还没有Commit,你修改的数据,我当前事务也是看不见的。

rr和rc的区别就是生成ReadView的时机不同,rr只有再第一次查询时生成ReadView,而rc在每一次查询时都会生成ReadView

ReadView和记录改动是两回事,ReadView只会被select/START TRANSACTION WITH CONSISTENT SNAPSHOT触发生成

场景举例

CREATE TABLE test (

id int(11) DEFAULT NULL,

name varchar(255) DEFAULT NULL

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

RR级别

db_trx_id < up_limit_id

  
session A session B
trx_id=1 trx_id=
begin;  
insert into test(id,name) values(1,'1');<br>db_trx_id=1  
commit;  
  begin;
  Select * from test;<br>trx_ids:[]<br>up_limit_id:2<br>low_limit_id:2<br>creator_trx_id:0<br><br>db_trx_id=1:<br>db_trx_id<low_limit_id,数据可见

db_trx_id = creator_trx_id||db_trx_id=low_limit_id

  
session A session B
trx_id=1 trx_id=2
begin;  
  begin;
  Select * from test;<br>trx_ids:[]<br>up_limit_id:1<br>low_limit_id:1<br>creator_trx_id:0<br><br><br>
insert into test(id,name) values(1,'1');<br>db_trx_id=1  
commit;  
  Select * from test;<br>trx_ids:[]<br>up_limit_id:1<br>low_limit_id:1<br>creator_trx_id:0<br>db_trx_id=low_limit_id 数据不可见
  update test set name = '2' where id = 1<br>分配trx_id为2<br>db_trx_id=2
  Select * from test;<br>trx_ids:[]<br>up_limit_id:1<br>low_limit_id:1<br>creator_trx_id:2<br><br>db_trx_id=2<br>db_trx_id = creator_trx_id 数据可见

db_trx_idlow_limit_id

   
session A session B session c
trx_id=1 trx_id=0 trx_id=2
begin;    
  begin;  
  Select * from test;<br>trx_ids:[]<br>up_limit_id:1<br>low_limit_id:1<br>creator_trx_id:0<br><br><br>  
insert into test(id,name) values(1,'1');<br>db_trx_id=1    
commit;    
    begin;
    insert into test(id,name) values(2,'2');<br>db_trx_id=2
    commit;
  Select * from test;<br>trx_ids:[]<br>up_limit_id:1<br>low_limit_id:1<br>creator_trx_id:0<br><br>db_trx_id=1:<br>db_trx_id=low_limit_id 数据不可见<br><br>db_trx_id=2:<br>db_trx_id>low_limit_id 数据不可见  

up_limit_iddb_trx_id < low_limit_id

   
session A session B<br> session C
trx_id=1 trx_id=2 trx_id=0
begin;    
  Begin;  
insert into test(id,name) values(1,'1');<br>db_trx_id=1    
  insert into test(id,name) values(2,'2');<br>db_trx_id=2  
  commit;  
    Select * from test;<br>trx_ids:[1]<br>up_limit_id:1<br>low_limit_id:3<br>creator_trx_id:0<br><br>db_trx_id=1:<br>low_limit_id>db_trx_idup_limit_id &&db_trx_id in trx_ids<br>所以数据不可见<br>db_trx_id=2:<br>low_limit_id>db_trx_idup_limit_id &&db_trx_id not in trx_ids<br>所以数据可见<br><br>
posted @ 2021-12-15 14:27  MRLL  阅读(280)  评论(0编辑  收藏  举报