博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
MySQL事务ACID底层实现原理
阅读量:2496 次
发布时间:2019-05-11

本文共 4460 字,大约阅读时间需要 14 分钟。

事务的特性

一批数据同时成功或者同时失败,这类需求就可以简单的理解为具有事务性,也就是ACID

  • A (Atomicity, 原子性):一个事务中的所有操作要不全部成功,要不全部失败,不能出现部分成功,部分失败的情况。
  • C(Consistency,一致性):数据库设计上这个含义比较模糊,简单可以理解为财务的对账一样,两边数据的加加减减必须要能保持一致。
  • I(Isolation,隔离性):主要是针对在并发访问数据时要有一定的隔离性,在MySQL中隔离性也是分等级的,根据不同的业务需求选择不同的隔离性,主要依靠锁+MVCC来实现,隔离性越强,数据库的吞吐就越差。
  • D(Durability,持久性):事务一旦提交,数据将会保存到数据库中,此时如果数据库发生错误,也不会造成数据丢失。

原子性的实现

MySQL主要是利用undo日志来实现原子性。

undo log

undo log是逻辑日志,在操作数据之前,会首先将数据通过记录undo log的方式存储起来,然后再对数据进行修改,当修改时系统出现异常或者用户执行了回滚,则可以通过undo log来把数据恢复到之前的状态。

undo log记录的就是相反的日志,比如执行delete时,记录的则是对应的insert,当执行insert时,记录则是对应的delete,当执行update时,记录则是相反的update记录。

流程

假设数据库有一条记录:age=1,现在要将age修改为2,流程如下:

1、事务开启。

2、记录undo日志,update age=1。
3、执行update,修改age=2。
4、事务提交、写入磁盘。
5、事务结束。

如果在写入磁盘前,发生异常,则可以通过undo日志进行数据回滚。

持久性的实现

MySQL持久性的实现主要是通过另外一种日志记录:redo log

redo log

redo log又叫重做日志,是InnoDB存储引擎中产生的,记录了对数据库中每个页的修改,redo log分为两部分:一部分是在内存中的缓冲日志 (redo log buffer),一部分是在磁盘上的文件日志 (redo log file),内存是会丢失的,而磁盘是永久的。

流程

1、事务开始。

2、执行update,age = 2。
3、redo日志,age = 2。
4、redo日志写入磁盘。
5、事务提交,数据写入磁盘。
6、事务结束。

实际上,在MySQL中为了提高写入的性能,数据并不是直接写入磁盘的,不然也不会存在持久性的问题,每次数据的写入实际上都是先写入一个缓冲区,然后再由操作系统定时的将数据写入磁盘,这样就避免了频繁的写磁盘的操作,提升了数据写入的效率,但是这样做也就带来了数据丢失的风险,如果数据在写入磁盘前,MySQL挂了,则会导致缓冲区的数据丢失。

所以redo log就可以解决上述的问题,每次数据提交写磁盘前,总会先写入redo log,如果缓冲区数据丢失,就可以通过redo log进行找回。

redo log数据也是先写入redo log buffer中,一般再由定时任务或者事务提交触发buffer中的数据写入磁盘,也就是redo log file中。

log buffer 和 os buffer

在这里插入图片描述

innodb_flush_log_at_trx_commit

具体buffer中的日志何时写入file中,由innodb_flush_log_at_trx_commit参数决定。

innodb_flush_log_at_trx_commit为0、1、2时分别表示如下写入方案。

在这里插入图片描述

innodb_flush_log_at_trx_commit默认为1,保证数据持久性,但性能也相对最低。

隔离性的实现

隔离性的实现主要是依靠MVCC+锁来实现。

理解隔离性实现原理之前,必须要先弄清楚事务的隔离级别。

事务的隔离级别

事务的四个隔离级别(mysql默认可重复度,oracle默认读已提交)

  • read uncommitted (读未提交)
    会产生:脏读、幻读、不可重复度
  • read commited (读已提交)
    会产生:幻读、不可重复度
  • repeatable read (可重复读)
    会产生:幻读
  • seariable (串行执行)
    不会产生任何异常

不同的隔离级别产生的现象

脏读

一个事务会读到另一个未提交的事务数据。

不可重复读

一个事务内多次读取到的数据不一致,A事务第一次读到值是1, B事务把1修改成2,并且提交了,A事务第二次读到值也变成了2,针对update。

幻读

一个事务内多次读取到的数据不一致,第一次读到1条,第二次读到2条。针对insert,delete,数据行数发生了变化。

下面通过案例演示,帮助理解脏读、幻读、不可重复度具体产生的问题现象。

脏读问题测试

1、设置事务隔离级别

A:set session transaction isolation level read uncommitted;A:start transaction;A:insert into tran_test values(4,'zhaoliu');A:select * from tran_test; --可以查询到最新插入的数据B:set session transaction isolation level read uncommitted;B:select * from tran_test; -- 也可以查询A最新插入的数据,尽管A的此时的事务还未提交

在这里插入图片描述

不可重复读问题测试

重复脏读的流程发现,新建的会话窗口是读不到其他会话中未提交的事务的。

在这里插入图片描述

但是会产生不可重复读问题

A:set session transaction isolation level read committed;A:start transaction;A:select * from tran_test where id = 1; --结果为zhangsanB:start transaction;B:update tran_test set name = 'zs' where id = 1; --把id为1的name修改为zsA:select * from tran_test where id = 1; -- B未提交所以A此时查询结果还是zhangsanB:commit;A:select * from tran_test where id = 1; -- B事务提交后,A再查询此时结果已经变成了zs

A再同一个事务中,两次查询结果不一致。

幻读问题测试

重复上限测试,不可重复读和脏读问题都不存在。

A:set session transaction isolation level read committed;A:start transaction;A:select * from tran_test where id = 4; --结果为空B:start transaction;B:insert into tran_test values(4,'zhaoliu'); -- B插入一条id=4的数据A:select * from tran_test where id = 4; --结果依旧为空B:commit; -- B提交事务A:select * from tran_test where id = 4; --结果依旧为空A:insert into tran_test values(4,'zhaoliu'); -- 插入失败,报主键冲突
insert into tran_test values(4,'zhaoliu')> 1062 - Duplicate entry '4' for key 'PRIMARY'> 时间: 0s

A事务在查询时数据明明不存在,但插入却报主键冲突,就像出现幻觉一样。

什么是MVCC?

MVCC(多版本并发控制),实现并发访问的一种方式,在mysql的innodb引擎读已提交和可重复读两种隔离级别下,事务在select时实际上读取的是版本链中的数据。

什么是版本链?

我们先来理解一下版本链的概念。在InnoDB引擎表中,它的聚簇索引记录中有两个必要的隐藏列:

trx_id

这个id用来存储的每次对某条聚簇索引记录进行修改的时候的事务id。

roll_pointer

每次对哪条聚簇索引记录有修改的时候,都会把老版本写入undo日志中。这个roll_pointer就是存了一个指针,它指向这条聚簇索引记录的上一个版本的位置,通过它来获得上一个版本的记录信息。(注意插入操作的undo日志没有这个属性,因为它没有老版本)

比如现在有一个trx_id = 1,那么当修改时,则会在undo日志中新增一条记录,trx_id = 2,roll_pointer = (trx_id = 1的地址)

ReadView

ReadView主要是用来处理读已提交和可重复读两种场景。

ReadView通过一个链表来记录当前已开启但还未提交的trx_id(当前活动事务)。

举例说明,在读已提交的场景下:

当前trx_id = 3,修改age=2,此时事务还未提交,版本链如下:

在这里插入图片描述

当前ReadView记录了trx_id = 3的事务,此时另一个事务执行select查询语句,于是从版本链中寻找,第一条trx_id = 3,已经在ReadView中,所以不能访问,于是找下一条,trx_id = 2,小于ReadView记录中的最小值,所以可以访问,于是返回age = 2的记录。

现在假设刚才trx_id = 3提交了,然后又新建了一个事务,修改age = 4,trx_id = 4,并且还未提交,版本链如下:

在这里插入图片描述
现在关键部分来了,看看读已提交和可重复读两种不同隔离级别的做法。

读已提交:

当另一个事务再次select时,会重新生成一次ReadView,由于当前活动事务id为4,所以ReadView中记录的trx_id = 4,然后再根据之前的方式,最终会查询到age = 3的记录。

可重复读:

当另一个事务再次select时,并不会重新生成ReadView,而是继续使用上一次的ReadView,那么ReadView中记录的当前活动事务id就还是为3,那么最终还是查询到age = 2的记录。

所以说在读已提交的隔离级别下,每次select时都会重新生成一个ReadView,而在可重复读时,只会在第一次select时生成ReadView,之后事务中的每次select,都只会复用第一次生成的ReadView。

一致性的实现

对于一致性的问题,最开始也已经解释了,它是一个比较模糊的概念,原子性、持久性、隔离性都是为了保证一致性的实现,或者说一致性更侧重于业务层面由开发人员的控制。

转载地址:http://fmlrb.baihongyu.com/

你可能感兴趣的文章
克罗谈投资策略01_期货交易中的墨菲法则
查看>>
克罗谈投资策略02_赢家和输家
查看>>
克罗谈投资策略03_你所期望的赌博方式
查看>>
克罗谈投资策略04_感觉与现实
查看>>
通向财务自由之路01_导读
查看>>
通向财务自由之路02_成功的决定因素:你
查看>>
中低频量化交易策略研发01_引言
查看>>
中低频量化交易策略研发06_推进的择时策略
查看>>
史丹·温斯坦称傲牛熊市的秘密
查看>>
期货市场技术分析01_理论基础
查看>>
期货市场技术分析02_趋势的基本概念
查看>>
期货市场技术分析03_主要反转形态
查看>>
期货市场技术分析04_持续形态
查看>>
期货市场技术分析05_交易量和持仓兴趣
查看>>
TB交易开拓者入门教程
查看>>
TB创建公式应用dll失败 请检查用户权限,终极解决方案
查看>>
python绘制k线图(蜡烛图)报错 No module named 'matplotlib.finance
查看>>
talib均线大全
查看>>
期货市场技术分析06_长期图表和商品指数
查看>>
期货市场技术分析07_摆动指数和相反意见理论
查看>>