MVCC多版本并发控制

在并发访问同一条数据的时候,可能会造成数据的丢失情况。

 

例如:

事务A和事务B查询同一个账户,A查看余额为1000,B也查看余额为1000.

A存了100进去,提交事务之后余额为1100.而事务B取出100,提交事务之后余额变成900.

然而实际上存100,再取出100,总的额度应该还是1000的。

基于锁解决并发问题:

使用锁的机制,在事务里修改数据的时候加锁,同一时间只有一个实务可以修改当前的数据,,别的事务想要修改的话必须要等当前的事务释放锁。

MVCC(多版本并发控制):

MVCC使得在读的时候不需要加锁,(即普通的select不需要加锁),提高了数据库的并发访问能力。

在使用mvcc的情况下,如果事务A开始修改账户,但是未提交,此时事务B想要读取账户,那么此时事务B读到的是事务A修改操作之前的账户副本,但如果事务B想要修改数据就必须要等到事务A提交之后才行。

非锁定读:MVCC实现的就是非锁定读。在读/读的情况下,并发并不会发生问题。但是读/写就会出现问题。在非锁定读模式下,如果一个事务修改了数据但是未提交,另一个事务通过普通的select查询数据的时候,读取的是一个快照版本,这也被称为快照读,这种不加锁的读取方式大大的提高了数据库的并发能力。

锁定读:

例如:select... lock in share mode, select... for update

以上这些都是锁定读,会在读数据的时候加锁,读取的数据是最新的版本,所以他也可以被叫做是当前读。

InnoDB对于MVCC的实现:

MVCC是实现主要依赖于:隐藏字段,read view,undo log。

隐藏字段:

在InnoDB中,每一行数据都添加了三个隐藏字段。

1. TRX_ID 记录最后一次修改改行数据的事务ID。

2. ROLL_PTR 回滚指针,指向undo log,指向修改之前的一个版本数据。

3. ROW_ID 如果没有设置主键,那么InnoDB会用这个ID自动生成一个聚集索引。

ReadView:(读视图)

主要是用来做可见性判断的。这里面保存了当前对本事务不可见的活跃的事务。(当前别的事务做的修改,删除,但是未提交的事务被称为活跃的事务)。

low_limit :当前出现过的最大的事务ID+1,即下一个分配的事务ID,每个事务会颁发一个id,这个id是递增的。如果当前读的行的ID大于等于这个值,那么就是不可见的。

up_limit : 活跃事务列表里最小的事务ID,如果小于这个值,那么就直接是可见的。如果大于这个值但是小于最大事务I D,就需要去活跃列表里比对了。

m_ids: 一个保存了所有活跃事务的列表(未提交事务),当创建绒read view的时候,记录下当前活跃的事务,即使这些事务后期修改了数据也对当前的事务不可见。

 Undo Log:(回滚日志)

回滚日志主要有两个作用:

1. 在事务回滚的时候,恢复之前的数据。

2. 在MVCC中,当读取记录的时候,如果该记录被其他事务占用或者对当前的事务不可见,就需要通过记录中的回滚指针在undo log中读取之前版本的数据,实现非锁定读。

InnoDB 存储引擎中 undo log 分为两种: insert undo logupdate undo log

  1. insert undo log :指在 insert 操作中产生的 undo log。因为 insert 操作的记录只对事务本身可见,对其他事务不可见,故该 undo log 可以在事务提交后直接删除。不需要进行 purge 操作。
  2. update undo logupdatedelete 操作中产生的 undo log。该 undo log可能需要提供 MVCC 机制,因此不能在事务提交时就进行删除。提交时放入 undo log 链表,等待 purge线程 进行最后的删除。

数据可见性算法:

  1. 如果记录 DB_TRX_ID < m_up_limit_id,那么表明最新修改该行的事务(DB_TRX_ID)在当前事务创建快照之前就提交了,所以该记录行的值对当前事务是可见的

  2. 如果 DB_TRX_ID >= m_low_limit_id,那么表明最新修改该行的事务(DB_TRX_ID)在当前事务创建快照之后才修改该行,所以该记录行的值对当前事务不可见。跳到步骤 5

  3. m_ids 为空,则表明在当前事务创建快照之前,修改该行的事务就已经提交了,所以该记录行的值对当前事务是可见的

  4. 如果 m_up_limit_id <= DB_TRX_ID < m_low_limit_id,表明最新修改该行的事务(DB_TRX_ID)在当前事务创建快照的时候可能处于“活动状态”或者“已提交状态”;所以就要对活跃事务列表 m_ids 进行查找(源码中是用的二分查找,因为是有序的)

    • 如果在活跃事务列表 m_ids 中能找到 DB_TRX_ID,表明:① 在当前事务创建快照前,该记录行的值被事务 ID 为 DB_TRX_ID 的事务修改了,但没有提交;或者 ② 在当前事务创建快照后,该记录行的值被事务 ID 为 DB_TRX_ID 的事务修改了。这些情况下,这个记录行的值对当前事务都是不可见的。跳到步骤 5

    • 在活跃事务列表中找不到,则表明“id 为 trx_id 的事务”在修改“该记录行的值”后,在“当前事务”创建快照前就已经提交了,所以记录行对当前事务可见

  5. 在该记录行的 DB_ROLL_PTR 指针所指向的 undo log 取出快照记录,用快照记录的 DB_TRX_ID 跳到步骤 1 重新开始判断,直到找到满足的快照版本或返回空

InnoDB在RC和RR的隔离级别下使用MVCC(多版本并发控制),但是生成read view的时机是不同的。在RC隔离级别下,每次查询都会生成一个最新的read view。而在RR的隔离级别下,在事务开启之后,只会生成一次read view,就是第一次select的语句。

在RR的情况下使用MVCC只能防止部分幻读的情况发生,通过维持只采用第一次select 查询到的read view ,那么只能读取到第一次查询之前所插入的数据。但是还有一种情况下,无法解决幻读的问题。当使用当前读的模式下(select xxx in share lock mode),这时候如果有别的事务插入了数据,那么两次读出来的行数都不同了。所以InnoDB采用了next-key lock 来解决这种情况。

总结:因此,我们可以通过MVCC+next-key lock 来解决在RR隔离级别下的幻读问题。

next-key lock:

gap lock 间隙锁:锁住行数据之间的间隙,防止有数据插入。

如下图所示,有6行数据,那么就有7个间隙。

当我们在执行select for update 这种当前读的操作的时候,不仅仅会给6个数据加锁,也会给这7个间隙加上锁,防止插入数据在间隙里。

next-key lock 是间隙锁+行锁的结合体。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值