MVCC的底层原理

Mysql事务的特性

Mysql InnoDB引擎,事务有四大特性ACID(atomicity, consistency, isolation, durability)

事务是可以被提交或者回滚的原子工作单元,原子性就是说,当事务中有多个造成数据库数据变更的操作时,要么当事务提交时全部成功,要么当事务回滚时全部不成功

事务在提交或者回滚后,数据库总保持一致性状态。比如多表之间的更新操作,查询事务提交后的多表中的相关数据,应该都是新值;或者事务回滚后,都是旧值。不应该存在混合状态。举个例子,A仓库派一颗料给B仓库,事务提交后,A仓库相关料库存应该少1,B仓库相关料库存应该多1(所谓新值);而如果事务回滚,A、B仓库相关料的库存应该都是原来的数量(所谓旧值)。不应该存在,A仓库少1,B仓库数量不变的情况(混合状态)

多个事务都在进行中时,相互之间是隔离的。事务之间无法相互影响,或者查看到别的事务未提交的数据。官方建议有经验的用户去调整事务隔离的级别,在不造成事务之间相互影响的前提下,以较少的隔离,换取更高的性能和并发能力

一旦提交成功后,事务的最终结果应该是持久的,不应该受电源故障、软件崩溃或其他潜在条件影响。持久性通常涉及写入磁盘存储,具有一定数量的冗余,以防止写入操作期间发生电源故障或软件崩溃。(使用InnoDB引擎,双写缓存对持久性有一定帮助)

原子性是由undo log保证的,一致性是由redo log保证的,隔离性是由MVCC或者锁机制保证的,持久性则由以上特性保证。

隔离性的隔离级别

隔离级别是在多个事务同时进行修改或者查询时,用于平衡性能、可靠性、一致性和重复性的一种设置。

从最高等级的隔离性往下排序,依次是:串行化SERIALIZABLE, 可重复读REPEATABLE READ, 读已提交READ COMMITTED,读未提交READ UNCOMMITTED

InnoDB的默认隔离级别是:可重复读

隔离级别及相关问题对应表

脏读

不可重复读

幻读

读未提交

读已提交

×

可重复读

×

×

串行化

×

×

×

隔离性问题实践

脏读:读到别的事务未提交的记录

不可重复读:同一个A事务内,读取两次,结果不一致,因为读取了B事务提交后的记录

幻读:官方释义是,一行记录出现在查询的结果集中,但是不在早期的查询结果集中。也就是说,当隔离级别设置为可重复读后,A事务不提交,同一个查询(快照读),运行多次,结果集不会发生变化。但是在A事务还未提交时,B事务执行了插入数据的操作,并且B事务也提交了,虽然不会影响A事务内的快照读结果,但是这时候数据库实际上有id为6这条数据,会出现以下两种现象:

1、执行相同的sql:insert into score value(5,"钱小六",80),会报主键冲突异常,有人说这也是一种幻读现象,明明查到只有四条数据,为啥插入第五条会报主键冲突呢?

2、执行select * from score for update,数据6出现在A事务的查询结果集中,但是不出现在早期的查询结果集中。这是官方解释的幻读行(也就是多出了id=6这一行)

所以,幻读强调的是,可重复读的隔离级别下,事务内虽然可以运行相同快照读sql查询出一致的结果,但是限制不了别的事务对数据的新增操作,只要新增相同主键的数据就会报主键冲突异常,或者使用当前读也能看出问题,MySQL 里除了普通查询(select * from xx)是快照读,其他都是当前读,比如update、insert、delete,这些语句执行前都会查询最新版本的数据,然后再做下一步的操作。

另外,select * from score for update 这种查询语句是当前读,每次执行的时候都是读取最新的数据。

什么是MVCC?

MVCC是多版本并发控制(multiversion concurrency control)的缩写,该技术允许具有特定隔离级别的InnoDB事务执行一致性读取操作。

MVCC如何运用到不同的隔离级别中?

什么是Read View?

定义:是InnoDB的MVCC机制使用的一个内部快照

表象:某些事务(取决于它们的隔离级别)可以看到事务(或者sql语句)启动时的数据值

使用范围:可重复读、读已提交、读未提交三种隔离级别都有用到Read View

InnoDB 给数据库每行记录新增了三个字段:

DB_TRX_ID:最近一次修改或者新增本行数据的事务标识

DB_ROLL_PTR:回滚指针,指向了undo log记录

DB_ROW_ID:随着新行记录的插入,单调递增的行标识,当没有主键的时候,就用DB_ROW_ID生成索引。

Read View在mysql源码中是一个class,有多个字段,其中以下四个是核心字段

m_low_limit_id:

The read should not see any transaction with trx id >= this value.

In other words, this is the "high water mark".

m_up_limit_id:

The read should see all trx ids which are strictly smaller (<) than this value.

In other words, this is the "low water mark".

m_creator_trx_id:trx id of creating transaction

m_ids:Set of read view transactions that was active when this snapshot was taken

以上字段,用法如下图

一个事务去访问记录的时候,除了自己的更新记录总是可见之外,还有这几种情况:

  • 如果记录的 trx_id 值小于 Read View 中的m_up_limit_id值,表示这个版本的记录是在创建 Read View 已经提交的事务生成的,所以该版本的记录对当前事务可见
  • 如果记录的 trx_id 值大于等于 Read View 中的 m_low_limit_id 值,表示这个版本的记录是在创建 Read View 才启动的事务生成的,所以该版本的记录对当前事务不可见
  • 如果记录的 trx_id 值在 Read View 的 m_up_limit_id 和 m_low_limit_id 之间,需要判断 trx_id 是否在 m_ids 列表中:
    • 如果记录的 trx_id m_ids 列表中,表示生成该版本记录的活跃事务依然活跃着(还没提交事务),所以该版本的记录对当前事务不可见
    • 如果记录的 trx_id 不在 m_ids列表中,表示生成该版本记录的活跃事务已经被提交,所以该版本的记录对当前事务可见

不同的隔离级别创建Read View的时机是怎样的?

可重复读

期望效果:开启事务,未关闭事务之前,多次执行相同sql读取到的数据都是一致的

隔离级别是可重复读时,是在事务启动时就创建一个Read View,整个事务期间都用这个Read View

此时,数据库的数据是这样的

我们来重现一下,可重复读是如何解决同一事务中,读取两次都能保持一致数据的。

事务A是读取操作,上面说道,可重复读是在事务启动时就创建一个Read View,当事务A执行:select * from score;时,会把数据库的所有数据都和事务A创建的Read View作比较,发现1-5的事务id都在低水位m_up_limit_id以下,所以查出来的数据为全量数据。

事务B为修改操作,把钱小六的分数调高10分。这时候数据库的数据长这样:

此时事务A再执行查询的时候,发现钱小六的DB_TRX_ID比自己创建的Read View的事务id大,于是根据回滚指针,找到undo log的历史记录,读取了钱小六DB_TRX_ID=5的这条日志数据

读已提交

期望效果:开启事务,未关闭事务之前,多次执行相同sql可以读到别人已提交的结果

隔离级别是读已提交时,是在sql执行前创建一个Read View

这个分析方法和可重复读一样,假设现在在可重复读的隔离级别下,

步骤一:A事务读取了一次数据,未提交事务

步骤二:B事务修改了钱小六的分数为90分

步骤三:A事务再次读取表数据

读取之前,数据库长这样:

这里分两种情况:

1、假设步骤二中B事务未提交

这时,活跃事务就有当前事务(DB_TRX_ID=6)和B事务(DB_TRX_ID=7),所以m_ids=[6,7]

由于7还是活跃事务,所以对当前版本对事务A不可见。

 

2、假设步骤二中B事务提交了

这时,活跃事务就只有当前事务(DB_TRX_ID=6),所以m_ids=[6]

当前版本钱小六的事务id号是7,恰好处于高水位和低水位中间,这种情况,我们需要看7是否处于活跃状态,显然m_ids不包含7,所以当前版本对事务A可见。

 

总结

1、Mysql事务有不同的隔离级别,不同的隔离级别也会遇到不同的问题,造成不同问题的原因就在于Mysql读取数据时采取的隔离方法不一样。

2、很显然,Read View就是Mysql设计的一种读取数据的隔离方法。Read View帮助事务,根据隔离级别,判断当前版本是否对事务可见。不同隔离级别创建Read View的时机不同:

【读已提交】是sql执行前创建Read View

【可重复读】是事务开启时创建Read View

2、MVCC基于Read View,是一种通过事务的 Read View 里的字段(高水位事务id,低水位事务id,激活事务id集合等)每行记录中的两个隐藏列字段(事务id,回滚指针)的比对,来控制并发事务访问同一个记录时展示不同访问结果的技术

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值