MySQL事务原理

Posted 小王子jvm

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MySQL事务原理相关的知识,希望对你有一定的参考价值。

原子性怎么实现的

首先需要知道的就是在mysql中有几种日志,什么undo log,redo log。而原子性就是undo log实现的。

undo log就是记录一些相反的操作,比如我插入一条数据,那么这个日志中记录的就是删除这条数据,更新这条数据,记录的就是更新为原来的数据。这样就可以在事务出现问题,需要回滚保证原子性,通过这个日志就可以查找到我之前的状态然后逐步 恢复过去。

持久性怎么实现

同样也是基于日志,叫做redo log日志。

数据库在更新数据时候,为了提升速度,会有一个对应的Buffer缓冲区,也就是先把数据读取到buffer中,对这个更改之后在刷回磁盘,这样就会出现一个问题,如果还没有刷回就挂了,数据岂不是直接丢了?

所以就有redo log这个东西来记录写入的东西:

  • redo log buffer :保存在内存中,同样是容易丢失的
  • redo log file:保存在磁盘中,不会丢失

有了这个之后再来看看开启事务插入数据的一个过程:

  1. 开启事务
  2. 执行语句(更新或者插入,删除)—> 此时数据就会刷到buffer中
  3. redo log buffer记录修改
  4. commit事务 ----> 此时redo log buffer 就真的写入文件当中

后续刷新到数据磁盘是在MySQL后台完成的

为什么还有binlog这个日志文件

因为最开始 MySQL 里并没有 InnoDB 引擎。MySQL 自带的引擎是 MyISAM,但是 MyISAM 没有 crash-safe 的能力,binlog 日志只能用于归档。而 InnoDB 是另一个公司以插件形式引入 MySQL 的,既然只依靠 binlog 是没有 crash-safe 能力的,所以 InnoDB 使用另外一套日志系统——也就是 redo log 来实现 crash-safe 能力。

这两者是有不同的:

  • binlog是server层的,所有存储引擎都可以使用,而redo log只能用于Innodb。
  • redo log是物理日志,直接记录的是在某个数据页改了什么,而binlog是逻辑日志,记录的是执行语句的逻辑。
  • redo log是循环写,空间大小固定,是会写完的,而binlog是追加的,满了就换下一个binlog。

至此再来看一下一条SQL执行的过程:

  1. 假设是一条更新语句,就会先去取到这个数据,假设id为2
  2. 判断在不在内存的数据页(buffer)中,有就往下执行,没有就从磁盘中读取更新到数据页中
  3. 将数据跟新,假设为就是自增1,然后写回buffer
  4. 然后就写入redo log,此时redo log处于prepare阶段
  5. 写入binlog当中
  6. 提交事务,此时处于commit阶段

这样数据就完整的写入日志磁盘当中,至于真正写回数据磁盘就是MySQL自己啥时候比较闲就去做的事情了。

这个prepare和commit就是两阶段提交了

两阶段提交

为毛数据库要这样搞呢,这是为了保证两个日志文件数据一致性的问题

  1. redo log写入磁盘时候,事务就是prepare
  2. binlog写入之后就是commit

这两都有一个共同的数据字段叫做XID,当数据崩溃进行恢复时候会先按顺序扫描redo log:

  • 如果碰到既有 prepare、又有 commit 的 redo log,就直接提交;
  • 如果说只有prepare,这个时候就要看binlog有没有记录了,拿着XID去binlog中找对应的事务。有记录就提交,没有就回滚。

所以保证持久性使用redo log日志,在更新之前会先写入日志,再去刷新buffer。而且,这是基于一个顺序IO追加的方式写入,效率很搞,buffer写入磁盘是随机的IO,并且buffer是以数据页为单位写入,MySQL中默认数据页大小为16k,一点点修改,也会让整个数据页的数据加载进buffer或者写入磁盘。

MVCC实现隔离性

在并发的情况下会出现一些问题,比如脏读,不可重复读,幻读。

  • 脏读:两个人读取数据,假设A改了数据,但是没有真的把这个数据提交,但是B去读却可以读到这个数据,而这个数据就属于脏数据。
  • 不可重复读:同样的,假设A读取一次数据,然后B改动了这个数据并且提交了,这个时候A再读取发现跟第一次读取不一样了。
  • 幻读:A读取几行数据,然后B插入或者删除几条数据,A再次读取发现数据变多了或者变少了。

对应的为了解决这几个问题,就引出了几种隔离级别:读未提交,读已提交,可重复读,串行化。

  • 读未提交:显然就是什么都解决不了,脏读后面的一系列问题都会出现。

  • 读已提交:显然可以解决脏读,但是不可重复读后面的解决不了。

  • 可重复读:就是为了解决不可重复读。

  • 串行化:都别急,一行一行的执行,都能解决但是速度太慢。

读已提交和可重复读(MVCC就是解决这两个之间的问题)

简单的介绍这个过程,每个数据行都有一个隐藏的数据字段,trx_id,用于记录当前这个更新是哪个事务更新的。

然后就是read view这个东西,就是一个快照,也就是比如开启事务就会生成一个快照,这个事务使用的数据以哪个最新更新的事务数据为标准。(每个事务开启都会有一个事务id,这个事务id是按照开启的时间严格递增的)

假设此时事务数据指向的事务id为90

如果设置的隔离级别为读已提交,那么每条SQL执行前都会生成一个readview:

  1. 如果事务1开启,查询A的值为1(查询之前生成一个ReadView),事务1的id为91
  2. 然后事务2开启,事务id为92,然后更新A的值为2并且提交,那么此时的A指向的事务就是92
  3. 然后事务1再次读取数据A,又会生成ReadView,生成的ReadView会获取到最新的值,也就是A=2,所以会出现不可重复读

如果设置的隔离级别为可重复读,那么RaedView会在事务开启的时候生成。

  1. 如果事务1开启(生成的readview,此时最新的记录就是A为1),查询A的值为1,A为91
  2. 事务2开启(同样最新的记录就是A为1),然后更新这个值并且提交
  3. 事务1再读取,同样会读取到A为2,但是此时A指向的事务id为92,大于自己的,说明这个是在我开启之后更新的,所以不会要,会拿着这个数据在undo log中找上一条记录,直到找到一个事务id比我小的。

所以读已提交和可重复读在这里面的区别就是ReadView生成的时机不同。

这里再来一个事务,假设为3,在事务2修改前修改了,然后事务2去修改会在事务3的基础上去修改,这种读取方式叫做当前读。当然可以推理如果事务1读取而不是修改,读取到的值依然是生成快照的那个值。

最后,我这里的MVCC可能说的非常的简陋,我的理解大概就是这么个意思吧。

以上是关于MySQL事务原理的主要内容,如果未能解决你的问题,请参考以下文章

Spring事务专题事务的基本概念,Mysql事务处理原理

深入学习MySQL事务:ACID特性的实现原理

MySQL事务篇:ACID原则事务隔离级别及事务机制原理剖析

MySQL事务实现原理

Mysql事务隔离级别及ACID实现原理

Mysql原理篇之事务--08