MySql事务及ACID实现原理详解

目录
  • 逻辑架构和存储引擎
    • 自动提交
    • 特殊操作
  • ACID 特性
    • 原子性
    • 持久性
    • 隔离性
      • 脏读、不可重复读和幻读
      • 事务隔离级别
      • MVCC
    • 一致性

逻辑架构和存储引擎

自动提交

MySQL 中默认采用的是自动提交(autocommit)模式,如下所示:

在自动提交模式下,如果没有 start transaction 显式地开始一个事务,那么每个 sql 语句都会被当做一个事务执行提交操作。

通过如下方式,可以关闭 autocommit;需要注意的是,autocommit 参数是针对连接的,在一个连接中修改了参数,不会对其他连接产生影响。

如果关闭了 autocommit,则所有的 sql 语句都在一个事务中,直到执行了 commit 或 rollback,该事务结束,同时开始了另外一个事务。

特殊操作

在 MySQL 中,存在一些特殊的命令,如果在事务中执行了这些命令,会马上强制执行 commit 提交事务;如 DDL 语句(create table/drop table/alter/table)、lock tables 语句等等。

不过,常用的 select、insert、update 和 delete 命令,都不会强制提交事务。

ACID 特性

ACID 是衡量事务的四个特性:

原子性(Atomicity,或称不可分割性)一致性(Consistency)隔离性(Isolation)持久性(Durability)按照严格的标准,只有同时满足 ACID 特性才是事务;但是在各大数据库厂商的实现中,真正满足 ACID 的事务少之又少。

例如 MySQL 的 NDB Cluster 事务不满足持久性和隔离性;InnoDB 默认事务隔离级别是可重复读,不满足隔离性;Oracle 默认的事务隔离级别为 READ COMMITTED,不满足隔离性……

因此与其说 ACID 是事务必须满足的条件,不如说它们是衡量事务的四个维度。

下面将详细介绍 ACID 特性及其实现原理,为了便于理解,介绍的顺序不是严格按照 A-C-I-D。

ACID 特性及其实现原理

原子性

定义

原子性是指一个事务是一个不可分割的工作单位,其中的操作要么都做,要么都不做。

如果事务中一个 sql 语句执行失败,则已执行的语句也必须回滚,数据库退回到事务前的状态。

实现原理:undo log

在说明原子性原理之前,首先介绍一下 MySQL 的事务日志。MySQL 的日志有很多种,如二进制日志、错误日志、查询日志、慢查询日志等。

此外 InnoDB 存储引擎还提供了两种事务日志:

redo log(重做日志)undo log(回滚日志)其中 redo log 用于保证事务持久性;undo log 则是事务原子性和隔离性实现的基础。

下面说回 undo log。实现原子性的关键,是当事务回滚时能够撤销所有已经成功执行的 sql 语句。

InnoDB 实现回滚,靠的是 undo log:

当事务对数据库进行修改时,InnoDB 会生成对应的 undo log。如果事务执行失败或调用了 rollback,导致事务需要回滚,便可以利用 undo log 中的信息将数据回滚到修改之前的样子。undo log 属于逻辑日志,它记录的是 sql 执行相关的信息。当发生回滚时,InnoDB 会根据 undo log 的内容做与之前相反的工作:

对于每个 insert,回滚时会执行 delete。对于每个 delete,回滚时会执行 insert。对于每个 update,回滚时会执行一个相反的 update,把数据改回去。以 update 操作为例:当事务执行 update 时,其生成的 undo log 中会包含被修改行的主键(以便知道修改了哪些行)、修改了哪些列、这些列在修改前后的值等信息,回滚时便可以使用这些信息将数据还原到 update 之前的状态。

持久性

定义

持久性是指事务一旦提交,它对数据库的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。

实现原理:redo log

redo log 和 undo log 都属于 InnoDB 的事务日志。下面先聊一下 redo log 存在的背景。

InnoDB 作为 MySQL 的存储引擎,数据是存放在磁盘中的,但如果每次读写数据都需要磁盘 IO,效率会很低。

为此,InnoDB 提供了缓存(Buffer Pool),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 也需要在事务提交时将日志写入磁盘,为什么它比直接将 Buffer Pool 中修改的数据写入磁盘(即刷脏)要快呢?

主要有以下两方面的原因:

刷脏是随机 IO,因为每次修改的数据位置随机,但写 redo log 是追加操作,属于顺序 IO。刷脏是以数据页(Page)为单位的,MySQL 默认页大小是 16KB,一个 Page 上一个小修改都要整页写入;而 redo log 中只包含真正需要写入的部分,无效 IO 大大减少。redo log 与 binlog

我们知道,在 MySQL 中还存在 binlog(二进制日志)也可以记录写操作并用于数据的恢复,但二者是有着根本的不同的。

  • 作用不同:

redo log 是用于 crash recovery 的,保证 MySQL 宕机也不会影响持久性;

binlog 是用于 point-in-time recovery 的,保证服务器可以基于时间点恢复数据,此外 binlog 还用于主从复制。

  • 层次不同:

redo log 是 InnoDB 存储引擎实现的,而 binlog 是 MySQL 的服务器层(可以参考文章前面对 MySQL 逻辑架构的介绍)实现的,同时支持 InnoDB 和其他存储引擎。

  • 内容不同:

redo log 是物理日志,内容基于磁盘的 Page。binlog 是逻辑日志,内容是一条条 sql。

  • 写入时机不同:

redo log 的写入时机相对多元。前面曾提到,当事务提交时会调用 fsync 对 redo log 进行刷盘;这是默认情况下的策略,修改 innodb_flush_log_at_trx_commit 参数可以改变该策略,但事务的持久性将无法保证。除了事务提交时,还有其他刷盘时机:如 master thread 每秒刷盘一次 redo log 等,这样的好处是不一定要等到 commit 时刷盘,commit 速度大大加快。

binlog 在事务提交时写入。

隔离性

定义

与原子性、持久性侧重于研究事务本身不同,隔离性研究的是不同事务之间的相互影响。

隔离性是指事务内部的操作与其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

严格的隔离性,对应了事务隔离级别中的 Serializable(可串行化),但实际应用中出于性能方面的考虑很少会使用可串行化。

隔离性追求的是并发情形下事务之间互不干扰。简单起见,我们仅考虑最简单的读操作和写操作(暂时不考虑带锁读等特殊操作)。

那么隔离性的探讨,主要可以分为两个方面:

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

首先来看两个事务的写操作之间的相互影响。隔离性要求同一时刻只能有一个事务对数据进行写操作,InnoDB 通过锁机制来保证这一点。

锁机制的基本原理可以概括为:

事务在修改数据之前,需要先获得相应的锁。获得锁之后,事务便可以修改数据。该事务操作期间,这部分数据是锁定的,其他事务如果需要修改数据,需要等待当前事务提交或回滚后释放锁。行锁与表锁:按照粒度,锁可以分为表锁、行锁以及其他位于二者之间的锁。

表锁在操作数据时会锁定整张表,并发性能较差;行锁则只锁定需要操作的数据,并发性能好。

但是由于加锁本身需要消耗资源(获得锁、检查锁、释放锁等都需要消耗资源),因此在锁定数据较多情况下使用表锁可以节省大量资源。

MySQL 中不同的存储引擎支持的锁是不一样的,例如 MyIsam 只支持表锁,而 InnoDB 同时支持表锁和行锁,且出于性能考虑,绝大多数情况下使用的都是行锁。

如何查看锁信息?有多种方法可以查看 InnoDB 中锁的情况,例如:

select * from information_schema.innodb_locks; #锁的概况
show engine innodb status; #InnoDB整体状态,

其中包括锁的情况下面来看一个例子:

在事务A中执行:start transaction;update account SET balance = 1000 where id = 1;

在事务B中执行:start transaction;update account SET balance = 2000 where id = 1;

此时查看锁的情况:

show engine innodb status 查看锁相关的部分:

通过上述命令可以查看事务 24052 和 24053 占用锁的情况;其中 lock_type 为 RECORD,代表锁为行锁(记录锁);lock_mode 为 X,代表排它锁(写锁)。

除了排它锁(写锁)之外,MySQL 中还有共享锁(读锁)的概念。由于本文重点是 MySQL 事务的实现原理,因此对锁的介绍到此为止。

介绍完写操作之间的相互影响,下面讨论写操作对读操作的影响。

脏读、不可重复读和幻读

首先来看并发情况下,读操作可能存在的三类问题。

①脏读:当前事务(A)中可以读到其他事务(B)未提交的数据(脏数据),这种现象是脏读。

举例如下(以账户余额表为例):

②不可重复读:在事务 A 中先后两次读取同一个数据,两次读取的结果不一样,这种现象称为不可重复读。

脏读与不可重复读的区别在于:前者读到的是其他事务未提交的数据,后者读到的是其他事务已提交的数据。

举例如下:

③幻读:在事务 A 中按照某个条件先后两次查询数据库,两次查询结果的条数不同,这种现象称为幻读。

不可重复读与幻读的区别可以通俗的理解为:前者是数据变了,后者是数据的行数变了。

举例如下:

事务隔离级别

sql 标准中定义了四种隔离级别,并规定了每种隔离级别下上述几个问题是否存在。

一般来说,隔离级别越低,系统开销越低,可支持的并发越高,但隔离性也越差。

隔离级别与读问题的关系如下:

在实际应用中,读未提交在并发时会导致很多问题,而性能相对于其他隔离级别提高却很有限,因此使用较少。

可串行化强制事务串行,并发效率很低,只有当对数据一致性要求极高且可以接受没有并发时使用,因此使用也较少。

因此在大多数数据库系统中,默认的隔离级别是读已提交(如 Oracle)或可重复读(后文简称 RR)。

可以通过如下两个命令分别查看全局隔离级别和本次会话的隔离级别:

InnoDB 默认的隔离级别是 RR,后文会重点介绍 RR。需要注意的是,在 SQL 标准中,RR 是无法避免幻读问题的,但是 InnoDB 实现的 RR 避免了幻读问题。

MVCC

RR 解决脏读、不可重复读、幻读等问题,使用的是 MVCC:MVCC 全称 Multi-Version Concurrency Control,即多版本的并发控制协议。

下面的例子很好的体现了 MVCC 的特点:在同一时刻,不同的事务读取到的数据可能是不同的(即多版本)——在 T5 时刻,事务 A 和事务 C 可以读取到不同版本的数据。

MVCC 最大的优点是读不加锁,因此读写不冲突,并发性能好。InnoDB 实现 MVCC,多个版本的数据可以共存,主要是依靠数据的隐藏列(也可以称之为标记位)和 undo log。

其中数据的隐藏列包括了该行数据的版本号、删除时间、指向 undo log 的指针等等。

当读取数据时,MySQL 可以通过隐藏列判断是否需要回滚并找到回滚需要的 undo log,从而实现 MVCC;隐藏列的详细格式不再展开。

下面结合前文提到的几个问题分别说明。

①脏读

当事务 A 在 T3 时间节点读取 zhangsan 的余额时,会发现数据已被其他事务修改,且状态为未提交。

此时事务 A 读取最新数据后,根据数据的 undo log 执行回滚操作,得到事务 B 修改前的数据,从而避免了脏读。

②不可重复读

当事务 A 在 T2 节点第一次读取数据时,会记录该数据的版本号(数据的版本号是以 row 为单位记录的),假设版本号为 1;当事务 B 提交时,该行记录的版本号增加,假设版本号为 2。

当事务 A 在 T5 再一次读取数据时,发现数据的版本号(2)大于第一次读取时记录的版本号(1),因此会根据 undo log 执行回滚操作,得到版本号为 1 时的数据,从而实现了可重复读。

③幻读

InnoDB 实现的 RR 通过 next-keylock 机制避免了幻读现象。

next-keylock 是行锁的一种,实现相当于 record lock(记录锁) + gap lock(间隙锁);其特点是不仅会锁住记录本身(record lock 的功能),还会锁定一个范围(gap lock 的功能)。

当然,这里我们讨论的是不加锁读:此时的 next-key lock 并不是真的加锁,只是为读取的数据增加了标记(标记内容包括数据的版本号等);准确起见姑且称之为类 next-key lock 机制。

还是以前面的例子来说明:

当事务 A 在 T2 节点第一次读取 0

这样当 T5 时刻再次读取 0

小结:概括来说,InnoDB 实现的 RR,通过锁机制、数据的隐藏列、undo log 和类 next-key lock,实现了一定程度的隔离性,可以满足大多数场景的需要。

不过需要说明的是,RR 虽然避免了幻读问题,但是毕竟不是 Serializable,不能保证完全的隔离。

下面是一个例子,大家可以自己验证一下:

一致性

基本概念

一致性是指事务执行结束后,数据库的完整性约束没有被破坏,事务执行的前后都是合法的数据状态。

数据库的完整性约束包括但不限于:

实体完整性(如行的主键存在且唯一)列完整性(如字段的类型、大小、长度要符合要求)外键约束用户自定义完整性(如转账前后,两个账户余额的和应该不变)实现

可以说,一致性是事务追求的最终目标:前面提到的原子性、持久性和隔离性,都是为了保证数据库状态的一致性。此外,除了数据库层面的保障,一致性的实现也需要应用层面进行保障。

实现一致性的措施包括:

保证原子性、持久性和隔离性,如果这些特性无法保证,事务的一致性也无法保证。数据库本身提供保障,例如不允许向整形列插入字符串值、字符串长度不能超过列的限制等。应用层面进行保障,例如如果转账操作只扣除转账者的余额,而没有增加接收者的余额,无论数据库实现的多么完美,也无法保证状态的一致。总结

下面总结一下 ACID 特性及其实现原理:

原子性:语句要么全执行,要么全不执行,是事务最核心的特性。

事务本身就是以原子性来定义的;实现主要基于 undo log。

持久性:保证事务提交后不会因为宕机等原因导致数据丢失;实现主要基于 redo log。

隔离性:保证事务执行尽可能不受其他事务影响;

InnoDB 默认的隔离级别是 RR,RR 的实现主要基于锁机制、数据的隐藏列、undo log 和类 next-key lock 机制。

一致性:事务追求的最终目标,一致性的实现既需要数据库层面的保障,也需要应用层面的保障。

以上就是MySql事务及ACID实现原理详解的详细内容,更多关于MySql事务ACID原理的资料请关注我们其它相关文章!

(0)

相关推荐

  • MySQL数据库的触发器和事务

    目录 一.触发器 概念 二. 触发器的操作 触发器的增删改操作 触发器的查看删除操作 三.事务 概念 四.事务的操作 基本流程 基础操作 事务的提交方式 事务的隔离级别 隔离操作 一.触发器 概念 触发器是SQL server提供给程序员和数据分析员来保证数据完整性的一种方法,它是与表事件相关的特殊的存储过程,是由事件来触发.触发器经常用来加强数据的完整性约束和业务规则. 二. 触发器的操作 创建account和account_log数据表: CREATE TABLE account( id I

  • MySQL事务管理的作用详解

    目录 1.为何使用事务管理 2.数据库事务的原理 3.什么是事务 3.1 事务的特性ACID 3.2 事务的并发问题 3.3 隔离级别 4.Spring事务管理 1.为何使用事务管理 可以保证数据的完整性.事务(Transaction),就是将一组SQL语句放在同一批次内去执行,如果一个SQL语句出错,则该批次内 的所有SQL都将被取消执行. 例子: 转账为例. 金庸向张无忌转账1000元.----在数据库中修改两个账号的余额. 发生意外情况,则出现金庸减钱成功,而张无忌加钱失败. 如何解决?

  • MySQL事务的隔离级别详情

    目录 一.隔离级别的概念 二.测试TRANSACTION_READ_UNCOMMITTED隔离级别 三.测试TRANSACTION_READ_COMMITTED隔离级别 四.测试TRANSACTION_REPEATABLE_READ隔离级别 五.测试TRANSACTION_SERIALIZABLE隔离级别 一.隔离级别的概念 为什么要考虑隔离级别? 因为事务要并发执行,而并发执行可能会出现一些问题:脏读.不可重复读和虚读,有的是允许出现的,有的不允许出现,对于这种不同程度上的出现或不出现的并发控

  • MySql索引和事务定义到使用全面涵盖

    目录 索引是什么 索引的使用场景 索引的常见操作 索引背后的数据结构 事务是什么 事务的基本特性 小结 索引是什么 索引是一种特殊的文件,包含着对数据表里所有记录的引用指针.可以对表中的一列或多列创建索引,并指定索引的类型,各类索引有各自的数据结构实现.索引就相当于一本书的目录,通过索引 可快速定位.检索数据.使用索引大大提高了查找效率,但同时索引也占用了更多的空间,拖慢了增删改的速度. 索引的使用场景 1.数据量较大,且经常对这些列进行条件查询. 2.该数据库表的插入操作,及对这些列的修改操作

  • MySql事务原理介绍及特性

    目录 1. 什么是事务 2. 事务是如何做到同时成功失败 3. 如何提交回滚事务 1. mysql 中默认的事务行为是怎样的 2. 回滚事务 3. 提交事务 4. 事务的4个特性 5. 事务的隔离性 1. 什么是事务 一个事务就是一个完整的业务逻辑. 什么是一个完整的业务逻辑? 假设转账,从A账户向B账户中转账10000. 将A账户的钱减去10000 ( update语句) 将B账户的钱加上10000 ( update语句) 这就是一个完整的业务逻辑. 以_上的操作是-一个最小的工作单元,要么同

  • MySql事务及ACID实现原理详解

    目录 逻辑架构和存储引擎 自动提交 特殊操作 ACID 特性 原子性 持久性 隔离性 脏读.不可重复读和幻读 事务隔离级别 MVCC 一致性 逻辑架构和存储引擎 自动提交 MySQL 中默认采用的是自动提交(autocommit)模式,如下所示: 在自动提交模式下,如果没有 start transaction 显式地开始一个事务,那么每个 sql 语句都会被当做一个事务执行提交操作. 通过如下方式,可以关闭 autocommit;需要注意的是,autocommit 参数是针对连接的,在一个连接中

  • MySQL事务与锁实例教程详解

    目录 MySQL事务和锁 事务 事务的控制语句 事务隔离级别设置 脏读 不可重复读 幻读 锁机制 InnoDB的行级锁 锁实战 死锁 总结 MySQL事务和锁 事务 说到关系型的数据库的事务,相信大家对四大特性都不陌生,分别是原子性.一致性.隔离性.持久性,简称为ACID特性. MySQL中支持3种不同的存储引擎: MyISAM存储引擎.Memory存储引擎.和InnoDB存储引擎 注:只有InnoDB才支持事务. 事务的控制语句 控制语句 作用 begin或者start transaction

  • spring事务Propagation及其实现原理详解

    本文研究的主要是spring事务Propagation及其实现原理,具体介绍如下. 简介 spring目前已是java开发的一个事实标准,这得益于它的便利.功能齐全.容易上手等特性.在开发过程当中,操作DB是非常常见的操作,而涉及到db,就会涉及到事务.事务在平时的开发过程当中,就算没有注意到,程序正常执行不会有副作用,但如果出现了异常,而又没有处理好事务的话,可能就会出现意想不到的结果.spring在事务方面进行了各种操作的封装,特别是声明式事务的出现,让开发变得更加的舒心.spring对事务

  • MySQL 事务概念与用法深入详解

    本文实例讲述了MySQL 事务概念与用法.分享给大家供大家参考,具体如下: 事务的概念 MySQL事务是一个或者多个的数据库操作,要么全部执行成功,要么全部失败回滚. 事务是通过事务日志来实现的,事务日志包括:redo log和undo log. 事务的状态 活动的(active) 事务对应的数据库操作正在执行过程中时,我们就说该事务处在活动的状态. 部分提交的(partially committed) 当事务中的最后一个操作执行完成,但由于操作都在内存中执行,所造成的影响并没有刷新到磁盘时,我

  • Mysql主从复制作用和工作原理详解

    一.什么是主从复制 主从复制,是用来建立一个和主数据库完全一样的数据库环境,称为从数据库,主数据库一般是准实时的业务数据库.在最常用的mysql数据库中,支持单项.异步赋值.在赋值过程中,一个服务器充当主服务器,而另外一台服务器充当从服务器:此时主服务器会将更新信息写入到一个特定的二进制文件中. 并会维护文件的一个索引用来跟踪日志循环.这个日志可以记录并发送到从服务器的更新中去.当一台从服务器连接到主服务器时,从服务器会通知主服务器从服务器的日志文件中读取最后一次成功更新的位置.然后从服务器会接

  • MySQL DISTINCT 的基本实现原理详解

    前言 DISTINCT 实际上和 GROUP BY 操作的实现非常相似,只不过是在 GROUP BY 之后的每组中只取出一条记录而已.所以,DISTINCT 的实现和 GROUP BY 的实现也基本差不多,没有太大的区别.同样可以通过松散索引扫描或者是紧凑索引扫描来实现,当然,在无法仅仅使用索引即能完成 DISTINCT 的时候,MySQL 只能通过临时表来完成. 但是,和 GROUP BY 有一点差别的是,DISTINCT 并不需要进行排序.也就是说,在仅仅只是 DISTINCT 操作的 Qu

  • Mysql MVCC机制原理详解

    什么是MVCC MVCC,全称Multi-Version Concurrency Control,即多版本并发控制.MVCC是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存. 我们知道,一般情况下我们使用mysql数据库的时候使用的是Innodb存储引擎,Innodb存储引擎是支持事务的,那么当多线程同时执行事务的时候,可能会出现并发问题.这个时候需要一个能够控制并发的方法,MVCC就起到了这个作用. Mysql的锁和事务隔离级别 在理解MVCC机制

  • MySQL COUNT(*)性能原理详解

    目录 前言 1.COUNT(1).COUNT(*)与COUNT(字段)哪个更快? 实验分析 实验结果 实验结论 2.COUNT(*)与TABLES_ROWS 3.COUNT(*)是怎么样执行的? 4.总结 前言 在实际开发过程中,统计一个表的数据量是经常遇到的需求,用来统计数据库表的行数都会使用COUNT(*),COUNT(1)或者COUNT(字段),但是表中的记录越来越多,使用COUNT(*)也会变得越来越慢,今天我们就来分析一下COUNT(*)的性能到底如何. 1.COUNT(1).COUN

  • mysql中的mvcc 原理详解

    目录 简介 前言 一.mysql 数据写入磁盘流程 二.redo log 1.redolog 的整体流程 2.为什么需要 redo log 三.undo log 1.undo log 特点 2.undo log 类型 3.undo log 生成过程 4.undo log 回滚过程 5.undo log的删除 四.mvcc 1.什么是MVCC 2.MVCC组成 3.快照读与当前读 快照读 当前读 五.mvcc操作演示 1.READ COMMITTED 隔离级别 2.REPEATABLE READ 

  • MySQL prepare原理详解

    Prepare的好处  Prepare SQL产生的原因.首先从mysql服务器执行sql的过程开始讲起,SQL执行过程包括以下阶段 词法分析->语法分析->语义分析->执行计划优化->执行.词法分析->语法分析这两个阶段我们称之为硬解析.词法分析识别sql中每个词,语法分析解析SQL语句是否符合sql语法,并得到一棵语法树(Lex).对于只是参数不同,其他均相同的sql,它们执行时间不同但硬解析的时间是相同的.而同一SQL随着查询数据的变化,多次查询执行时间可能不同,但硬解

随机推荐