事务篇

事务基础知识

存储引擎支持情况

  1. 只有InnoDB是支持事务的

基本概念

  • 事务:一组逻辑操作单元,使数据从一种状态变换到另一种状态
  • 事务处理的原则:保证所有事物作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都被提交(commit),那么这些修改就永久地保存下来;要么数据库管理系统将放弃所作的所有修改,整个事务回滚(rollback)到最初状态

事物的ACID特性

  • 原子性(atomicity):原子性是指事务是一个不可分割的工作单位,要么全部提交,要么全部失败回滚
  • 一致性(consistency):根据定义,一致性是指事务执行前后,数据从一个合法性状态变换到另外一个合法性状态。这种状态是语义上的而不是语法上的,跟具体的业务有关。
    • 那什么是合法的数据状态呢?满足预定的约束的状态就叫合法的状态。满足这个状态,数据就是一致的,不满足这个状态,数据就是不一致的。如果事务中的某个操作失败了,系统就会自动撤销当前正在执行的事务,返回到事务操作之前的状态。
  • 隔离性(isolation):事物的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰
  • 持久性(durabilty):持久性是指一个事务一旦被提交,它对数据库中数据的改变是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。
    • 持久性是通过事务日志来保证的。日志包括了重做日志和回滚日志。

事务的状态

  • 活动的(active)
    • 事务对应的数据库操作正在执行过程中
  • 部分提交的(partially committed)
    • 当事务中的最后一个操作执行完成,但由于操作都在内存中执行,所造成的影响并没有刷新到磁盘时,我们就说该事务处在部分提交的状态
  • 失败的(failed)
    • 当事务处在活动的或者部分提交的状态时,可能遇到某些错误无法继续执行,就说该事务处在失败的状态
  • 中止的(aborted)
    • 如果事务执行了一部分而变为失败的状态,那么就需要把已经修改的事务中的操作还原到事务执行前的状态
  • 提交的(committed)
    • 当一个处在部分提交的状态的事务将修改过的数据同步到磁盘上之后,就说该事务处在了提交的状态

如何使用事务

事务两种方式

  1. 显式事务
  2. 隐式事务

显式事务

  1. START TRANSACTION 或者 BEGIN,作用是显式开启一个事务
    • START TRANSACTION 语句相较于BEGIN 特别之处在于,后边能跟随几个 修饰符:
      • READ ONLY:标识当前事务是一个只读事务,也就是属于该事务的数据库操作只能读取数据,而不能修改数据
      • READ WRITE:标识当前事务是一个读写事务,也就是属于该事务的数据库操作既可以读取数据,也可以修改数据
      • WITH CONSISTENT SNAPSHOT:启动一致性读
  2. 一系列食物中的操作(主要式DML,不含DDL)
  3. 提交事务或中止事务(即回滚事务)

SAVEPOINT

#在事务中创建保存点,方便后续指针针对保存点进行回滚。一个事务中可以存在多个保存点
SAVEPINT 保存点名称;

#删除某个保存点
RELEASE SAVEPOINT 保存点名称;

completion_type

  • completion_type参数的作用
    1. completion=0,这是默认情况。当我们执行COMMIT的时候会提交事务,在执行下一个事务时,还需要使用START TRANSACTION 或者 BEGIN 开启。
    2. completion=1,在这种情况下,当我们提交事务后,相当于执行了 COMMIT AND CHAIN,也就是开启一个链式事务,即当我们提交事务之后会开启一个相同隔离级别的事务
    3. completion=2,在这种情况下 COMMIT = COMMIT AND RELEASE,也就是当我们提交后,会自动与服务器断开连接。

隐式事务

  • MySQL中有一个系统变量 autocommit;
SHOW VARIABLES LIKE 'autocommit';
  • 如果想关闭这种自动提交的功能,可以使用下边两种方法之一:

    • 显式的使用START TRANSACTION 或者 BEGIN 语句开启一个事务。这样在本次事务提交或者回滚前会暂时关闭掉自动提交的功能
    • 把系统变量 autocommit 的值设置为 OFF
    SET autocommit = OFF;
    #或
    SET autocommit = 0;
    

隐式提交数据的情况

  • 数据定义语句(Data definition language,缩写为:DDL)

    • 数据库对象,指的就是数据库、表、视图、存储过程等结构。当我们使用 CREATE、ALTER、DROP 等语句去修改数据库对象式,就会隐式的提交前边语句所属于的事务
  • 隐式使用或修改mysql数据库中的表

    • 使用 ALTER USER、CREATE USER、DROP USER、GRANT、RENAME USER、REVOKE、SET PASSWORD 等语句时也会隐式的提交前边语句所属于的事务
  • 事务控制或关于锁定的语句

    1. 当我们在一个事务还没提交或者回滚时就又使用 START TRANSACTION 或者 BEGIN 语句开启了另一个事务时,会隐式的提交上一个事务。
    2. 当前的autocommit系统变量的值为OFF,手动调整为ON时,也会隐式的提交前边语句所属的事务
    3. 使用 LOCK TABLES、UNLOCK TABLES 等关于锁定的语句也会隐式的提交 前边语句所属的事务
  • 加载数据的语句

    • 使用 LOAD DATA 语句来批量往数据库中导入数据时,也会隐式的提交前边语句所属的事务
  • 关于MySQL复制的一些语句

  • 其它的一些语句

事务隔离级别

  • MySQL是一个客户端/服务器架构的软件,对于同一个服务器来说,可以有若干个客户端与之连接,每个客户端与服务器连接上之后,就可以称为一个会话,每个会话能向服务器发出请求语句,一个请求语句可能是某个事务的一部分,也就是对于服务器来说可能同时处理多个事务。
  • 事务具有隔离性特性,理论上在某个事务对某个数据进行访问时,其他事务应该进行排队,当该事务提交之后,其他事务才可以继续访问这个数据。但对性能影响太大,即想保持事务的隔离性,又想让服务器在处理访问同一数据的多个事务时性能尽量高些,才有了事务隔离级别。

数据并发问题

  • 在并发场景下可能出现的问题
    1. 脏写(Dirty Write):对于两个事务Session A、Session B,如果事务Session A 修改了另一个未提交事务Session B修改过的数据,那就发生了脏写
    2. 脏读 (Dirty Read):对于两个事务Session A、Session B,Session A 读取了已经被Session B 更新 但还没有被提交的字段。之后若Session B 回滚,Session A 读取的内容就是临时且无效的
    3. 不可重复读(Non-Repeatable Read):对于两个事务 Session A、Session B,Session A 读取了一个字段,然后 Session B 更新了该字段。之后 Session A 再次读取同一个字段,值就不同了。那就意味着发生了不可重复读
    4. 幻读(Phantom):对于两个事务Session A、Session B,Session A从一个表中读取了一个字段,然后Session B 在该表中插入了一些新的行。之后,如果Session A 再次读取 同一个表,就会多出几行。那就意味发生了幻读

SQL中的四种隔离级别

脏写 > 脏读 > 不可重复读 > 幻读
  • SQL标准设立了4个隔离级别
    • READ UNCOMMITTED:读未提交,在该隔离级别,所有事物都可以看到其他未提交事务的执行结果。不能避免脏读、不可重复读、幻读
    • READ COMMITEED:读已提交,它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。大多数数据库系统的默认隔离级别(MySQL不是)。可以避免脏读,但不可重复读、幻读问题仍然存在
    • REPEATABLE READ:可重复读,事务 A 在读到一条数据之后,此时事务 B 对该数据进行了修改并提交,那么事务 A 再读该数据,读到的还是原来的内容
    • SERIALIZABLE:可串行化,确保事务可以从一个表中读取相同的行。在这个事务持续期间,禁止其他事务对该表执行插入、更新和删除操作
  • SQL标准规定,针对不同的隔离级别,并发事务可以发生不同严重程度的问题
隔离级别脏读可能性不可重复读可能性幻读可能性加锁读
READ UNCONMITEDYesYesYesNo
READ COMMITEDNoYesYesNo
REPEATABLE READNoNoYesNo
SERIALIZABLENoNoNoYes
  • 脏写问题严重,哪个隔离级别都不允许

  • 随着隔离级别的增高,并发性能降低

MySQL支持的四种隔离级别

  • MysQL的默认隔离级别为REPEATABLE READ,可以手动修改一下事务的隔离级别
#查看隔离级别,MySQL 5.7.20的版本之前
SHOW VARIABLES LIKE 'tx_isolation';

#MySQL 5.7.20 版本之后,引入transaction_isolation来替换tx_isolation
SHOW VARIABLES LIKE 'transaction_isolation';

SELECT @@transaction_isolation

如何设置事务的隔离级别

SET [GLOBAL | SESSION ] TRANSACTION ISOLATION LEVEL 隔离级别;
## 或者
SET [ GLOBAL | SESSION ] TRANSACTION_ISOLATION = '隔离级别';
## 隔离级别
READ UNCOMMITTED
READ COMMITTED
REPEATABLE READ
SERIALIZABLE
  • 关于使用GLOBAL 或 SESSION 的影响
    • GLOBAL:
      • 当前已经存在的会话无效
      • 只对执行完该语句之后产生的会话起作用
    • SESSION
      • 对当前会话的所有后续的事务有效
      • 如果在事务之间执行,则对后续的事务有效
      • 该语句可以在已经开启的事务中间执行,但不会影响当前正在执行的事务
  • 小结:
    • 数据库规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱

事务的分类

  1. 扁平事务:

    • 扁平事务是事务类型中最简单的一种,但是在实际生产环境中,这可能是使用最频繁的事务,在扁平事务中,所有操作都处于同一层次,其由BEGIN WORK开始,由COMMIT WORK 或 ROLLBACK WORK 结束,其间的操作是原子的,要么都执行,要么都回滚,因此,扁平事务是应用程序称成为原子操作的基本组成模块。
    • 扁平事务一般有三种不同的结果:
      1. 事务成功完成。在平常应用中约占所有事务的96%
      2. 应用程序要求停止事务。比如应用程序在捕获到异常时会回滚事务,约占事务的3%
      3. 外界因素强制终止事务,如连接超时或连接断开,约占所有事务的1%
  2. 带有保存点的扁平事务

    • 除了支持扁平事务支持的操作外,还允许在事务执行过程中回滚到同一事务中较早的一个状态。

    • 保存点(Savepoint)用来通知事务系统应该记住事务当前的状态,以便当之后发生错误时,事务能回到保存点当时的状态。

  3. 链事务

    • 链事务是指一个事务由多个子事务链式组成,它可以被视为保存点模式的一个变种。带有保存点的扁平事务,当发生系统奔溃时,所有的保存点都将消失,这意味着当进行恢复时,事务需要从开始处重新执行,而不能从最近的一个保存点继续执行。
    • 链事务的思想:在提交一个事务时,释放不需要的数据对象,将必要的处理上下文隐式地传给下一个要开始地事务,前一个子事务的提交操作和下一个子事务的开始操作合并成一个原子操作,这意味着下一个事务将看到上一个事务的结果,就好像在一个事务中进行一样。这样,在提交子事务时就可以释放不需要的数据对象,而不必等到整个事务完成后才释放。
    • 链事务与带有保存点的扁平事务的不同之处
      1. 带有保存点的扁平事务能回滚到任意正确的保存点,而链事务中的回滚仅限于当前事务,即只能恢复到最近的一个保存点
      2. 对于锁的处理,两者也不相同,链事务在执行 COMMIT 后即释放了当前所持有的锁,而带有保存点的扁平事务不影响迄今为止所持有的锁
  4. 嵌套事务

    • 嵌套事务是一个层次结构框架,由一个顶层事务(Top-Level Transaction) 控制着各个层次的事务,顶层事务之下嵌套的事务被称为子事务(Subtransaction),其控制着每一个局部的变换,子事务本身也可以是嵌套事务。因此,嵌套事务的层次结构可以看成是一棵树。
  5. 分布式事务

    • 分布式事务通常是在一个分布式环境下运行的扁平事务,因此,需要根据数据所在位置访问网络中不同节点的数据库资源。例如,一个银行用户从招商银行的账户向工商银行的账户转账1000元,这里需要用到分布式事务,因此不饿能仅调用某一家银行的数据库就完成任务。

MySQL事务日志

  • 事务的四种特性:原子性、一致性、隔离性和持久性。基于哪些机制实现的
    • 事务的隔离性由锁机制实现
    • 事务的原子性、一致性和持久性由事务的 redo 日志和 undo 日志来保证
      • REDO LOG 称为重做日志,提供再写入操作,恢复提交事务修改的页操作,用来保证事务的持久性
      • UNDO LOG 称为回滚日志,回滚行记录到某个特定版本,用来保证事务的原子性、一致性

redo 日志

  • 每次提交事务之前,利用 redo 日志将事务的更改保存到磁盘中,防止系统宕机时造成的数据消失

redo日志的好处、特点

  1. 好处
    • redo日志降低了刷盘频率
    • redo日志占用的空间非常小
  2. 特点
    • redo日志是顺序写入磁盘的
    • 事务执行过程中,redo log 不断记录

redo的组成

  • Redo log 可以简单分为以下两个部分:
    • redo log buffer
      • 重做日志的缓冲 (redo log buffer),保存在内存中,是易失的。
      • 参数设置:innodb_log_buffer_size
        • 大小默认是 16M,最大值是 4096M,最小值为 1M
    • redo log file
      • 重做日志文件(redo log file),保存在硬盘中,是持久的

redo的整体流程

  1. 先将原始数据从磁盘中读入内存中来,修改数据的内存拷贝
  2. 生成一条重做日志并写入 redo log buffer,记录的是数据被修改后的值
  3. 当事务 commit 时,将 redo log buffer 中的内容刷新到 redo log file,对 redo log file 采用追加写的方式
  4. 定期将内存中修改的数据刷新到磁盘中
  • 体会
    • Write-Ahead Log(预先日志持久化):在持久化一个数据页之前,先将内存中相应的日志页持久化

redo log的刷盘策略

  • redo log buffer 刷盘到 redo log file 的过程并不是真正的刷到磁盘中去,只是刷入到 文件系统缓存(page cache)中去(现代操作系统为了提高文件写入效率做的优化)。那么对于InnoDb来说就存在一个问题,如果交给系统来同步,同样如果系统宕机,那么数据也丢失了
  • 针对这种情况,InnoDB 给出 innodb_flush_log_at_trx_commit 参数,该参数控制 commit 提交事务时,如何将 redo log buffer 中的日志刷新到 redo log file 中。支持三种策略
    • 设置为 0:表示每次事务提交时不进行刷盘操作。(系统默认 master thread 每隔 1s 进行一次重做日志的同步)
    • 设置为 1:表示每次事务提交时都将进行同步,刷盘操作(默认值)
    • 设置为 2:表示每次事务提交时都只会把 redo log buffer 内容写入 page cache,不进行同步。由 os 自己决定什么时候同步到磁盘文件

写入 redo log buffer 过程

  1. 补充概念:Mini - Transaction
    • 一个事务可以包含若干条语句,每一条语句其实是由若干个 mtr 组成,每一个 mtr 又可以包含若干条 redo 日志。
  2. redo 日志 写入 log buffer
    • 不同的事务 可能是 并发 执行的,所以 T1、T2之间的 mtr 可能是交替执行的
  3. redo log block的结构
    • 总共是 512B
      • log block header:12B
        • LOG_BLOCK_HDR_NO:4B
        • LOG_BLOCK_HDR_DATA_LEN:2B
        • LOG_BLOCK:FIRST_REC_GROUP:2B
        • LOG_BLOCK_CHECKPOINT_NO:4B
      • lob block body:496B
      • log block trailer:4B
        • LOB_BLOCK_CHECKSUM:4B

redo log file

  1. 相关参数
    • innodb_log_group_home_dir:指定 redo log 文件组所在的路径,默认为./,表示在数据库的数据目录下。MySQL的默认数据目录 (var/lib/mysql)下默认由两个名为 ib_logfile0 和 ib_logfile1 的文件,log buffer中的日志默认情况下就是刷新到这两个磁盘文件中。此 redo 日志文件位置还可以修改
    • innodb_log_files_in_group:指明 redo log file 的个数,命名方式如:ib_logfile0,bilogfile1...iblogfilen。默认2个,最大100个
    • innodb_flush_log_at_trx_commit:控制 redo log 刷新到磁盘的策略,默认为1
    • innodb_log_file_size:单个 redo log 文件设置大小,默认值为 48M。最大值为512G,注意最大值指的是整个 redo log 系列文件之和,即 (innodb_log_files_in_group * innodb_log_file_size)不能大于最大值 512G

日志文件组

  • 总共的 redo 日志文件大小是:innodb_log_file_size * innodb_log_files_in_group。采用循环使用的方式向 redo 日志文件组里写数据的话,会导致后写入的 redo 日志覆盖掉前边写的 redo 日志。因此,提出用 checkpoint 的概念
  1. checkpoint
    • 如果 write pos 追上 checkpoint,表示日志文件组满了,这时候不能再写入新的 redo log记录,MySQL得停下来,清空一些记录,把 checkpoint 推进一下

Undo日志

  • redo log 是事务持久性的保证, undo log 是事务原子性的保证。在事务中 更新数据 的前置操作 其实是要 先写入一个 undo log

Undo 日志作用

  • 作用1:回滚数据

    • 事务需要保证原子性,也就是事务中的操作要么全部完成,要么什么也不做。但有时候事务执行到一半会出现一些清空,比如:

      1. 事务执行过程中可能遇到各种错误,比如 服务器本身的错误,操作系统错误,甚至是突然断电导致的错误
      2. 程序员可以在事务执行过程中手动输入 ROLLBACK 语句结束当前事务的执行
    • 以上情况出现,需要把数据改回原先的样子,这个过程称之为回滚。符合了原子性要求

  • 作用2:MVCC

undo的存储结构

  1. 回滚段与undo页
    • InnoDB 对 undo log 的管理采用段的方式,也就是回滚段 (rollback segment)。每个回滚段记录了 1024个 undo log segment,而在每个 undo log segment 段中进行 undo页 的申请
      • InnoDB支持最大 128个rollback segment,一个 rollback segment同时支持在线的事务限制为1024,故其支持同时在线的事务限制提高到了 128*1024.
  2. 回滚段与事务
    1. 每个事务只会使用一个回滚段,一个回滚段在同一时刻可能会服务于多个事务
    2. 当一个事务开始的时候,会制定一个回滚段,在事务进行的过程中,当数据被修改时,原始的数据会被复制到回滚段
    3. 在回滚段中,事务会不断填充盘区,直到事务结束或所有的空间被用完。如果当前的盘区不够用,事务会在段中请求扩展下一个盘区,如果所有已分配的盘区都被用完,事务会覆盖最初的盘区或者在回滚段允许的情况下扩展新的盘区来使用
    4. 回滚段存在于 undo 表空间中,在数据库中可以存在多个 undo 表空间,但同一时刻只能使用一个 undo 表空间
    5. 当事务提交时,InnoDB存储引擎会做以下两件事情:
      • 将 undo log 放入列表中,以供之后的purge操作
      • 判断 undo log 所在的页 是否可以重用,若可以分配给下个事务使用
  3. 回滚段中的数据分类
    1. 未提交的回滚数据(uncommitted undo information)
    2. 已经提交但未过期的回滚数据(committed undo information)
    3. 事务已经提交并过期的数据(expired undo information)

undo的类型

  1. insert undo log
  2. update undo log

undo log的删除

  • 针对于 insert undo log
    • 因为insert操作的记录,只对事务本身可见,对其他事务不可见。故该undo log可以在事务提交后直接删除,不需要进行 purge操作
  • 针对于 update undo log
    • 该undo log可能需要提供 MVCC 机制,因此不能在事务提交时就进行删除。提交时放入 undo log 链表,等待 purge线程进行最后的删除

小结

  • undo log是逻辑日志,对事物回滚时,只是将数据库逻辑地恢复到原来的样子
  • redo log是物理日志,记录的是数据页的物理变化,undo log不是redo log的逆过程