MySQL是如何实现事务的ACID
前言
最近在面试,有被问到,MySQL的InnoDB引擎是如何实现事务的,又或者说是如何实现ACID这几个特性的,当时没有答好,所以自己总结出来,记录一下。
事务的四大特性ACID
事务的四大特性ACID分别是,A-原子性(Atomicity),C-一致性(Consistency),I-隔离性(Isolation),D-持久性(Durability)。一致性是最终目的,原子性、隔离性、持久性是为了保证一致性所做的措施。所以我写的顺序并不是按照ACID来写的,将一致性放到了最后,顺序就变成了,ADIC。
原子性(A)
原子性是指一个事务就是一个不可分割的工作单位,要么全部都执行成功,要么全部都执行失败,没有中间状态或是只执行一部分。
MySQL的InnoDB引擎是靠undo log(回滚日志)来实现的,undo log能够保证在事务回滚时,能够撤销所有已经执行成功的SQL。
undo log 属于逻辑日志,它记录的是SQL执行相关的信息。当事务对数据库进行修改时,InnoDB会生成与之对应的undo log。如果事务执行失败或者调用的rollback,导致事务需要回滚,InnoDB引擎会根据undo log中的记录,将数据回滚到之前的样子。
例如在执行insert语句时会生成相关的delete语句的undo log。反之执行delete语句也会生成相关的insert语句的undo log。执行update语句时也是如此,不过update语句在执行undo log回滚时有可能会涉及到MVCC。主要是为了保证在执行undo log的时候的select能看到哪个版本的数据。
持久性(D)
持久性是指事务一旦提交,对数据库的操作就是永久性的,接下来的其他操作和异常故障不应该对它有任何影响。
我们都知道MySQL的数据最终是存放在磁盘中的,所以才会有磁盘的容量大小决定数据容量的大小。但是如果对MySQL的操作都是通过读写磁盘来进行的话,那么光是磁盘的I/O就够把效率大大的拉低了。
所以InnoDB为MySQL提供了缓冲池(Buffer Pool),Buffer Pool中包含了磁盘中部分数据页的映射。
当从数据库读取数据时,会先从Buffer Pool中读取数据,如果Buffer Pool中没有,则从磁盘读取后放入到Buffer Pool中。
当向数据库写入数据时,会先写入到Buffer Pool中,Buffer Pool中更新的数据会定期刷新到磁盘中(此过程称为刷脏)。
虽然Buffer Pool为MySQL的读写提高了效率,但是却也带来了新的问题,那就是如果数据刚更新到Buffer Pool中还没来得及刷新到磁盘中时,MySQL突然宕机了,这就会导致数据丢失,造成事务的持久性无法保证了。
为了解决这个缓存的一致性问题,redo log就出现了。在对Buffer Pool中的数据进行修改的时候通过redo log记录这次操作,当事务提交时会通过fsync接口对redo log进行刷盘。
redo log是记录在磁盘中的,所以当MySQL出现宕机时,可以从磁盘中读取redo log进行数据的恢复,从而保证了事务的持久性。
redo log 采用的预写的方式记录日志,即先记录日志,再更新Buffer Pool,这样就强行的保证了,数据只要保存在了redo log中就一定会存储到磁盘中了。
这要解释一下,redo log 也是写磁盘,刷脏也是写磁盘,为啥要先记录redo log而不是直接刷脏?
主要原因就是redo log比刷脏快很多。
第一点是,redo log是追加操作日志,是顺序IO;而刷脏是随机IO,因为每次更新的数据不一定是挨着的,也就是随机的。
第二点是,刷脏是以数据页(Page)为单位的,MySQL默认页大小是16KB,对一个页上的修改,都要整个页都刷到磁盘中;而redo log只包含真正的需要写入磁盘的操作日志。
MySQL还有一个记录操作的日志,叫binlog ,那么redo log和binlog又有什么区别呢?
- 第一点作用上的区别:
redo log是用来记录更新缓存的,为了保证MySQL就算宕机也不会影响事务的持久性;binlog是用来记录什么时间操作了什么,主要有时间点,可以保证将数据恢复到某个时间点,也有用于主从同步数据的。 - 第二点层次上的区别:
redo log是存储引擎InnoDB实现的(MyISAM就没有redo log),而binlog是在MySQL服务器层面存在的任何其他存储引擎也有binlog。
存储内容上,redo log是物理日志,基于磁盘的数据页,binlog是逻辑日志,存储的一条执行SQL。 - 第三点写入时机的区别:
redo log 在默认情况下是在事务提交时,进行刷盘的;可以通过参数:innodb_flush_log_at_trx_commit
来改变策略,可以不用等到事务提交时才进行刷盘。
如:可以设置成每秒提交一次。
binlog是在事务提交时写入。
隔离性(I)
原子性和持久性都是基于单个事务内部的措施,而隔离性是只多个事务之间相互隔离,互不影响的特性。
我们都知道事务的隔离级别中最严谨的是串行化(Serializable),但是隔离性越高,性能就越低,所以一般不使用串行化这个隔离级别。
对于隔离性的,我们要分两种情况进行讨论:
- 一个事务中的写操作对另一个事务中的写操作的影响;
- 一个事务中的写操作对另一个事务中的读操作的影响;
首先,事务间的写操作其实是靠MySQL的锁机制来实现隔离的,而事务间的写和读操作是靠MVCC机制来实现的。
锁机制
MySQL中的锁主要有
按照功能分:读锁和写锁;按照作用范围分:表级锁和行级锁;
还有意向锁,间隙锁等。
读锁:又称“共享锁”,是指多个事务可以共享一把锁,都只能访问数据,并不能修改。
写锁:又称“排他锁”,是不能和其他事务共享数据的,如果一个事务获取到了一个数据的排他锁,那么其他事务就不能再获取该行的其他锁,包括共享锁和排他锁。
表级锁:是指会将整个表进行锁定,性能较差,不同存储引擎支持的锁的粒度不同,MyISAM引擎支持表级锁,InnoDB引擎支持表级锁也支持行级锁。
行级锁:会将需要操作的相应行进行锁定,性能好。
意向锁:意向锁是表级锁,如果在一个事务已经对一个表中的某个数据加上了排他锁或共享锁,那么就可以加上意向锁,这样当下一个事务来进行锁表的时候发现已经存在意向锁了,就会先被阻塞,如果不加意向锁的话,第二个事务来锁表的时候需要一行一行的遍历查看是否有数据已经被锁住了。
间隙锁:间隙锁是为了防止产生幻读而加的锁,加在不存在的空闲空间,可以是两个索引记录之间,也可能是第一个索引记录之前或最后一个索引之后的空间(但是并不包含当前记录)。这样就保证了在间隙锁执行的时候,新增的数据会阻塞,保证了一个事务中的两次查询获得的记录数都是一致的。
Next-Key Lock:Next-Key Lock是行级锁和间隙锁的结合产生的锁,因为间隙锁是不会锁住当前记录的而Next-Key Lock是会将当前记录也锁住的。
例如:如果一个表中有三条数据分别是:
id | name | number |
---|---|---|
1 | 小明 | 16 |
2 | 小红 | 17 |
3 | 小张 | 20 |
4 | 小王 | 20 |
那么在执行SQL:select * from table where number = 17 for update 时间隙锁会锁住,number的区间是(16,17),(17,20),但是Next-Key Lock的锁住的是:
16,17),(17,20)区间加间隙锁,同时number=17加记录锁。
锁机制保障了多个事务间的写操作的隔离,而多个事务间的读和写操作的保证是需要通过MVCC机制来保证的。
MVCC机制
MVCC全称是【Multi-Version ConCurrency Control】即多版本控制协议。
MVCC的主要是靠在每行记录上增加隐藏列和使用undo log来实现的,隐藏列主要包括,改行数据创建的版本号(递增的),删除时间,指向undo log的指针等。
那么MVCC是如何保证读写隔离的呢?主要是通过快照读和当前读两个操作。
- 快照读:
MVCC为了保证并发的效率,在进行读取数据的时候是不加锁的,在执行select的时候(不带锁的普通select),会先读取当前数据的版本号,如果在select还没返回结果时,有事务将此行数据进行了修改,那么版本号就会比执行select的时候的大,所以为了保证select读取数据的一致性,就只会读取小于或等于当前版本的数据,这个历史版本的数据就是从undo log中获取到的。 - 当前读:
当执行insert、update、delete的时候,是读取的当前最新的版本数据,并且会给当前记录加上锁,用来保证在操作的时候不会被别的事务将版本号进行修改。
像普通的select就是快照读即读取的有可能就是数据的历史版本。
insert、update、delete、select ... lock in share mode 和select ... for update 读取的就是当前读,即读取的都是数据的最新版本。
其实将隔离级别设置为Serializable也是可以实现读写隔离的,但是并发效率会比低很多,所以一般用的很少,但是MVCC是读不加锁的,只有在写的时候才会加锁,从而提高的并发的效率。
通过MVCC机制保证了多个事务间的读写隔离,从而实现了事务的隔离性。
一致性(C)
一致性是指在事务执行前后,数据的一致性,事务前后数据完整性没有破坏,并且都是合法的数据状态。
- 其中一致性的指标有:
索引的完整(唯一索引,不重复等),数据列的完成(字段类型,长度,大小符合要求),外键约束等。 - 实现一致性的措施:
保证原子性,持久性,隔离性,如果这些特性都无法保证,那么一致性就也无法保证了。从数据库层面来看,除了前面那几个特性的保证外,对字段的一致性是有保证措施的,例如整型的字符不能传入,字符串、时间等格式,字符串的长度不能超过列的限制。但是在应用层面也是需要开发者自己来保证的,
例如:从A转账给B一部分金额,那么就要保证,从A从将金额扣除多少就要去给B增加多少金额,如果只扣除A的金额,而没有增加B的金额,是无法保证一致性的。
总结
MySQL事务的ACID,一致性是最终目的。
保证一致性的措施有:
A原子性:靠undo log来保证(异常或执行失败后进行回滚)。
D持久性:靠redo log来保证(保证当MySQL宕机或停电后,可以通过redo log最终将数据保存至磁盘中)。
I隔离性:事务间的读写靠MySQL的锁机制来保证隔离,事务间的写操作靠MVCC机制(快照读、当前读)来保证隔离性。
C一致性:事务的最终目的,即需要数据库层面保证,又需要应用层面进行保证。
MySQL是如何实现事务的ACID的更多相关文章
- MySQL InnoDB存储引擎事务的ACID特性
1.前言 相信工作了一段时间的同学肯定都用过事务,也都听说过事务的4大特性ACID.ACID表示原子性.一致性.隔离性和持久性.一个很好的事务处理系统,必须具备这些标准特性: 原子性(Atomicit ...
- 深入学习MySQL事务:ACID特性的实现原理
事务是MySQL等关系型数据库区别于NoSQL的重要方面,是保证数据一致性的重要手段.本文将首先介绍MySQL事务相关的基础概念,然后介绍事务的ACID特性,并分析其实现原理. MySQL博大精深,文 ...
- MySQL事务及ACID特性
一.事物 1.定义:事务是访问和更新数据库的程序执行单元,事务中包含一条或者多条SQL语句,这些语句要么全部执行成功,要么都不执行. 在MySQL中,事务支持是在引擎层实现的,MySQL是一个支持多引 ...
- 一文说尽MySQL事务及ACID特性的实现原理
MySQL 事务基础概念 事务(Transaction)是访问和更新数据库的程序执行单元:事务中可能包含一个或多个 sql 语句,这些语句要么都执行,要么都不执行.作为一个关系型数据库,MySQL 支 ...
- mysql innodb事务的ACID及其实现的保证机制
MySQL事务的ACID,一致性是最终目的.保证一致性的措施有:A原子性:靠undo log来保证(异常或执行失败后进行回滚).D持久性:靠redo log来保证(保证当MySQL宕机或停电后,可以通 ...
- [MySQL] 事务的ACID特性
事务的ACID特性: 原子性(atomicity):一个事务是一个不可分割的最小工作单位,事务中的所有操作要么都做,要么都不做. 一致性(consistency):事务前后数据的完整性必须保持一致.事 ...
- 数据库的事务、ACID及隔离级别
事务 所谓事务是用户定义的一个数据库操作序列,这些操作要么全做,要么不做,是一个不可分割的工作单位.例如,在关系数据库中,一条或一组SQL语句.整个程序都可以是一个事务. 事务和程序是两个概念,一个程 ...
- MySQL/MariaDB中的事务和事务隔离级别
本文目录:1.事务特性2.事务分类 2.1 扁平事务 2.2 带保存点的扁平事务 2.3 链式事务 2.4 嵌套事务 2.5 分布式事务3.事务控制语句4.显式事务的次数统计5.一致性非锁定读(快照查 ...
- 事务的ACID属性,图解并发事务带来问题以及事务的隔离级别
事务的概述 事务是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行. 事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源.通过将一组相关操作组 ...
随机推荐
- 关于node-sass安装失败问题
在进行Vue开发中npm run dev报错,按照提示尝试进行npm以及cnpm安装均失败 解决办法:npm uninstall node-sass卸载模块 指定淘宝镜像源安装 npm i node- ...
- 解决android studio Gradle无法同步问题
打开根目录build.gradle buildscript { repositories { // 添加阿里云 maven 地址 maven { url 'http://maven.aliyun.co ...
- NACOS安装和配置
安装包nacos-server-1.1.4.tar.gz 环境 JDK1.8 上传及解压 [root@centos7- ~ ]# mkdir -p /cslc/nacos #通过SFTP将安装包上传至 ...
- Nexus安装与迁移
Maven registry(maven私有仓库) 配置Java export JAVA_HOME=/software/jdk1.7.0_79 export JRE_HOME=${JAVA_HOME} ...
- for循环运用,三角形
用for循环打出三角形.倒三角形.金字塔.99乘法表 三角形: 打出如图三角形,分析行数与*个数的关系,用for循环 for(var i=0;i<5;++i){//i表示行数 var str=& ...
- PHP restore_exception_handler() 函数
定义和用法 restore_exception_handler() 函数恢复之前的异常处理程序. 该函数用于在通过 set_exception_handler() 函数改变后恢复之前的异常处理程序. ...
- 牛客练习赛63 牛牛的树行棋 差分 树上博弈 sg函数
LINK:牛牛的树行棋 本来是不打算写题解的. 不过具体思考 还是有一段时间的. 看完题 一直想转换到阶梯NIM的模型上 转换失败. 考虑SG函数. 容易发现 SG函数\(sg_x=max{sg_{t ...
- 区块链钱包开发 - USDT - 一、Omni本地钱包安装
背景 Tether(USDT)中文又叫泰达币,是一种加密货币,是Tether公司推出的基于稳定价值货币美元(USD)的代币Tether USD,也是目前数字货币中最稳定的币,USDT目前发行了两种代币 ...
- 【BZOJ1471】不相交路径 题解(拓扑排序+动态规划+容斥原理)
题目描述 在有向无环图上给你两个起点和终点分别为$a,b,c,d$.问有几种路径方案使得能从$a$走到$b$的同时能从$c$走到$d$,且两个路径没有交点. $1\leq n\leq 200,1\le ...
- 【USACO02FEB】Rebuilding Roads 重建道路 题解(树形DP)
题目链接 题目大意:问使含有$p$个节点的子树分离至少需要去掉几条边. ------------------ 设$f[i][j]$表示以$i$为根的子树保留$j$个节点所去掉的最少边数. 初始化$f[ ...