个人MySQL的事务特性原理笔记总结

​ 事务Transaction是数据库区别于文件系统的重要特性之一,也是MySQL等关系型数据库区别于NoSQL数据库的重要方面。本文会先介绍事务的4个特性,然后讲解每个特性在MySQL中的实现原理。过程中有不对的地方,欢迎读者指出矫正与探讨。

一、基础概念

	### 1. 事务定义

​ 根据MySQL官网介绍:
​ 参考 https://dev.mysql.com/doc/refman/5.6/en/glossary.html#glos_acid

​ Transactions are atomic units of work that can be committed or rolled back. When a transaction makes multiple changes to the database, either all the changes succeed when the transaction is committed, or all the changes are undone when the transaction is rolled back.

​ 事务是可以提交回退的原子工作单元。当事务对数据库进行多次更改时,要么在提交事务后所有更改成功,要么在回滚事务后撤消所有更改。

2. 事务控制语句

  • START TRANSACTION | BEGIN:显示地开启一个事务。
  • COMMIT:也可以写成COMMIT WORK,会提交事务,修改数据会写入redo日志进行持久化。
  • ROLLBACK:回滚会结束用户的事务,并撤销在该事务中已经修改的数据。

3. 事务特性

​ 事务具体有四个特性ACID,严格来说,事务一定要同时满足这4个特性。但是不同的数据库厂商会在这个定义上有所调整。例如在MYSQL中,也只有InnoDB存储引擎完全满足ACID,像MyISAM存储引擎就完全不支持事务。ACID是以下4个词的缩写:

  • 原子性(atomicity)
  • 一致性(consistency)
  • 隔离性(isolation)
  • 持久性(durability)

​ 如无特殊说明,后文中描述的内容都是基于InnoDB存储引擎。

二、原子性

1. 原子性定义

​ 原子性指一个事务是数据库中不可分割的工作单位。只有事务中的所有操作CRUD都执行成功,才算整个事务成功。事务中的任何一个SQL语句执行失败,已经执行成功的SQL语句也必须撤销,数据库状态应该回到执行事务前的状态。

2. 实现

​ 在说明实现原理之前,先介绍一下MySQL的事务日志。MySQL的日志有很多种,比如:二进制日志、查询日志、慢查询日志、错误日志。此外InnoDB存储引擎还提供了两种事务日志:redo log(重做日志)和undo log(回滚日志)。其中redo log用于保证事务数据的持久化,undo log用于事务的回滚以及MVCC机制。

​ 说回undo log,实现原子性的保证,是当事务回滚时能够撤销该事务中已经执行成功的所有SQL语句。undo log 能够保证原子性在于:当事务对数据库进行数据修改、删除、增加操作时,会产生对应的undo log日志,这样如果事务或者语句由于某种原因失败了,又或者执行了ROLLBACK语句请求回滚,就可以利用这些undo信息将数据回滚到修改之前的样子。

​ undo是逻辑日志,会记录事务中执行的SQL语句。当InnoDB存储引擎回滚时,它会根据undo log的中事务的语句,执行相反的SQL语句。对于每个INSERT,InnoDB存储引擎会完成一个DELETE;对于每个DELETE,InnoDB存储引擎会执行一个INSERT;对于每个UPDATE,InnoDB存储引擎会执行一个相反的UPDATE,将修改前的行修改回去。

​ MySQL在information_schema数据库下添加一张数据字典表为INNODB_TRX_UNDO,用来记录事务对应的undo log。该字典表会记录当前事务的这些信息:事务ID、回滚事务ID、插入类型、参与的表ID、发生变化的数据信息等.这样在需要会根据对应信息进行回滚。

三、持久性

1. 定义

​ 事务一旦提交,其结果就是永久性的。即使发生宕机等故障,数据库也能将数据恢复。

2. 实现

​ 持久性的实现依赖于上面提到的另一个事务日志:redo log日志,也叫重做日志。重做日志由两部分组成:一个是内存中的重做日志缓冲redo log buffer,它是易失的;二是重做日志文件,redo log file,它是持久的。

​ InnoDB是事务的存储引擎,其通过Force Log at Commit机制实现事务的持久性。当对数据库进行修改时,会将执行的语句先添加到redo log buffer中,当事务提交COMMIT时,InnoDB存储引擎会将redo log buffer的日志写入到redo log日志中进行持久化。同时还会进行一次fsync操作,可以确保重做日志写入磁盘。

3. 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中修改的数据写入磁盘(即刷脏)要快呢?主要有以下两方面的原因:

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

附上一张InnoDB内存数据对象图

四、隔离性

1. 定义

​ 事务的隔离性是指__事务内的数据库操作与其他事务是隔离的,即该事务提交前对其他事务都不可见,事务间的操作是相互不影响的。__

2. 锁机制

​ 隔离性的原理主要依赖锁来完成。可以参考我的另一篇文章 https://www.cnblogs.com/process-h/p/14173838.html

五、一致性

1. 定义

​ 一致性是指__事务将数据库从一种状态转变为下一种一致的状态。__在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。比如说在表中有一个姓名字段,是唯一约束(即姓名不可重复),这时有个事务对姓名字段做了修改,但是事务提交或回滚后,姓名字段变得非唯一了,那这就破坏了事务的一致性要求。

2. 实现

​ 一致性也是事务追求的最终目的:__前面提到的原子性、持久性和隔离性,都是为了保证数据库状态的一致性。__不过,仅仅靠数据库来实现一致性还不够,也需要应用层面提供保障,比如在涉及转账业务时,A账户转出100,B账户入账100这2步需要一起完成,而不能只从A账户转出100.

参考文献

《深入浅出MySQL》
《MySQL技术内幕:InnoDB存储引擎》
https://dev.mysql.com/doc/refman/5.6/en/mysql-acid.html
https://dev.mysql.com/doc/refman/5.6/en/innodb-multi-versioning.html
https://www.cnblogs.com/kismetv/p/10331633.html#!comments

版权声明:本文为process-h原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/process-h/p/14201773.html