在说ACID如何实现之前,应该要理解ACID,这里面AID我很容易就理解了,但是一致性一直都没明白是什么意思。
一致性意思的官话是:一致性是指事务将数据库从一种状态转变为下一种一致的状态。在事务开始前和事务结束以后,数据库的完整性约束没有被破坏(系统从一种正确的状态迁移成另外一种正确的状态)。
这段官话是在《MySQL 技术内幕:InnoDB存储引擎》里抄来的。但是我没有理解什么是一致(正确)的状态,什么是数据库的完整性约束没有破坏。在知乎上看到了回答感觉理清楚了。
其中提到的例子:
如果A有90元,向B转账100元,但余额的约束是不能小于0,所以转账失败。如果转账成功,那么数据库的约束就被破坏,那么就没有了一致性。
如果上面没有数据库约束,在业务中不允许余额少于0,支付完后去检查A的账户,发现小于0,进行事务回滚。这样虽然没有破坏数据库的约束,但是破坏了业务上的约束,但是通过事务回滚保证了业务上的一致性。
如果数据库上没约束,业务上也没约束,那么就会支付成功了。但任何约束被破坏,那么就算不合理,那一日是保存了一致性。
事务能够通过AID来保证这个C的过程,C是目的,AID都是手段。
复述的不好,还是原文清楚。
进入正题,事务的ACID如何实现
原子性
实现原子性的关键是当前事务回滚时能够撤销所有已经执行成功的SQL语句。InnoDB实现回滚靠的是 undo log,当事务对数据进行修改时,InnoDB会生成对应的 undo log。如果事务执行失败或调用了rollback,导致事务需要回滚,就可以通过利用undo log中的信息将数据回滚到修改之前的样子。
undo log属于逻辑日志,它记录的时SQL执行相关的信息。当发生回滚时,InnoDB会根据undo log的内容做与之前相反的操作。insert-delete,delete-insert,update-update。
持久性
数据是存放在磁盘中的,但每次读写数据都需要磁盘IO,效率会很低。因此,InnoDB提供了缓存(Buffer Pool),Buffer Pool中包含了磁盘中部分数据页的映射,作为访问数据库的缓冲。当从数据库读取数据时,会首先从Buffer Pool中读取,如果没有,则会去磁盘读取后放入Buffer Pool。当数据库写入数据时,会先写入Buffer Pool,Buffer Pool中修改的数据会定期刷新到磁盘中(这个过程称为刷脏)。
Buffer Pool的使用大大提高了读写数据的效率,但是也带来了新的问题:如果MySQL宕机,而此时Buffer Pool中修改的数据还没刷新到磁盘,就会导致数据的丢失,事务的持久性也无法保证。
于是,redo log被引入来解决这个问题。当数据修改时,除了修改Buffer Pool中的数据,还会再redo log记录这次操作。当事务提交时,会调用fsync接口对redo log进行刷盘。如果MySQL宕机,重启时可以读取redo log中的数据,对数据库进行恢复。redo log采用的是WAL(Write-ahead logging,预写日志),所有修改先写入日志,再更新到Buffer Pool,保证了数据不会因MySQL宕机而丢失,从而满足了持久性要求。
既然redo log也是需要在事务提交时将日志写入磁盘,为什么它比刷脏快?主要原因有
- 刷脏是随机IO,因为每次修改数据位置随机,但redo log是追加操作,属于顺序IO。
- 刷脏是以数据页(page)为单位的,MySQL默认页大小是16KB,一个Page上的一个小修改都要整页写入。而redo log中只包含了真正需要写入的部分,无效IO大大减少。
隔离性
隔离性追求的是并发情况下事务之间互补干扰。通过锁和MVCC来实现。
举个例子,一个数据在修改数据之前,要先获得相应的锁。获得锁之后就可以修改数据。在该事务操作期间,这部分的数据是锁定的,其他事务需要修改数据,需要得这个事务提交或回滚。
一致性
一致性是事务追求的最终目标。事务的AID都是为了实现C,可以说C是目的,AID是手段。除了AID这些数据库层面的保障,一致性还需要应用层面的保障。例如转账操作只扣除转账者的余额,而没增加接收者的余额,无论数据库多么完美也无法保证状态一致。
- 知乎 如何理解数据库事务中的一致性的概念?
- 《MySQL 技术内幕:InnoDB存储引擎》
- 《2022校招面试宝典 java岗》——牛客




近期评论