面试官:MySQL InnoDB事务中的ACID特性是如何实现的?

数据库 MySQL
我们再看一下对于事务原子性的定义,事务是一个不可分割的最小单位,要么全部执行成功,要么全部失败回滚。在这里,事务全部执行成功依赖于 Redo Log(重做日志),而事务回滚则依赖于 Undo Log(撤销日志)。

不得不说,现在的面试还是比几年前卷了很多的。

以前的面试官大概率只会问,“说下 MySQL InnoDB 事务中的 ACID 特性各是什么”仅此而已了,根本不会涉及到什么底层实现。

嗯,那就卷起来吧,接下来我们先看看 ACID 特性的定义,然后再延展开来往底层实现上讲。

原子性(Atomicity),事务是一个不可分割的最小单位,要么全部执行成功,要么全部失败回滚。

一致性(Consistency),业务逻辑的一致性,保证事务从一个一致的业务状态转换到另一个。

比如转账功能,从你的账户里扣减10元,必须在我的账户里增加10元。

隔离性(Isolation),多个并发执行的事务是相互隔离、互不干扰的。当然,隔离是分等级的,这就是所谓的事务隔离级别。

持久性(Durability),事务提交后就会在硬盘中持久化,数据不会丢失。

原子性(Atomicity)

我们再看一下对于事务原子性的定义,事务是一个不可分割的最小单位,要么全部执行成功,要么全部失败回滚。

在这里,事务全部执行成功依赖于 Redo Log(重做日志),而事务回滚则依赖于 Undo Log(撤销日志)。

也就是说,事务的原子性是通过 Redo Log(重做日志)和Undo Log(撤销日志)实现的。

图片图片

Redo Log

当事务被提交的时候,对数据表的写操作并不是直接刷新磁盘上的数据文件上,而是先被写入到 Redo Log 中(默认情况下),再通过 Master Thread 适时刷新到磁盘上的数据文件中,其目的是为了减少磁盘频繁的随机 IO 操作。

这里在重点说下用来控制 Redo Log 的 innodb_flush_log_at_trx_commit 参数,该参数有0,1,2 三个选项:

图片图片

参数为0:事务提交时并不需要将 Redo Log 写入到磁盘中,仅仅写入到Log Buffer 中,然后通过 Master Thread 每秒钟进行一次 Redo Log 的刷盘操作。

等于1:默认值,在事务进行中不断地写入到 Redo Log Buffer 中,在事务提交时必须将事务的 Redo Log 刷新到磁盘上。

等于2:事务提交时将仅将 Redo Log 写入到 OS Buffer 中,然后操作系统每秒钟进行一次 Redo Log 的刷盘操作。

选择该选项,如果只是 MySQL 数据库挂掉了,操作系统没有问题的情况下,对应的事务数据并没有丢失。

Undo Log

在事务提交前,MySQL InnoDB 会先将用于回滚操作的 SQL 语句保存到 Undo Log 中,以便于将其恢复到事务开始前的状态,其属于逻辑日志。

举例说明:如果 MySQL InnoDB 进行 insert 操作时,其对应的 Undo Log 为 delete 语句,反之则是 delete 操作对应 Undo Log 的 insert 语句。

如果 MySQL InnoDB 进行 update 操作时,其对应的 Undo Log 为反向 update 语句。

Undo Log 是通过回滚段和 Undo 段来进行存储的,其中一个事务系统段可保存 256 个回滚段,一个回滚段可保存 1024 个 Undo 段的信息。

三者关系如下:

图片图片

BTW:该图截取自《MySQL 内核:InnoDB存储引擎》

Undo Log 除了保证原子性,还可以通过其实现 MVCC(多版本并发控制)机制,支持 MySQL InnoDB 的快照读的操作。

持久性(Durability)

网上的很多资料说,MySQL InnoDB 的持久性是通过具备 WAL (Write-Ahead Logging)机制的 Redo Log 来实现的,要求所有数据库写操作在写入数据文件之前,必须先写入到日志文件中。

这种说法不能说不对,只是不太全面。

除了 Redo Log 之外,Double Write 机制也是 MySQL InnoDB用来保证数据页完整性的技术,从而保证了持久性,接下来我们了解一下它的作用。

众所周知,MySQL InnoDB 的最小 IO 单元为默认 16KB 的 Page,无论其存储在 Buffer Pool 中还是在磁盘上,而 Linux 的文件系统 Page 只有 4KB。

这也就意味着,如果 MySQL InnoDB 将一个 Page 的数据进行刷盘操作,需要写四个文件系统的 Page,但这个操作并不是原子性的。

如果在这个过程中遇到系统崩溃或者服务器宕机,就会导致四个文件系统的 Page 没有全部写完,从而出现 MySQL InnoDB 的 Page 损坏的情况,而 Double Write 机制则正是为了解决这个问题。

Double Write 机制包含两个部分,内存中的 Double Write Buffer 和磁盘共享表空间的 128 个数据页,大小都是 2MB。

其具体运行机制如下图所示:

图片图片

在事务提交时 Redo Log 被刷新到磁盘上,随后 Master Thread 适时将 Buffer Pool 中的脏页刷新到 Double Write Buffer 中,最后将 Double Write Buffer 中的数据写入到 Double Write 共享表空间和数据文件中。

如果在写入 Double Write 共享表空间的时候出现宕机崩溃的情况,此时数据文件仍然是完整的,可以通过 Redo Log 进行恢复。

反之,如果在写入 Double Write 共享表空间的时候成功了,却在写入数据文件的时候出现宕机崩溃的情况,则如上图所示:可先通过 Double Write 共享表空间的 Page 对数据文件中的 Page 进行覆盖,再通过 Redo Log 进行恢复。

隔离性(Isolation)

MySQL InnoDB 的默认事务隔离级别是可重复读,其隔离性是通过锁机制和 MVCC(多版本并发控制)来实现的。

先说说写操作,如下图所示,MySQL InnoDB 有很多类型的锁,这些锁机制可以避免多个事务对同一项数据资源进行修改,从而保证了各事务间相互隔离、互不干扰。

图片图片

这里所说的“同一项数据资源”不仅仅是指一行数据,也可以是数据页级别、表级别甚至是库级别。

MySQL InnoDB 中的读操作分为快照读和当前读两种。

快照读是指在事务开始时将数据的一个副本保存起来,然后在整个事务过程中使用这个副本进行读取,不受其他并发事务的影响,SQL语句如下:

SELECT * FROM table1;

当前读是指在事务期间每次读取数据都返回最新的数据,不使用事务开始时的数据副本,SQL语句如下:

SELECT * FROM table1 FOR UPDATE;
SELECT * FROM table1 LOCK IN SHARE MODE;

当前读的隔离性仍然是通过锁机制来实现的,而快照读则是MVCC(多版本并发控制)。

MVCC 是一种数据库中用于处理并发读写事务的技术,通过维护数据的不同版本的方式,来实现查询操作在不需要等待其他事务持有的锁的情况下进行,从而提高了数据库的并发性。

MVCC 是通过数据行的隐藏字段、Undo Log 和 Read View 来实现的。

其中隐藏字段包括:

  • DB_TRX_ID:6 Byte,最近一次插入或修改该行记录的事务ID。
  • DB_ROLL_PTR:7 Byte,回滚指针,指向存储在Undo Log中的这条记录的上一个版本。
  • DB_ROW_ID:6 Byte,隐藏的自增主键,如果数据表中没有设定主键的话,MySQL InnoDB 会自动通过 DB_ROW_ID 产生一个聚簇索引。

再来说说 Read View,当我们发起快照读的时候,会对该行记录生成一个 Read View(读视图),用来判断当前事务能够看到哪个版本的数据,既可能是当前数据表中最新的数据,也可能是该行记录在 Undo Log 里面的某个版本的数据。

如下图所示:

图片图片

一致性(Consistency)

有的同学在一致性保证这块提到了 Binlog,我认为不太准确,因为这里说的是实现事务的一致性,而不是主从库的数据一致性,不一样的。

事务的一致性特性,是为了保障业务逻辑的一致性,保证事务从一个一致的业务状态转换到另一个。

我认为事务的 ACID 之间,并不是像《金字塔原理》一书中提到的 MECE 原则一样 —— 相互独立,完全穷尽。

事务的原子性、持久性和隔离性都是为了实现事务的一致性,让业务状态可以正常流转。

图片图片


责任编辑:武晓燕 来源: 托尼学长
相关推荐

2021-09-27 07:11:18

MySQLACID特性

2024-10-15 10:00:06

2022-03-30 07:28:24

MySQL数据库ACID

2024-12-25 15:44:15

2024-02-04 10:08:34

2021-09-17 12:50:10

MySQL数据库ACID

2021-07-08 07:08:21

MySQL ACID 数据库

2022-05-24 08:03:28

InnoDBMySQL数据

2010-09-08 15:55:20

SQL事务特性

2022-02-09 09:37:54

ReactorNettyI/O

2024-05-11 15:11:44

系统软件部署

2015-08-13 10:29:12

面试面试官

2019-04-03 09:27:01

MySQLInnoDB务ACID

2019-01-29 09:36:10

MySQLACID特性

2023-02-08 07:04:20

死锁面试官单元

2024-09-11 22:51:19

线程通讯Object

2023-11-20 10:09:59

2024-02-20 14:10:55

系统缓存冗余

2020-09-30 06:49:25

MySQL查询删除

2024-06-06 11:38:55

点赞
收藏

51CTO技术栈公众号