MySQL-ACID与事务隔离级别

发布时间 2023-03-23 16:28:19作者: zibuyu886

MySQL-ACID与事务隔离级别

1. 事务的概念

数据库的事务是并发控制的基本单位,是指逻辑上的一组操作,要么全部执行,要么全部不执行。中间任何一个操作出现错误, 都会回滚(rollback)到数据最初的安全状态, 以保证不会对系统数据库造成错误的改动。

事务有如下几个特点:

  1. 原子性(Atomicity)-- A

    事务是数据库中最小的工作单元,要么全部执行成功,要么全部执行失败,不能只执行其中一部分。当一个事务需要执行多个步骤时,数据库将把这些步骤组合成一个原子性操作,使得这个操作具有“全部或者不执行”的特性。如果事务执行过程中出现故障,数据库会自动回滚这个事务,使得数据库的状态不会因为事务执行失败而受到影响。

  2. 一致性(Consistency)-- C

    数据库中的每个事务都需要保证其执行前后都维护了数据库的一致性。在执行事务的过程中,数据库会对数据进行检查,确保事务的执行不会破坏数据库的完整性约束条件。

  3. 隔离性(Isolation)-- I

    事务的隔离性是指在并发环境中,并发的事务是互相隔离的,一个事务的执行不能被其它事务干扰。也就是说,不同的事务并发操作相同的数据时,每个事务都有各自完整的数据空间。

    隔离性通过事务的隔离级别来定义, 并用锁机制来保证写操作的隔离性, MVCC来保证读操作的隔离性.

  4. 持久性(Durability)-- D

    一旦一个事务成功地执行完成,其所做的修改将永久保存到数据库中。即使在系统故障或崩溃的情况下,数据库也能够通过日志等机制保证这些修改不会丢失。

2. 事务ACID实现

img

2.1 原子性的实现

MySQL的事务日志。MySQL的日志有很多种,如二进制日志、错误日志、查询日志、慢查询日志等,此外InnoDB存储引擎还提供了两种事务日志:redo log(重做日志)和undo log(回滚日志)。其中redo log用于保证事务持久性;undo log则是事务原子性和隔离性实现的基础。

undo log中记录了数据变更的前映像,如果事务失败,或者调用了 rollback,数据库就会根据undo log来做数据变更的反向操作。在InnoDB存储引擎中,undo log是采用分段(segment)的方式进行存储的。rollback segment称为回滚段,每个回滚段中有1024个undo log segment。在MySQL5.5之前,只支持1个rollback segment,也就是只能记录1024个undo操作。在MySQL5.5之后,可以支持128个rollback segment,分别从resg slot0 - resg slot127,每一个resg slot,也就是每一个回滚段,内部由1024个undo segment 组成,即总共可以记录128 * 1024个undo操作。

不仅存放着数据更新前的记录,还记录着RowID、事务ID、回滚指针。其中事务ID每次递增,回滚指针第一次如果是insert语句的话,回滚指针为NULL,第二次update之后的undo log的回滚指针就会指向刚刚那一条undo log日志,依次类推,就会形成一条undo log的回滚链,方便找到该条记录的历史版本。

2.2 隔离性

隔离性研究的是不同事务之间的相互影响

简单起见,我们主要考虑最简单的读操作和写操作(加锁读等特殊读操作会特殊说明),那么隔离性的探讨,主要可以分为两个方面:

  • (一个事务)写操作对(另一个事务)写操作的影响:锁机制保证隔离性
  • (一个事务)写操作对(另一个事务)读操作的影响:MVCC保证隔离性

关于MySQL的锁机制,查看另一篇文章:

2.3 持久性

MySQL的持久性依赖于其参数变量的配置、机器硬件的能力,比较复杂。官方列出的一些因素如下:

  • The InnoDB doublewrite buffer
  • The innodb_flush_log_at_trx_commit variable.
  • The sync_binlog variable.
  • The innodb_file_per_table variable.
  • The write buffer in a storage device, such as a disk drive, SSD, or RAID array.
  • A battery-backed cache in a storage device.
  • The operating system used to run MySQL, in particular its support for the fsync() system call.
  • An uninterruptible power supply (UPS) protecting the electrical power to all computer servers and storage devices that run MySQL servers and store MySQL data.
  • Your backup strategy, such as frequency and types of backups, and backup retention periods.
  • For distributed or hosted data applications, the particular characteristics of the data centers where the hardware for the MySQL servers is located, and network connections between the data centers.

翻译成中文就是如下图所示:

graph LR; A-->C(InnoDB doublewrite buffer) A(持久性影响因素)-->B(innodb_flush_log_at_trx_commit -- redo log刷盘机制) A-->D(sync_binlog -- binlog刷盘机制) A-->E(存储设备中的写缓冲区 例如磁盘驱动器固态硬盘或RAID阵列) A-->F(存储设备的备用电池) A-->G(操作系统对MySQL调用`fsync`的支持) A-->H(无间断电源 用于保护运行MySQL服务器和存储MySQL数据的所有计算机服务器和存储设备的电力) A-->I(备份策略影响:备份周期,备份类型,备份保留时长) A-->J(对于分布式或托管数据应用程序,MySQL服务器硬件所在的数据中心的特定特征,以及数据中心之间的网络连接)

2.4 一致性

ACID模型中的一致性方面主要涉及内部InnoDB处理,以保护数据免受崩溃的影响。相关的MySQL功能包括:

  • InnoDB doublewrite buffer.
  • crash recovery.

3. MVCC机制

3.1 MVCC实现原理

InnoDB是一个多版本存储引擎。它 保留有关已更改行的旧版本的信息以支持 事务功能,例如并发和回滚。这 信息存储在名为 Undo 的数据结构中的 undo segment中(回滚段)。使用回滚中的信息段以执行事务中所需的撤消操作。它还使用这些信息来构建早期版本的,用于一致读取的行。

三个隐藏字段

innodb表内部,为每一行数据增加了三个隐藏字段:

  • 6-byte DB_TRX_ID:最后插入或更新该行的事务标识符。此外,删除在内部被视为更新,其中一行中的一个特殊位被设置为标记它已被删除。
  • 7-byte DB_ROLL_PTR:回滚指针指向写入回滚段的撤销日志记录。如果该行已更新,则撤销日志记录包含重建该行内容所需的信息,以使其回滚到更新前的状态。
  • 6-byte DB_ROW_ID:行ID,随着插入新行而单调递增。如果自动生成一个聚集索引,则该索引包含行ID值。否则,该列不会出现在任何索引中。数据没有主键,会以此生存聚餐索引。

undo log

在InnoDB存储引擎中,undo log是采用分段(segment)的方式进行存储的。rollback segment称为回滚段,每个回滚段中有1024个undo log segment。在MySQL5.5之前,只支持1个rollback segment,也就是只能记录1024个undo操作。在MySQL5.5之后,可以支持128个rollback segment,分别从resg slot0 - resg slot127,每一个resg slot,也就是每一个回滚段,内部由1024个undo segment 组成,即总共可以记录128 * 1024个undo操作。

不仅存放着数据更新前的记录,还记录着RowID、事务ID、回滚指针。其中事务ID每次递增,回滚指针第一次如果是insert语句的话,回滚指针为NULL,第二次update之后的undo log的回滚指针就会指向刚刚那一条undo log日志,依次类推,就会形成一条undo log的回滚链,方便找到该条记录的历史版本。

在MySQL5.6中开始支持把undo log分离到独立的表空间,并放到单独的文件目录下;这给我们部署不同IO类型的文件位置带来便利,对于并发写入型负载,我们可以把undo文件部署到单独的高速存储设备上。

一个事务最多分配四个撤消日志,每个日志对应一个 以下操作类型:

  1. 用户自定义表的插入操作
  2. 用户自定义表的删除和更新操作
  3. 用户自定义的临时表上的插入操作
  4. 用户定义的临时表的更新和删除操作

总的来说,分为insert undo log and update undo log。插入回滚日志代表在事务insert新纪录时产生,只有在书屋回滚时需要,并且在事务提交后,可以立即删除;更新回滚日志,在事务更新和删除时产生,不仅在事务回滚时需要,在快照读时也需要,不能立即删除,只有在快照读或者事务回滚不需要时才可以删除。另外MySQL有专用的purge thread来清理。

purge线程自己维护了一个系统中最老的read view,如果事务的read view可以被purge线程看见,那这个read view和对应的undo log就可以被删除。

read view

要理解读视图,首先要理解当前读和快照读。

当前读,读的是数据的最新版本,读取时还要保证其他事物不会修改当前的记录。(select ... for update \ lock in share mode, update, delete, insert )。

快照读,读的是数据的历史版本,不加锁的select就是快照读,数据可能不是最新的。快照读的前提是 隔离级别不是串行化,串行化隔离级别下,快照读会退化为当前读。

总的来说,MVCC是为了读写冲突不加锁的一种实现方式,这个读就是快照读。当前读实际上是一种加锁的操作,是悲观锁的实现。

3.2 隔离级别

MySQL有四种隔离级别,分别为读未提交(Read Uncommitted)、读已提交(Read Committed)、可重复读(Repeatable Read)、串行化(Serializable)。MySQL默认的隔离级别是Repeatable Read.

  • 读未提交(Read Uncommitted):最低的隔离级别,允许一个事务可以读取到其他事务未提交的数据。

  • 读已提交(Read Committed):一个事务执行的查询,只能看到这次查询开始之前提交的数据。读已提交无法防止不可重复读和幻读两种异常现象。如果冲突的事务比较少,简单高效的读已提交隔离级别,对应用来说是足够的。

  • 可重复读(Repeatable Read):事务内不同时间读到的同一批数据是一致的。无法防止幻读这种异常现象。

  • 可串行化(Serializable):一个事务的查询,只能看到事务开始之前提交的数据。这是最严格的隔离级别,可以防止脏读、不可重复读和幻读三种异常现象,事务看起来就像是串行执行的。

4. 不同的隔离级别带来的问题

可能导致的问题有

1.脏读: 一个事务读到另一个事务未提交的更新数据。

2.不可重复读: 一个事务两次读同一行数据,可是这两次读到的数据不一样。脏读与不可重复读的区别在于:前者读到的是其他事务未提交的数据,后者读到的是其他事务已提交的数据。

3.幻读: 一个事务执行两次查询,但第二次查询比第一次查询多出了一些数据行。不可重复读与幻读的区别可以通俗的理解为:前者是数据变了,后者是数据的行数变了。

img

参考文档:

事务的四大特性_事务的四个特性_今天你学习了么的博客-CSDN博客

【数据库】事务 ACID 实现原理 - 原子性 & 持久性 - 知乎 (zhihu.com)