数据库在使用过程中,经常会问及ACID属性,何为ACID,它与事务(transaction)又有什么关联,以及mysql如何利用日志来保证acid的。

何为ACID

ACID对应如下四个英文单词:Atomicity,Consistency,Isolation,Durability,通过四个方面的约束,保证了数据库的事务。

事务:对应数据库相关英文书籍中的transaction,transaction意为交易、处理、业务。我们可以把它理解为这样一个过程:比如我们买东西,我们出钱,店家出货,只有在店家拿到钱,我们拿到货时,方才算这笔交易完成。从我们提出买货,到我们拿到货和店家收到钱,这一系列的过程,就是一个交易。对应数据中流程:执行sql时,先提出一个交易请求(这里指数据更新),执行交易请求,提交并持久化结果。这样一系列的流程,就是数据库的一个事务。

事务有以下几个特性约束:

Atomicity(原子性):指事务是一个不可分割的最小单位,一个事务中所包含的逻辑要么全执行,要么不执行。

Consistency(一致性):事务前后的数据完整性必须保持一致。

Isolation(隔离性):事务间相互独立,互不干扰

Durability(持久性):事务一旦被提交,它对数据库的改变是永久性的,即数据被物理化记录(更新到硬盘中)。

acid是什么

原子性(Atomicity)

原子,构成一般物质的最小单位,它是化学反应中不可再分的微粒,化学上不可再分割。原子性:指数据库的事务不能再进行分割,也就是说数据库需保证事务内所有流程有这样的特性:要么都执行成功,即数据成功更新;要么执行过程中发生失败时,数据回复到原始状态,即数据不发生任何改变。

事务的原子性,保证事务中流程执行完整性。

原子性

常见的银行转转账,A账户转200元到B账户,对应的数据库操作:A账户扣200元,B账户增加200元。一个事务中包含了这两个操作,转账成功意味着这笔交易成功(两个操作都执行成功),转账失败意味着交易失败(两个操作等同于未执行,即数据还是原始状态)。

若事务不是原子性的,可能会发生:A账户扣200元,B账户未发生变化,这是绝对不容许发生的。

如果说事务不具有原子性,那么数据库的行为就会出现随机的情况,这种情况在实际应用场景中是绝不容许出现的。

所以数据库设计上,事务上必须保证原子性,至于保证原子性的方式,我们后续再讨论。

隔离性(Isolation)

事务间需保持相互独立,互不干扰。

在同一个环境下,可能会有多个事务同时执行(并发执行),并发执行这些事务所达到的数据结果应等同于串行执行这些事务。这样在一定程度上就表现为事务间是各自独立执行。这要求两件事:

  • 在一个事务执行过程中,数据的中间的(可能不一致)状态不应该被暴露给所有的其他事务。
  • 两个并发的事务应该不能操作同一项数据。数据库管理系统通常使用锁来实现这个特征
一致性(Consistency)

是指事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。保证数据库一致性是指当事务完成时,必须使所有数据都具有一致的状态。在关系型数据库中,所有的规则必须应用到事务的修改上,以便维护所有数据的完整性。

比如:数据库设置的唯一规则、非空约束等,在事务执行后,数据须依然遵守这样的约束。再比如转账操作,业务上须保证余额在事务执行前后应保证一致的。可以说一致性更多的是表现业务逻辑上的约束性上。事务执行后,业务约束性应保持一致。

mysql一致性解释

原子性保证的事务内流程的整体执行,隔离性保证的事务间相互独立不干扰,按理说,这样即使是并发情况下,执行结果是一致的。那为什么我们还需要一致性呢,注意,事务的一致性指的是数据库数据约束状态在事务执行前后不发生变化,多个事务任意并发顺序执行结果的一致性仅是事务一致性其中一部分的约束,即数据完整性状态,但数据约束可能还有其他情况存在:比如余额守恒、唯一约束、某个业务约束。若事务执行后,数据的约束性发生了变化,这就不符合数据的一致性。

所以说,一致性不单单指数据结果的一致性,也指数据约束状态的一致性。

持久性(Durability)

指事务一旦提交后,其执行结果将会被记录下来,即使宕机,数据记录依然存在,该记录被持久化在硬盘上。

日志

将所有的数据更新操作都写入到日志文件中,如果一个事务中的多个操作,前面一部分成功了,后面一部分还未执行,此时服务宕机重启后,数据库服务将回溯这个日志,将已经执行的操作撤消,从而还原数据初始状态(即事务中操作全部操作失败-等价于未执行)。

mysql日志

一般数据库在宕机重启后,数据库中的数据处于不一致状态,必须执行一个crash recovery过程(故障恢复),读取日志进行REDO(重演将所有已经执行成功但尚未写入到磁盘的操作,保证持久性),再对所有到崩溃时尚未成功提交的事务进行UNDO(撤销所有执行了一部分但尚未提交的操作,保证原子性)。crash recovery结束后,数据库恢复到一致性状态,可以继续被使用。

如何保证ACID

事务ACID属性约束保证了事务的正确执行,那么数据库是通过何种手段来保证ACID的呢。

原子性保证

对于数据库事务而言,一个事务包含有多个操作的集合,它所需要保证的是这些操作结果的一致性,即要么全部成功,要么全部失败,这是数据库的原子性。

对于mysql来说,它是通过回滚日志来保证数据操作事务的原子性。

回滚日志

我们要保证原子性,首先我们需要记录下我们所做的一系列的操作,当在操作过程中,发生异常,我们只需要考虑对之前所做的操作进行还原即可恢复到操作前的数据。这样我们就需要在执行每一个操作的时候,既记录我们所做的操作,同时也要记录一笔操作,该笔操作可以对数据进行还原(使数据回到修改前的样子),这个还原的操作日志即为回滚日志。

大致的意思如下图所示:

回滚日志

利用回滚日志,我们可以根据回滚日志的记录,在发生异常时,逐条回溯执行,直到数据恢复到原始状态,这样就可以让一个事务下的操作保证执行结果上的一致性。

但要注意的是,对于回滚日志,回滚日志的记录一定是在数据从内存刷到磁盘之前,也就是WAL(Write After Log)机制。

另外回滚日志是逻辑上的日志,并不是物理上将数据直接恢复原来状态,而是通过一步步数据更新,将数据恢复到原始状态,它是一种逻辑上操作使用数据恢复的。

持久性保证

对于数据库而言,记录的是数据,这就要求物理的将数据记录下来。一旦事务提交,数据将被持久的记录在磁盘中,而且该数据是无法回滚的,只有通过反向的数据操作,将数据还原,而这一系列的行为将会被数据库记录下来。这就是数据库持久性的一种体现。

对于mysql,它的持久性也是通过日志来保证的,就是重做日志。

重做日志

更新一条数据的具体操作:

  1. 接收到更新操作指令
  2. 从mysql数据库中将待更新数据读取到内存中
  3. 更新数据,先在redo log buffer中记录日志
  4. 事务提交时,将redo log buffer刷盘
  5. 将内存中的数据刷盘,即更新物理磁盘上的数据。

WAL机制

在 InnoDB 中,重做日志都是以 512 字节的块的形式进行存储的,同时因为块的大小与磁盘扇区大小相同,所以重做日志的写入可以保证原子性,不会由于机器断电导致重做日志仅写入一半并留下脏数据。

对数据库的修改会产生重做日志,而回滚日志也是需要持久化存储的,它们也会创建对应的重做日志,在发生错误后,数据库重启时会从重做日志中找出未被更新到数据库磁盘中的日志重新执行以满足事务的持久性。

隔离性保证

首先介绍下数据库的隔离级别:

隔离级别 脏读 不可重复读 幻读
读未提交 存在 存在 存在
读已提交 解决 存在 存在
可重复读 解决 解决 存在
串行化 解决 解决 解决

上述几种隔离级别,由上到下,性能越来越低。

关于事务隔离级别的实现,可以看mysql-事务隔离级别,该文会对事务隔离级别做一个详细介绍,以及是如何实现事务隔离级别。

一致性保证
  1. 原子性保证事务执行结果的准确性,隔离性保证了事务间相互不受影响,它保证了多事务并行执行结果的准确性,而持久性保证了数据记录的准确完备性,通过这三方面保证了数据操作数据上的一致性

  2. 应用程序上也需要保证数据更新上的一致性。比如转账操作,我们需要对两个账户在一个事务上操作,而不是对一个账户操作。应用层面需要保证数据操作上业务一致性。

 上一页

 评论