前言:
说到事务的ACID,各位一定都不陌生,它们指的是原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。
-
原子性
- 根据定义,原子性是指一个事务是一个不可分割的工作单位,其中的操作要么都做,要么都不做。要么全部提交,要么全部回滚。
-
一致性
- 事务中操作的数据及状态改变是一致的,即写入资料的结果必须完全符合预设的规则,不会因为出现 系统意外等原因导致状态的不一致。
-
隔离性
- 一个事务所操作的数据在提交之前,对其他事务的可见性设定(一般设定为不可见)
- MySQL中的隔离级别有:读未提交(Read Uncommitted)、读已提交(Read committed)、可重复读(Repeatable Read)、串行化(Serializable)。
-
持久性
- 事务的持久性是指事务⼀旦提交后,数据库中的数据必须被永久的保存下来。即使服务器系统崩溃或服务器宕机等故障。只要数据库重新启动,那么⼀定能够将其恢复到事务成功结束后的状态。
MySQL中我们常用的默认引擎一般是InnoDB,接下来就来看看在InnoDB引擎中是如何实现事务的这四大特性的。
InnoDB引擎架构(MySQL5.7)
在说明InnoDB引擎是如何实现ACID之前,为了更好的理解,首先对InnoDB引擎架构中的某些部分有一些基本的认识。
-
缓冲池(Buffer Pool)
Buffer Pool中包含了磁盘中部分数据页的映射。
当从数据库读取数据时,会先从Buffer Pool中读取数据,如果Buffer Pool中没有,则从磁盘读取后放入到Buffer Pool中。
当向数据库写入数据时,会先写入到Buffer Pool中,Buffer Pool中更新的数据会定期刷新到磁盘中(此过程称为刷脏)。 -
日志缓冲区(Log Buffer)
当在MySQL中对InnoDB表进行更改时,这些更改首先存储在InnoDB日志缓冲区的内存中,然后写入通常称为重做日志(redo logs)的InnoDB日志文件中。
-
双写机制缓存(DoubleWrite Buffer)
Doublewrite Buffer是开在共享(系统)表空间的物理文件的 buffer,其大小是2MB.是一个一分为二的2MB空间。
刷脏操作开始之时,先进行脏页**‘备份’**操作.将脏页数据写入 Doublewrite Buffer.
将Doublewrite Buffer(顺序IO)写入磁盘文件中(共享表空间) 进行刷脏操作.(绝大多数是随机IO)
-
回滚日志(Undo Log)
Undo Log记录的是逻辑日志.记录的是事务过程中每条数据的变化版本和情况.
在Innodb 磁盘架构中Undo Log 默认是开在共享(系统)表空间的物理文件的Buffer.
在事务异常中断,或者主动(Rollback)回滚的过程中,Innodb基于 Undo Log进行数据撤销回滚,保证数据回归至事务开始状态.
-
重做日志(Redo Log)
Redo Log通常指的是物理日志,记录的是数据页的物理修改.并不记录行记录情况。(也就是只记录要做哪些修改,并不记录修改的完成情况)
当数据库宕机重启的时候,会将重做日志中的内容恢复到数据库中。
原子性
Innodb事务的原子性保证,包含事务的提交机制和事务的回滚机制.
- 提交机制
执行一个DML语句时,会执行如上图的操作
- 将修改后的数据存入缓冲池(Buffer Pool)中,等待刷脏。
- 在Log Buffer中写入重做日志(Redo Log),并将日志状态设置为Prepare。
- 返回MySQL服务层,记录BinLog日志。
- 将Log Buffer中的日志文件状态设为Commit,并等待日志文件存入盘中。
以上的等待刷脏、等待事务日志文件刷盘操作不是串行,而是交由后台进程并发处理
以上的事务提交过程称为XA的两阶段提交
- 回滚机制
在Innodb引擎中事务的回滚机制是依托**回滚日志(Undo Log)**进行回滚数据的保证的.
在事务异常中断,或者主动(Rollback)回滚的过程中,Innodb基于 Undo Log进行数据撤销回滚,保证数据回归至事务开始状态.
隔离性
隔离性的实现主要依赖两大技术
- LBCC(Lock Based Concurrency Control)
事务开始操作数据前,对其加锁,阻止其他事务对数据进行修改。
针对SQL的操作,使用当前读解决并发读写的隔离性问题。(当前读:读取到的数据是线程独占的、最新的数据)
- MVCC(Multi Version Concurrency Control)
事务开始操作数据前,将数据在当下时间点进行一份数据快照(Snapshot)的备份,并用这个快照来提供给其他事务进行一致性读取 。
并发访问(读或写)数据库时,对正在事务内处理的数据做多版本的管理。
避免写操作的堵塞,从而引发读操作的并发阻塞问题,使用快照读解决并发读写的隔离性问题。
是基于undo日志进行多版本信息管理。
持久性
数据库IO的最小单位是页大小为16KB
操作系统IO的最小单位是页大小为4KB
所以针对数据库中的一页脏页,需要进行4次的磁盘IO操作。在操作中若突然断电,会出现页断裂现象。
基于事务的提交机制流程有可能出现三种场景.
-
数据刷脏正常.一切正常提交
Redo Log 循环记录.数据成功落盘.持久性得以保证 -
数据刷脏的过程中出现的系统意外导致页断裂现象 (部分刷脏成功)
针对页断裂情况,采用Double write机制进行保证页断裂数据的恢复. -
数据未出现页断裂现象,也没有刷脏成功
MySQL通过Redo Log 进行数据的持久化即可
Double write机制详解
Doublewrite Buffer是开在共享(系统)表空间的物理文件的 buffer,其大小是2MB.
刷脏操作开始之时,先进行脏页**‘备份’**操作.将脏页数据写入 Doublewrite Buffer.
将Doublewrite Buffer(顺序IO)写入磁盘文件中(共享表空间) 进行刷脏操作.(绝大多数是随机IO)
Double Write机制其核心思想是: 在刷脏之前,建立脏页数据的副本.系统意外宕机造成页断裂的情况可通过脏页数据副本 (DoubleWrite Buffer)进行恢复.
一致性
从数据库层面,数据库通过原子性、隔离性、持久性来保证一致性。
也就是说ACID四大特性之中,一致性是目的,原子性、隔离性、持久性是手段,是为了保证一致性,数据库提供的手段。
数据库必须要实现AID三大特性,才有可能实现一致性。
例如,原子性无法保证,显然一致性也无法保证。
但是,如果在事务里故意写出违反制定规则的代码,一致性也还是无法保证的。(例如转账从本帐户扣了钱,没给其他账户加钱)
最后
感谢大家看到这里,文章有不足,欢迎大家指出;如果你觉得写得不错,那就给我一个赞吧。
也欢迎大家关注我的公众号:Java程序员聚集地,麦冬每天都会分享java相关技术文章或行业资讯,欢迎大家关注和转发文章!