MySQL的事务,原子性、一致性、持久性

提到MySQL的事务,相信大家都能说上几句,无论是求职面试还是日常开发,MySQL事务都与我们息息相关。
而事务的ACID(原子性Atomicity、一致性Consitency、隔离性Isolation、持久性Durability),可以说涵盖了大部分知识点。我们不仅要知道ACID是什么,还要了解ACID背后的实现。

基本概念

  • 原子性
    整个事务是不可分割的最小单位,事务中任何一个语句执行失败,所有已经执行成功的语句也要回滚,整个数据库状态恢复到执行事务前的状态。
  • 一致性
    事务将数据库从一种状态转变成另一种状态。在事务前后,数据库的完整性约束性没有被破坏。(事务的acid并不是完全正交的,尤其一致性,与原子性和隔离性都有一定关系)。
  • 持久性
    事务一旦提交,那么就是永久性的,不会因为宕机等故障而导致数据丢失。持久性保证了数据库的高可靠性(High Reliability),而不是高可用性。

持久性实现

MySQL的innoDB存储引擎,使用Redo log保证事务持久性。
当事务提交时,必须将事务所有的日志写入日志文件进行持久化,就是WAL(write ahead log)机制。保证断电或者宕机等情况发生后,已提交的事务不会丢失,称之为crash-safe。
redo log机制:
Redo log包括两部分,重做日志缓冲(redo log buffer)和重做日志文件(Redo log file),前者是易失的缓存,后者是持久化文件。
举一个例子:

  • 步骤1:begin
  • 步骤2:insert into t1 ...
  • 步骤3:insert into t2...
  • 步骤4:commit
    这个事务的写入过程:
    微信图片_20200418003042.png

    关注点:这个事务提交前,将redo log写入拆成两个步骤,prepare和commit,这就是"两阶段提交"。
    为什么采用两阶段提交?
    实际上,两阶段提交是分布式系统常用的机制。MySQL使用了两阶段提交后,也是为了保证事务的持久性。Redo log 和bin log 有一个共同的数据字段,叫 XID,崩溃恢复的时候,会按顺序扫描 redo log。
  • 假设在写入binlog前系统崩溃,那么数据库恢复后顺序扫描 redo log,碰到只有 parepare、而没有 commit 的 redo log,就拿着 XID 去 binlog 找对应的事务,而且binlog也没写入,所以事务就直接回滚了
  • 假设在写入binlog之后,事务提交前数据库崩溃,那么数据库恢复后顺序扫描 redo log,碰到既有 prepare、又有 commit 的 redo log,就直接提交,保证数据不丢失
    这个事务要往两个表中插入记录,插入数据的过程中,生成的日志都得先写入redo log buffer ,等到commit的时候,才真正把日志写到 redo log 文件。(当然,这里不绝对,因为redo log buffer可能因为其他原因被迫刷新到redo log)。
    为了确保每次日志都能写入日志文件,在每次将重做日志缓冲 写入 重做日志文件 后,InnoDB存储引擎都需要调用一次fsync操作,确保写入了磁盘。对于redo log的持久化,可以如下图所示:


    微信图片_20200418004638.png

    1)先写入redo log buffer,在蓝色区域。
    2)写入redo log file,但是还没有fsync,这时候是处于黄色的位置,处于系统缓存。
    3)调用fsync,真正写入磁盘。
    为了控制redo log的写入,InnoDB提供了innodb_flush_log_at_trx_commit参数:

  • 设置为0时,便是提交事务时只是把redo log留在redo log buffer中;
  • 设置为1时,表示每次事务提交时都将redo log直接持久化到磁盘;
  • 设置为2时,表示每次事务提交时都只是把redo log写到page cache;
    binlog的写入和redo log一样,也包括bingo cache和bingo file,同样跟上面的三色层次类似(当然,binlog是server层的,不是存储引擎层的),包括log buffer、文件系统page cache、hard disk。
    写入page cache 和 fsync到disk 的时机,是由参数 sync_binlog 控制的:
  • sync_binlog=0 的时候,表示每次提交事务都只 写入文件系统的page cache,不 fsync;
  • sync_binlog=1 的时候,表示每次提交事务都会执行 fsync;
  • sync_binlog=N(N>1) 的时候,表示每次提交事务都写入文件系统的page cache,但累积 N 个事务后才 fsync。(如果主机发生异常重启,会丢失最近 N 个事务的 binlog 日志)。
    通常我们说 MySQL 的“双 1”配置,指的就是 sync_binlog 和 innodb_flush_log_at_trx_commit 都设置成 1。也就是说,一个事务完整提交前,需要等待两次刷盘,一次是 redo log(prepare 阶段),一次是 binlog。
    特别需要区分的是,redo log和binlog的不同。这也是经常在面试中可能会问到的两种日志的差异。
    几点不同:
  • 产生位置不同:
    redo log是innodb的存储引擎产生的,而binlog是数据库的server层实现的。换句话说,如果你使用MySQL,换其他存储引擎,那么可能没有redo log,但是还是会有binlog。
  • 日志记录的内容形式不同
    binlog是一种逻辑日志,记录对应的SQL语句,而redo log记录了物理日志,是针对每个数据页的修改。
  • 日志写入时间不同
    binlog只有在事务提交后完成一次写入,对于一个事物而言,在binlog中只有一条记录。而redo log在事务进行中不断被写入,并发写入,不是顺序写入。
    *保存方式不同
    redo log是循环写的,空间固定会用完;binlog是可以追加写入的。“追加写”是指binlog文件写到一定大小后会切换到下一个,不会覆盖以前的日志。

原子的实现

undo log保证事务的原子性。
在对数据库进行修改时,innoDB引擎除了会产生redo log,还会产生undo log。
InnoDB实现回滚,靠的是undo log:当事务对数据库进行修改时,InnoDB会生成对应的undo log;如果事务执行失败导致事务需要回滚,就利用undo log中的信息将数据回滚到修改之前的样子。
有人认为undo log是redo log的逆过程,其实是不对的。两个日志文件其实都能看作是一种对数据的恢复操作,redo log恢复事务导致的数据页的修改,而undo log能够恢复数据记录到某个特定的版本。
所以redo log是一种物理日志(数据页的修改),而undo log是一种逻辑日志(数据记录)。
undo log还要另外一个重要作用,就是用于mvcc中,进行多版本控制,也就是实现事务隔离性的基础,当用户读取一行记录时,如果这个记录已接被其他事务占用,那么当前事务就可以通过undo读取之前的行版本信息,用来实现非锁定读取,就是“快照读”。

一致性的实现

事务的ACID性质不是完全正交的,尤其是一致性,我们可以认为原子性、持久性和隔离性都是为了实现事务的一致性。当然,这里的一致性是指数据库层面的事务一致性。

你可能感兴趣的:(MySQL的事务,原子性、一致性、持久性)