ACID的实现原理
引言
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的记录恢复所有数据。
你可能会有的两个疑问:
为什么buffer pool中的数据要定期刷脏,如果每次事务提交都刷新到磁盘,就不需要redo log
因为每次修改的数据随机,buffer pool刷脏过程是随机IO,速度很慢,而redo log是追加操作,数据都是连续的,属于顺序IO。第二个原因是刷脏都是以数据页为单位的,一个小修改都要整页写入,而redo log中只包含真正修改的部分,无效IO减少。
redo log 和 bin log的关系,bin log是否与持久性有关?
完全无关,两者的层次和维度都不相同。bin log是server端的,用于备份和恢复数据、主从复制等,而redo log是innodb特有的,用于保证异常情况下数据安全。当然redo log的二阶段提交也是必要的,用来保证redo log和bin log的一致性。
3、隔离性(Isolation)
1. 引言
这是个重头戏,涉及到很多方面的内容
隔离性指的是事物内部的操作与其他事务是隔离的,并发执行的事务之间不能互相干扰。不同的隔离级别事务并发程度也不相同,能解决的问题也不同。一般来说,隔离级别越高并发程度越低,因为要加不同的锁。
MySQL隔离级别 -- 可能产生的问题:
- 读未提交 -- 脏读、不可重复读、幻读
- 读已提交 -- 不可重复读、幻读
- 可重复读 -- 幻读
- 串行化 -- 无
先对各个级别加锁情况做个介绍,让你有个基本概念:
- 读未提交级别:不需要加任何锁,因此它的并发程度最高,但同时也会引发各种并发问题
- 读已提交级别:读不需要加锁,但是写需要加排它锁/MVCC
- 可重复读级别:有两种不同的实现方式,一是悲观锁即读加共享锁,写加排它锁,这种方式并发程度低;二是乐观锁即MVCC,它的优势是不加锁,使用undo log和视图的概念实现,并发程度高
- 串行化:读加共享锁,写加排他锁,读写互斥
可以看到,随着隔离级别的提高,假的锁也更多,并发程度自然更低。实际应用时要根据业务需求,选择最合适的隔离级别。
其实隔离性本质上就是解决两个问题:
(一个事务)写操作对(另一个事务)写操作的影响:只能通过锁机制(当前读,读取最新数据)
(一个事务)写操作对(另一个事务)读操作的影响:目标就是不通过加锁也能解决,目前最优解是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的实现原理的更多相关文章
- Java进阶之路——从初级程序员到架构师,从小工到专家
原创文章 怎样学习才能从一名Java初级程序员成长为一名合格的架构师,或者说一名合格的架构师应该有怎样的技术知识体系,这是不仅一个刚刚踏入职场的初级程序员也是工作三五年之后开始迷茫的老程序员经常会问到 ...
- Java进阶之路
Java进阶之路——从初级程序员到架构师,从小工到专家. 怎样学习才能从一名Java初级程序员成长为一名合格的架构师,或者说一名合格的架构师应该有怎样的技术知识体系,这是不仅一个刚刚踏入职场的初级程序 ...
- 问题集录--从初级java程序员到架构师,从小工到专家
怎样学习才能从一名Java初级程序员成长为一名合格的架构师,或者说一名合格的架构师应该有怎样的技术知识体系,这是不仅一个刚刚踏入职场的初级程序员也是工作三五年之后开始迷茫的老程序员经常会问到的问题.希 ...
- java大神进阶之路
既然励志在java路上走的更远,那就必须了解java的路径.先看图 更加细化的细节如下 一: 编程基础 不管是C还是C++,不管是Java还是PHP,想成为一名合格的程序员,基本的数据结构和算法基础还 ...
- Java成长之路
怎样学习才能从一名Java初级程序员成长为一名合格的架构师,或者说一名合格的架构师应该有怎样的技术知识体系,这是不仅一个刚刚踏入职场的初级程序员也是工作三五年之后开始迷茫的老程序员经常会问到的问题.希 ...
- Java进阶的道路,怎么成为大牛?
已然励志在java路上走的更远,那就有必要了解java的途径.先看图 image.png 愈加细化的细节如下 一: 编程基础 不管是C仍是C++,不管是Java仍是PHP,想成为一名合格的程序员,根 ...
- 成神之Java之路
既然励志在java路上走的更远,那就必须了解java的路径.先看图 image.png 更加细化的细节如下 一: 编程基础 不管是C还是C++,不管是Java还是PHP,想成为一名合格的程序员,基本的 ...
- mysql-事务总结
目录 事务基本概念 事务的定义 使用事务 自动提交 特殊操作 ACID特性及其原理 原子性(A) 持久性 (D) 隔离性 脏读.不可重复读和幻读 事务隔离级别 mysql事务日志 redo log 定 ...
- java学习要想精炼掌握应运的必备知识(博文来源于网络)
一: 编程基础 不管是C还是C++,不管是Java还是PHP,想成为一名合格的程序员,基本的数据结构和算法基础还是要有的.下面几篇文章从思想到实现,为你梳理出常用的数据结构和经典算法. 1-1 常用数 ...
随机推荐
- 使用账号密码来操作github? NO!
目录 简介 背景介绍 创建令牌 使用令牌 缓存令牌 使用GCM 总结 简介 最近在更新github文件的时候,突然说不让更新了,让我很是困惑,原因是在2021年8月13号之后,github已经不让直接 ...
- Java ArrayList【笔记】
Java ArrayList[笔记] ArrayList ArrayList基本结构 ArrayList 整体架构比较简单,就是一个数组结构 源码中的基本概念 index 表示数组的下标,从 0 开始 ...
- vim编辑文件时[O]pen Read-Only, (E)dit anyway, (R)ecover, (D)elete it, (Q)uit, (A)bort:
出现这个问题是因为你上次编辑的时候在没有保存的情况下退出了(如:电脑关机等)也有可能是有其他人在和你同时进行编辑行为(不同会话中).这是因为在用vim编辑的时候,vim会在打开文件目录下 ...
- Java社区——个人项目开发笔记(二)
1.B\S架构通信原理 浏览器,服务器之间产生通信,浏览器访问服务器,服务器返回一个HTML,浏览器会对HTML进行解析,并渲染相关的内容. 在解析过程中,会发现HTML里引用了css文件,js文件, ...
- RabbitMQ 的使用
MiaoshaMessage 类 ---------------------------------------------------------------- import com.imooc. ...
- C#序列化和反序列化 之 dynamic 动态Json的反序列化
序列化和反序列化的常识不再赘述,如果不清楚这个,可以 参考一下其他人写的文章https://www.cnblogs.com/maitian-lf/p/3670570.html 总结的说, 序列化 是把 ...
- js之DOM入门(慕课网学习笔记)
DOM简介 获得元素 document.getElementById('') 1.通过id获得元素内容 document.getElementsByTagName('') 2.通过标签获得元素内容 d ...
- java 常用Bean 转换工具类
package com.hnf.framework.utils; import com.alibaba.fastjson.JSON; import com.fasterxml.jackson.data ...
- jwt三种方式
package library.book.demo.config.loginconfig; import com.alibaba.fastjson.JSON; import com.sun.org.a ...
- GO的GC辣鸡回收(一)
用户程序通过内存分配器(Allocator)在堆上申请内存,而垃圾收集器(Collector)负责回收堆上的内存空间,内存分配器和垃圾收集器共同管理程序中的堆内存空间. 基本概念 垃圾分类 语义垃圾: ...