引言

ACID是事务的特点也是必须的要求,只有保证ACID事务的执行才不会出错,分别是原子性、一致性、隔离性和持久性。我们知道典型的MySQL事务是这样执行的:

  • start transaction 开启事务
  • commit 提交事务
  • rollback 回滚事务

注意两个默认机制:

  • 如果没有显示开启事务,每条SQL都是单独的事务
  • 自动提交机制

下面我们就来分析一下ACID是如何实现的?以及它和锁机制、隔离级别的关系。

实现原理

1、原子性(Atomicity)

原子性就是说事务是一个不可分割的基本单位,其中的操作要么全部执行,要么都不执行,其实就是rollback的实现机制,原子性实现的原理是通过undo log。

undo log是逻辑日志,它记录的是每条sql。当事务对数据库进行修改时,InnoDB 会生成对应的 undo log,如果事务执行失败调用了rollback,便可以利用 undo log 中的信息将数据回滚到修改之前的样子。

对于每个 insert,回滚时会执行 delete。

对于每个 delete,回滚时会执行 insert。

对于每个 update,回滚时会执行一个相反的 update,把数据改回去。

2、持久性(Durability)

持久性是指事务一旦提交,它对数据库的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。重点就是如何保证数据库宕机数据不受影响。实现原理是通过redo log

要想深入了解redolog,需要事先了解MySQL的存储引擎是怎么从磁盘读取数据,又是如何把数据刷回磁盘的?这里以InnoDB为例,由于磁盘IO速度很慢,因此InnoDB不直接与磁盘打交道,而是通过Buffer Pool缓冲池,以此来加速读和加速写。

加速读指的是读取时会优先从Buffer Pool读取,如果 Buffer Pool 中没有,则从磁盘读取后放入 Buffer Pool。

加读写指的是当向数据库写入数据时,会首先写入 Buffer Pool,Buffer Pool 中修改的数据会定期刷新到磁盘中,这一过程称为刷脏

但是如果buffer中保存的数据还没刷新到磁盘数据库就宕机了,会造成数据永久丢失。于是引入redo log解决这个问题,原理是WAL,先写log,再写buffer,并且每次事务提交都会把redo log刷新到磁盘。这个时候如果数据库宕机,也可以通过redo log的记录恢复所有数据。

你可能会有的两个疑问:

  1. 为什么buffer pool中的数据要定期刷脏,如果每次事务提交都刷新到磁盘,就不需要redo log

    因为每次修改的数据随机,buffer pool刷脏过程是随机IO,速度很慢,而redo log是追加操作,数据都是连续的,属于顺序IO。第二个原因是刷脏都是以数据页为单位的,一个小修改都要整页写入,而redo log中只包含真正修改的部分,无效IO减少。

  2. redo log 和 bin log的关系,bin log是否与持久性有关?

    完全无关,两者的层次和维度都不相同。bin log是server端的,用于备份和恢复数据、主从复制等,而redo log是innodb特有的,用于保证异常情况下数据安全。当然redo log的二阶段提交也是必要的,用来保证redo log和bin log的一致性。

3、隔离性(Isolation)

1. 引言

这是个重头戏,涉及到很多方面的内容

隔离性指的是事物内部的操作与其他事务是隔离的,并发执行的事务之间不能互相干扰。不同的隔离级别事务并发程度也不相同,能解决的问题也不同。一般来说,隔离级别越高并发程度越低,因为要加不同的锁。

MySQL隔离级别 -- 可能产生的问题:

  • 读未提交 -- 脏读、不可重复读、幻读
  • 读已提交 -- 不可重复读、幻读
  • 可重复读 -- 幻读
  • 串行化 -- 无

先对各个级别加锁情况做个介绍,让你有个基本概念:

  1. 读未提交级别:不需要加任何锁,因此它的并发程度最高,但同时也会引发各种并发问题
  2. 读已提交级别:读不需要加锁,但是写需要加排它锁/MVCC
  3. 可重复读级别:有两种不同的实现方式,一是悲观锁即读加共享锁,写加排它锁,这种方式并发程度低;二是乐观锁即MVCC,它的优势是不加锁,使用undo log和视图的概念实现,并发程度高
  4. 串行化:读加共享锁,写加排他锁,读写互斥

可以看到,随着隔离级别的提高,假的锁也更多,并发程度自然更低。实际应用时要根据业务需求,选择最合适的隔离级别。

其实隔离性本质上就是解决两个问题:

  • (一个事务)写操作对(另一个事务)写操作的影响:只能通过锁机制(当前读,读取最新数据)

  • (一个事务)写操作对(另一个事务)读操作的影响:目标就是不通过加锁也能解决,目前最优解是MVCC(快照读,不需要最新的数据)

2. 锁的分类

接下来简单介绍一下数据库的锁,可以从两个维度进行分析:

一、从锁范围分,可以把锁分为:全局锁、表级锁、行级锁,锁的精度逐渐增加,锁精度越高,需要同时锁住的数据越少,并发程度越高

二、从锁的作用分,可以把锁分为:共享锁(其他锁只能读不能写)、排他锁(其他锁不能读也不能写),很明显,共享锁的并发程度更高

3. MVCC的实现原理

主要依靠数据的隐藏列(也可以称之为标记位)和 undo log。其中数据的隐藏列包括了该行数据的版本号、删除时间、指向 undo log 的指针等等。当读取数据时,MySQL 可以通过隐藏列判断是否需要回滚并找到回滚需要的 undo log,从而实现 MVCC。

在InnoDB中,会在每行数据后添加两个额外的隐藏的值来实现MVCC,这两个值一个记录这行数据何时被创建,另外一个记录这行数据何时过期(或者被删除)。 在实际操作中,存储的并不是时间,而是事务的版本号,每开启一个新事务,事务的版本号就会递增。 在可重读Repeatable reads事务隔离级别下:

  • SELECT时,读取创建版本号<=当前事务版本号,删除版本号为空或>当前事务版本号。

    • 1、InnoDB 只查找版本早于当前事务版本的数据行(也就是数据行的版本必须小于等于事务的版本),这确保当前事务读取的行都是事务之前已经存在的,或者是由当前事务创建或修改的行
    • 2、行的删除操作的版本一定是未定义的或者大于当前事务的版本号,确定了当前事务开始之前,行没有被删除

    符合了以上两点则返回查询结果。

  • INSERT时,保存当前事务版本号为行的创建版本号

    • InnoDB 为每个新增行记录当前系统版本号作为创建 ID。
  • DELETE时,保存当前事务版本号为行的删除版本号

    • InnoDB 为每个删除行的记录当前系统版本号作为行的删除 ID。
  • UPDATE时,插入一条新纪录,保存当前事务版本号为行创建版本号,同时保存当前事务版本号到原来删除的行

这里简单做下总结:

  • insert 操作时 “创建时间”=DB_ROW_ID,这时,“删除时间 ”是未定义的;

  • update 时,复制新增行的“创建时间”=DB_ROW_ID,删除时间未定义,旧数据行“创建时间”不变,删除时间=该事务的 DB_ROW_ID;

  • delete 操作,相应数据行的“创建时间”不变,删除时间=该事务的 DB_ROW_ID;

  • select 操作对两者都不修改,只读相应的数据

4. 可重复读的实现

快照读(MVCC)

当你执行 begin 开启事务之后,MySQL 会拍下像下图这样的快照:

  • 当读取的记录的事务版本号小于当前事务版本号,并且不再活跃事务中,说明修改该记录的事务已经被提交,此记录可读
  • 当读取的记录的事务版本号大号当前事务版本号,说明修改该记录的事务已经未提交,此记录不可读,通过undo log往前找,直到找到第一个 trx_id 等于或者小于自己事务 ID 的记录为止

当前读(间隙锁)

与其他数据库,MySQL数据库的可重复读可以解决幻读问题,原理就通过间隙锁,为某行记录添加行锁时同时为附近的记录也添加行锁,虽然这种实现方式很多时候会锁住不需要锁的区间。如下所示:

5. 读已提交的实现

得益于MVCC,读已提交的隔离级别也可以通过undo log+视图的机制实现,避免频繁加锁

具体的实现方式是每执行一个SQL都要重新创建视图,根据视图各变量和记录事务ID判断此记录可不可读

为什么要每条SQL都要重复创建视图呢?因此读已提交隔离级别下可以读到其他事务已提交的事务,所以每条SQL执行前都要更新视图中的活跃事务ID。

4、一致性(Consistency)

致性是指事务执行结束后,数据库的完整性约束没有被破坏,事务执行的前后都是合法的数据状态。数据库的完整性约束包括但不限于:实体完整性(如行的主键存在且唯一)、列完整性(如字段的类型、大小、长度要符合要求)、外键约束、用户自定义完整性(如转账前后,两个账户余额的和应该不变)

可以说,一致性是事务追求的最终目标:前面提到的原子性、持久性和隔离性,都是为了保证数据库状态的一致性。此外,除了数据库层面的保障,一致性的实现也需要应用层面进行保障。

总结

本文从事务的四大特性出发,结合日志机制、锁机制以及隔离级别,简单梳理了事务四大特性ACID的实现原理以及它们之间的关系,其中最重要的是隔离性的实现,保护经典乐观锁MVCC以及视图机制,希望能对你理解MySQL事务有一点帮助。

作者实力有限,若有错误之处,欢迎留言指出。最后祝大家中秋快乐!

参考

ACID的实现原理的更多相关文章

  1. Java进阶之路——从初级程序员到架构师,从小工到专家

    原创文章 怎样学习才能从一名Java初级程序员成长为一名合格的架构师,或者说一名合格的架构师应该有怎样的技术知识体系,这是不仅一个刚刚踏入职场的初级程序员也是工作三五年之后开始迷茫的老程序员经常会问到 ...

  2. Java进阶之路

    Java进阶之路——从初级程序员到架构师,从小工到专家. 怎样学习才能从一名Java初级程序员成长为一名合格的架构师,或者说一名合格的架构师应该有怎样的技术知识体系,这是不仅一个刚刚踏入职场的初级程序 ...

  3. 问题集录--从初级java程序员到架构师,从小工到专家

    怎样学习才能从一名Java初级程序员成长为一名合格的架构师,或者说一名合格的架构师应该有怎样的技术知识体系,这是不仅一个刚刚踏入职场的初级程序员也是工作三五年之后开始迷茫的老程序员经常会问到的问题.希 ...

  4. java大神进阶之路

    既然励志在java路上走的更远,那就必须了解java的路径.先看图 更加细化的细节如下 一: 编程基础 不管是C还是C++,不管是Java还是PHP,想成为一名合格的程序员,基本的数据结构和算法基础还 ...

  5. Java成长之路

    怎样学习才能从一名Java初级程序员成长为一名合格的架构师,或者说一名合格的架构师应该有怎样的技术知识体系,这是不仅一个刚刚踏入职场的初级程序员也是工作三五年之后开始迷茫的老程序员经常会问到的问题.希 ...

  6. Java进阶的道路,怎么成为大牛?

    已然励志在java路上走的更远,那就有必要了解java的途径.先看图 image.png 愈加细化的细节如下​ 一: 编程基础 不管是C仍是C++,不管是Java仍是PHP,想成为一名合格的程序员,根 ...

  7. 成神之Java之路

    既然励志在java路上走的更远,那就必须了解java的路径.先看图 image.png 更加细化的细节如下 一: 编程基础 不管是C还是C++,不管是Java还是PHP,想成为一名合格的程序员,基本的 ...

  8. mysql-事务总结

    目录 事务基本概念 事务的定义 使用事务 自动提交 特殊操作 ACID特性及其原理 原子性(A) 持久性 (D) 隔离性 脏读.不可重复读和幻读 事务隔离级别 mysql事务日志 redo log 定 ...

  9. java学习要想精炼掌握应运的必备知识(博文来源于网络)

    一: 编程基础 不管是C还是C++,不管是Java还是PHP,想成为一名合格的程序员,基本的数据结构和算法基础还是要有的.下面几篇文章从思想到实现,为你梳理出常用的数据结构和经典算法. 1-1 常用数 ...

随机推荐

  1. 特殊回文数 BASIC-9

    特殊回文数 代码 import java.util.Scanner; /*123321是一个非常特殊的数,它从左边读和从右边读是一样的. 输入一个正整数n, 编程求所有这样的五位和六位十进制数, 满足 ...

  2. Vue 实现微信提示浏览器转跳功能

    <template> <div class="main"> <div :class="show==true ? 'block':'block ...

  3. Linux服务器下JVM堆栈信息dump及问题排查

    #dump 方法栈信息 jstack $pid > /home/$pid/jstack.txt #dump jvm内存使用情况 jmap -heap $pid > /home/$pid/j ...

  4. vivo商城计价中心 - 从容应对复杂场景价格计算

    一.背景 随着vivo商城的业务架构不断升级,整个商城较为复杂多变的营销玩法被拆分到独立的促销系统中. 拆分后的促销系统初期只是负责了营销活动玩法的维护,促销中最为重要的计价业务仍然遗留在商城主站业务 ...

  5. Android模块化开发实践

    一.前言 随着业务的快速发展,现在的互联网App越来越大,为了提高团队开发效率,模块化开发已经成为主流的开发模式.正好最近完成了vivo官网App业务模块化改造的工作,所以本文就对模块化开发模式进行一 ...

  6. 浅谈模拟彩票代码,html,javascript

    今天简单介绍一下用html,javascript来模拟双色球彩票选择器. 双色球彩票规则:由6个红球和1个蓝球组成,其中6个红球是从1-33中随机选出的不重复的6个数,从小到大一次排列:蓝球是1-16 ...

  7. 带有附件及图片正文的JavaMail邮件发送

    1 package javamail; 2 3 import java.io.UnsupportedEncodingException; 4 import java.util.Properties; ...

  8. JDBC中级篇(MYSQL)——处理文件(BLOB)

    注意:其中的JdbcUtil是我自定义的连接工具类:代码例子链接: package b_blob_clob; import java.io.FileInputStream; import java.i ...

  9. 如何让BootStrap栅格之间留出空白间隙呢?

    BootStrap栅格之间留出空隙 BootStrap栅格系统可以把我们的container容器划分为若干等分,如果想要每个部分之间留出一定的空隙,我们很可能首先想到的方法就是用margin外边距来使 ...

  10. docker安装与配置redis详细过程

    注:大鸟飞过,这只是简单搭建,能快速运用而已!! 第一步 pull redis 命令:docker pull redis 第二步 创建redis管理目录,方便后期管理 命令: mkdir /data/ ...