InnoDB 中事务的解决方案

InnoDB 中事务的解决方案

我们知道单机事务中事务的解决方案有两个层面:
其一是事务本身需要满足的条件:也就是事务的ACID四大特性,数据库需要采用什么措施来保证
其二是并发事务带来的问题:也就是脏读,不可重复读和幻读,数据库应该如何来解决这些问题

(1)事务的ACID:
innodb中针对事务本身的ACID的要求分别采取了一下措施来保证

D-持久性:

  • innodb是通过 redo log 和 double write 双写缓冲来实现的事务的持久性的,我们操作数据的时候,会先写到内存的 buffer pool 里面,同时记录 redo log,如果在buffer pool刷盘之前出现数据库出现异常,在重启后就可以读取 redo log 的内容,写入到磁盘,保证数据的持久性
  • 当然,恢复成功的前提是数据页本身没有被破坏,是完整的,这个通过双写缓冲(double write)保证

A-原子性:

  • innodb采用undo log 逻辑日志来实现事务的原子性,当然undo log还可以作为快照备份供其他事务进行快照读
  • 为什么这么说呢?因为原子性的含义是要么全部成功,要么全部失败,一旦全部成功,那就进入持久性的环节了,而全部失败就是需要将事务之前的操作进行回滚,这正是undo log的工作

C-一致性:

  • 一致性常常指的是逻辑的自洽性

I-隔离性:

  • 隔离性描述的就是多个事务并发执行的时候,并发执行的事务不会相互影响,其对数据库的影响和它们串行执行时一样
  • 隔离性描述的是事务处于完美状态下的要求,满足隔离性最简单的方案就是串行化,但是由于串行化会导致性能的严重下降,所以innodb使用MVCC+锁机制希望在并发的场景下带来同样的效果

(2)事务并发带来的问题:

通过上面的描述,我们明白了其实隔离性描述的就是事务并发带来的问题(脏读、幻读、不可重复读)的解决方案
也就是说innodb对并发事务事务带来的问题的解决,其实就是对事务隔离性的要求的保证

InnoDB 中并发事务问题的解决方案:MVCC+锁
在 InnoDB 中并发事务问题的解决方案是MVCC+锁,使用Next-Key Lock 锁保证了在RR(可重复读)级别即可实现事务最高隔离级别的要求

MVCC 的核心思想是:
一般我们认为MVCC有下面几个特点:

  • 每行数据都存在一个版本号,每次数据更新时都更新该版本号
  • 修改时Copy出当前版本随意修改,不影响原始数据,各个事务之间无干扰
  • 保存时比较版本号,如果成功(commit),则覆盖原记录;失败则放弃copy(rollback)

简单来说就是每行数据都有版本号,保存时根据版本号决定是否成功,是属于乐观并发控制的一种实现

而Innodb的实现方式是:

  • 事务以排他锁的形式修改原始数据
  • 把修改前的数据存放于undo log,通过回滚指针与主数据关联
  • 修改成功(commit)则啥都不做,失败则恢复undo log中的数据(rollback)

二者最本质的区别是,当修改数据时是否要排他锁定,如果锁定了还算不算是MVCC?

  • MVCC因为是对复制的数据版本进行的修改,所以修改的过程不需要加锁,只有合并数据的时候需要解决冲突或者选择其中一个成功
  • 而innodb因为是对原始数据的修改所以必须要加锁,这样说起来innodb的实现并不是纯粹的
InnoDB实现MVCC原理:

InnoDB会为每一行数据添加两个隐藏字段,分别表示该行创建时的版本号和删除时的版本号,这两个版本号填入的是系统版本号,系统版本号会随着新事务的创建而不断递增

**事务版本号:**事务开始时的系统版本号会作为当前事务的版本号
通过每行数据的版本号(创建版本号/删除版本号)与操作数据的事务的版本号比较来实现数据版本控制

  • select:满足以下两个条件innodb会返回该行数据:
    • 该行的创建版本号小于等于当前事务版本号,保证改行在当前事务创建之前已经被插入
    • 该行的删除版本号大于当前事务版本或者为空。删除版本号大于当前事务版本号意味着当前事务并未执行该行的删除操作
  • insert:将新插入的行的创建版本号设置为当前系统的版本号,说明该行在当前版本被插入。
  • delete:将要删除的行的删除版本号设置为当前系统的版本号,说明该行在当前版本被删除。
  • update:不执行原地update,而是转换成insert + delete。将旧行的删除版本号设置为当前版本号,并将新insert的行的创建版本号设置为当前版本号

InnoDB 中事务中的锁是直接对原始数据加的,而读则可以从两个方面拿到,其一是从快照信息(undo log中读的),其二是从原始数据

具体示例:
首先假设我们有一张数据库表user ,其中以一条数据 id:1name:1age1
事务开始之前系统版本号10,事务1(版本号11),事务2(版本号12),事务3(版本号13),事务4(版本号14)

1、事务1和事务2读取数据
在这里插入图片描述
2、执行步骤3 写的时候,事务2会首先复制原数据到undo log中,然后在原数据上添加一把写锁(可重入排它锁)。(unlog永远不会被修改,因为一旦事务2修改数据失败,需要把数据回滚到初始状态)
3、事务2因为给原始数据加的是可重入的排它锁,所以再次读取数据的时候是读取的是原始数据。事务1和事务3不能读取获得锁,因此只能读取快照数据
在这里插入图片描述

4.步骤7:事务2会执行提交操作,写锁会解除,原始数据会标识删除版本号,新插入数据会标识创建版本号
5、由于事务读取数据的时候,只能读取到创建版本号比自己低的,并且删除版本号为null,或者大于自己版本号的数据,因此事务1和事务3只能读取到快照数据,而事务4可以读取新数据
在这里插入图片描述
6、事务2提交了修改的数据,事务2 结束,原始数据变为修改之后的值
7、步骤11:事务1发出写请求,事务1会首先复制数据2提交之后的数据到undo log2中,然后直接在事务2提交的数据上添加一把写锁(可重入排它锁),事务3和事务4读取数据的时候按照规则访问数据
在这里插入图片描述
8、步骤15,事务1提交修改,事务1仅仅只修改age字段,将其值从1改成2,提交之后,原始数据变成了(id:1,name:2,age2),说明是事务1和事务2共同修改完成的,这也证明了我们的说法:事务的读是从快照信息(undo log中读取的),事务的写是直接修改原始数据
在这里插入图片描述

9.在事务1、2、3结束之后,会有后台线程清理结束的事务的undo log,避免undo log链过长,并且定期清除标记了删除的数据
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值