MySQL之MVCC机制实现原理

为什么需要MVCC?

MVCC(multi versionong concurrent control:多版本并发控制),主要是发生在分布式事务上。维特事务隔离级别的数据一致性要求。

事务隔离级别

事务是Innodb存储引擎所特有的,它的隔离级别有四种:

  1. 读未提交(Read Uncommit):一个事务可以读取到另一个事务修改但未提交的数据;

  1. 读已提交(Read Commit,RC):一个事务只能读取另一个事务修改且提交的数据;

  1. 可重复读(Repeat Read:RR):一个事务多次读取的数据都是一致的(默认隔离级别)

  1. 串行化(Serial Read):所有事务都需要串行化执行(排队执行,类似于synchronized)。

事务隔离级别带来的数据一致性问题

脏读

不可重复读

幻读

读未提交

读已提交

可重复读

串行化

  • 脏读:

  1. 原始数据id为1的name为李四;

  1. 事务A修改id为1的name属性为张三(未提交);

  1. 事务B读取到事务A修改且未提交的数据,name为张三;

  1. 事务A在提交之前发生问题,导致事务A回滚,id为1的name属性仍然为李四。

称这种情况为脏读。

原始数据:{id:1, name:李四}

事务A:update user set name='张三' where id = 1

uncommit;

事务B:select name from user where id = 1

结果:{张三}

事务A提交之前中途发生网络问题,事务A回滚,id为1的name为李四

  • 不可重复读:

  1. 原始数据id为1的name为李四;

  1. 事务A修改id为1的name属性为张三(提交);

  1. 事务B读取id为1的name属性为张三,读完之后事务A又修改id为1的name属性为王五(提交);

  1. 事务B读取id为1的name属性为王五。

事务内数据前后读取不一致的问题称为不可重复读。

原始数据:{id:1, name:李四}

事务A:update user set name='张三' where id = 1

commit;

事务B:select name from user where id = 1

结果:{张三}

事务B:select name from user where id = 1

结果:{王五}

事务A:update user set name='王五' where id = 1

commit;

  • 幻读:

  1. 原始数据name为李四只有一条;

  1. 事务A读取name为李四有一条id为1的数据,此时事务B插入另一条name为李四且id为2的数据(提交);

  1. 事务A再次读取name为李四的数据变成两条。

这种前后读取数据条数不一致的情况称为幻读(幻读针对数据的插入,不可重复读针对数据的修改)

原始数据:{id:1, name:李四}

事务A:select * from user where name = 李四

结果:{id:1, name:李四}

事务A:select * from user where name = 李四

结果:

{id:1, name:李四}

{id:2, name:李四}

事务B:insert into user (id, name) values (2, 李四);

commit;

如何解决不同隔离级别带来的数据一致性问题

MySQL利用MVCC机制来解决这个问题。

对于脏读的情况,MySQL使用读(也称共享锁)、写(也称排他锁)锁来进行控制。

例如上面表格描述的脏读的情况,在事务A修改数据前加上写锁,在提交后释放锁。其他事务想要读/写都需要等待锁的释放,这样就不会读取到脏数据。

对于不可重复读、幻读的情况,时使用MVCC机制来进行控制的。

MVCC解决不可重复读和避免幻读

MVCC=ReadView+undo_log,MVCC机制其实就是靠的ReadView和undo_log。

ReadView四个字段

  • m_ids:记录创建ReadView时的活跃事务ID(活跃事务:修改但未提交的事务)

  • m_low_limit_id:活跃事务ID的最小ID

  • m_up_limit_id:活跃事务ID的最大ID+1(也就是下一个产生的活跃事务ID)

  • m_create_trx_id:创建ReadView的事务ID

undo_log两个关键字段

每个数据行都会存在以下两个关键字段(undo_log的具体实现):

  • DB_TRX_ID:当前数据行所从属的事务ID

  • DB_ROLL_PTR:上一个版本的事务ID指针(回滚到上一个版本数据)

RC和RR情况下MVCC情况

  • RC(读已提交):在每次读之前生成ReadView

  1. 图中,事务102、104分别对id为1的name属性进行修改,但是事务都未提交;

  1. 事务103在事务102和事务104之间执行了一条select操作(查询user表中id为1的name属性),此时103在m_ids(102-104)之间,所以事务102、事务104对事务103的查询的数据都不可见,因此找到m_ids的最小事务版本(m_up_limit_id)进行回滚操作,回滚后到达事务101(这个不在m_ids范围内),对事务103的select操作数据可见,因此事务103查询到的name属性为张三。

下一个select操作也是再次生成ReadView视图。

  • RR(可重复度):在第一次读之前生成ReadView

RR是在事务第一次读的时候生成ReadView,然后一直复用这个ReadView,而不是每次读都生成新的ReadView,因此它可以保证多次读的结果都相同。

解决幻读

幻读需要锁与MVCC相结合,也即是在创建视图的同时,添加next-key-lock(对影响到的数据区间进行枷锁),因此不可以在该视图影响的数据区域间进行更新或插入操作,可以打到解决幻读的情况。这种情况对性能有一定的要求,一般情况我们只需要到可重复读的隔离级别就可以了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值