事务是数据库操作的基本执行单位。

事务包含四大特性

  • 原子性(Atomicity):事务中的全部操作在数据库中是不可分割的,要么全部完成,要么全部不执行。
  • 一致性(Consistency):几个并行执行的事务,其执行结果必须与按某一顺序 串行执行的结果相一致。
  • 隔离性(Isolation):事务的执行不受其他事务的干扰,事务执行的中间结果对其他事务必须是透明的。
  • 持久性(Durability):对于任意已提交事务,系统必须保证该事务对数据库的改变不被丢失,即使数据库出现故障。

语法

  1. 在 MySQL 命令行的默认设置下,事务是自动提交的,即执行了SQL 语句之后会马上执行 commit 操作,我们可以设置 set autocommit=0 来禁用当前回话的自动提交。
  2. 还可以用 begin 、start transaction 来显式的开始一个事务。
  3. commit 在默认设置下是等价于 commit work 的,表示提交事务。
  4. rollback 在默认设置下等价于 rollback work,表示事务回滚。
  5. savepoint xxx 表示定义一个保存点,在一个事务中可以有多个保存点。
  6. release savepoint xxx 表示删除一个保存点,当没有该保存点的时候执行该语句,会抛出一个异常。
  7. rollback to [savepoint] xxx 表示回滚到某个保存点。
    1. show variables like '%commit%';
    image.png ```sql — 全局修改 set global autocommit=0; show global variables like ‘autocommit’;

— 局部修改 set session autocommit=0; show global variables like ‘autocommit’;

  1. <a name="sTgoo"></a>
  2. ## 事务的生命周期
  3. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/2569471/1649770400879-810eb581-a037-4e31-b81f-c08ba94e6f13.png#clientId=u17aa929d-de3f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=517&id=ub93c8451&margin=%5Bobject%20Object%5D&name=image.png&originHeight=517&originWidth=807&originalType=binary&ratio=1&rotation=0&showTitle=false&size=229726&status=done&style=none&taskId=u235acce3-8482-4d13-bfe9-2c6678a453f&title=&width=807)
  4. <a name="B0BR8"></a>
  5. ## 隔离性与隔离级别
  6. <a name="ZNJ1U"></a>
  7. ### 基本概念
  8. SQL 标准的事务隔离级别包括:读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(serializable )。
  9. - 读未提交:指一个事务还没提交时,它做的变更就能被别的事务看到。
  10. - 读提交 :指一个事务提交之后,它做的变更才会被其他事务看到。
  11. - 可重复读:指一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。
  12. - 串行化 :指对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。
  13. 从上往下,隔离强度逐渐增强,性能逐渐变差。<br />示例:<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/2569471/1649770722525-a352a12e-2a93-4ad9-a485-5ae3ec38c714.png#clientId=u17aa929d-de3f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=733&id=udbbeaaaf&margin=%5Bobject%20Object%5D&name=image.png&originHeight=733&originWidth=620&originalType=binary&ratio=1&rotation=0&showTitle=false&size=178794&status=done&style=none&taskId=uc0c1da16-6864-4ba0-b39b-069b54c039d&title=&width=620)<br />若隔离级别是“读未提交”, 则 V1 的值就是 2。这时候事务 B 虽然还没有提交,但是结果已经被 A 看到了。因此,V2、V3 也都是 2。<br />若隔离级别是“读提交”,则 V1 是 1,V2 的值是 2。事务 B 的更新在提交后才能被 A 看到。所以, V3 的值也是 2。 <br />若隔离级别是“可重复读”,则 V1、V2 是 1,V3 是 2。之所以 V2 还是 1,遵循的就是这个要求:事务在执行期间看到的数据前后必须是一致的。 <br />若隔离级别是“串行化”,则在事务 B 执行“将 1 改成 2”的时候,会被锁住。直到事务 A 提交后,事务 B 才可以继续执行。所以从 A 的角度看, V1、V2 值是 1,V3 的值是 2。
  14. ```sql
  15. show variables like 'transaction_isolation';

image.png

隔离级别导致的问题

  1. 脏读:脏读指的是读到了其他事务未提交的数据,未提交意味着这些数据可能会回滚,也就是可能最终不会存到数据库中,也就是不存在的数据。读到了并一定最终存在的数据,这就是脏读。
  2. 可重复读:可重复读指的是在一个事务内,最开始读到的数据和事务结束前的任意时刻读到的同一批数据都是一致的。通常针对数据更新(UPDATE)操作。
  3. 不可重复读:对比可重复读,不可重复读指的是在同一事务内,不同的时刻读到的同一批数据可能是不 一样的,可能会受到其他事务的影响,比如其他事务改了这批数据并提交了。通常针对数据更新(UPDATE)操作。
  4. 幻读:幻读是针对数据插入(INSERT)操作来说的。幻读指的是一个事务在前后两次查询同一个范围的时候,后一次查询看到了前一次查询没有看到的行。

image.png

读取未提交-脏读问题

读未提交,其实就是可以读到其他事务未提交的数据,但没有办法保证你读到的数据最终一定是提交后的数据,如果中间发生回滚,那就会出现脏数据问题,读未提交没办法解决脏数据、可重复读和
幻读的问题。

读取已提交-不可重复读问题

读提交就是一个事务只能读到其他事务已经提交过的数据,也就是其他事务调用 commit 命令之后的数据,因此不存在脏读。但是事务的不同时刻同样的查询条件,查询出来的记录内容是不一样的,例如事务A的提交影响了事务B的查询结果,这就产生了不可重复读的现象。

可重复读取-幻读问题

可 重复读是指,事务不会读到其他事务对已有数据的修改,及时其他事务已提交,也就是说,事务开始时读到的已有数据是什么,在事务提交前的任意时刻,这些数据的值都是一样的。但是,对于其他事务新插入的数据是可以读到的,这也就引发了幻读问题。

事务启动的时机

begin/start transaction 命令并不是一个事务的起点,在执行到它们之后的第一个操作 InnoDB 表的语句,事务才真正启动。如果你想要马上启动一个事务,可以使用 start transaction with consistent snapshot 这个命令。

MYSQL视图

在 MySQL 里,有两个“视图”的概念:

  • 一个是 view。它是一个用查询语句定义的虚拟表,在调用的时候执行查询语句并生成结果。创建视图的语法是 create view … ,而它的查询方法与表一样。
  • 另一个是 InnoDB 在实现 MVCC 时用到的一致性读视图,即 consistent read view, 用于支持 RC(Read Committed,读提交)和 RR(Repeatable Read,可重复读)隔离级别的实现。

它没有物理结构,作用是事务执行期间用来定义“我能看到什么数据”。

事务隔离的实现

在 MySQL 中,实际上每条记录在更新的时候都会同时记录一条回滚操作。记录上的最新
值,通过回滚操作,都可以得到前一个状态的值。
假设一个值从 1 被按顺序改成了 2、3、4,在回滚日志里面就会有类似下面的记录。
image.png
当前值是 4,但是在查询这条记录的时候,不同时刻启动的事务会有不同的 read-view。
如图中看到的,在视图 A、B、C 里面,这一个记录的值分别是 1、2、4,同一条记录在系统中可以存在多个版本,就是数据库的多版本并发控制(MVCC)。对于 read-view A,要得到 1,就必须将当前值依次执行图中所有的回滚操作得到。
当没有事务再需要用到这些回滚日志时,回滚日志才会被删除。
问:为什么尽量不要使用长事务?
答:长事务意味着系统里面会存在很老的事务视图。由于这些事务随时可能访问数据库里面的任何数据,所以这个事务提交之前,数据库里面它可能用到的回滚记录都必须保留,这就会导致大量占用存储空间。除此之外,长事务会占用锁资源,导致其他事务更新失败。
InnoDB 里面每个事务有一个唯一的事务 ID,叫作 transaction id。它是在事务开始的时候向 InnoDB 的事务系统申请的,是按申请顺序严格递增的。
而每行数据也都是有多个版本的。每次事务更新数据的时候,都会生成一个新的数据版本,并且把 transaction id 赋值给这个数据版本的事务 ID,记为 row trx_id。同时,旧的数据版本要保留,并且在新的数据版本中,能够有信息可以直接拿到它。
数据表中的一行记录,其实可能有多个版本 (row),每个版本有自己的 row trx_id。

以下为一个记录被多个事务连续更新后的状态的示例图:
image.png
图中的三个虚线箭头,就是 undo log。
而 V1、V2、V3 并不是物理上真实存在的,而是每次需要的时候根据当前版本和 undo log 计算出来的。
对于可重复读隔离级别,以事务启动时刻为准,如果上一个版本是启动前的,则认为该版本是有效的,如果是在启动后生成的,则认为是无效的,继续往前查找版本,直到找到有效版本。除此之外,事务本身更新的数据,也属于有效版本。
在实现上, InnoDB 为每个事务构造了一个数组,用来保存这个事务启动瞬间,当前正在“活跃”的所有事务 ID。“活跃”指的就是,启动了但还没提交。
数组里面事务 ID 的最小值记为低水位,当前系统里面已经创建过的事务 ID 的最大值加 1 记为高水位。
这个视图数组和高水位,就组成了当前事务的一致性视图(read-view)
image.png
一个数据版本,对于一个事务视图来说,除了自己的更新总是可见以外,有三种情况:

  1. 版本未提交,不可见;
  2. 版本已提交,但是是在视图创建后提交的,不可见;
  3. 版本已提交,而且是在视图创建前提交的,可见。
  4. 更新数据都是先读后写的,而这个读,只能读当前的值,称为“当前读”(current read)

    事务的可重复读的能力是怎么实现的?

    可重复读的核心就是一致性读(consistent read);而事务更新数据的时候,只能用当前读。如果当前的记录的行锁被其他事务占用的话,就需要进入锁等待。
    读提交和可重复读区别是:
  • 在可重复读隔离级别下,只需要在事务开始的时候创建一致性视图,之后事务里的其他查询都共用这个一致性视图;
  • 在读提交隔离级别下,每一个语句执行前都会重新算出一个新的视图。

    为什么表结构不支持“可重复读”?

    这是因为表结构没有对应的行数据,也没有 row trx_id,因此只能遵循当前读的逻辑。

    事务与IO的关系

    image.png
  1. innodb_flush_log_at_trx_commit=0,innodb中的log thread每隔一秒钟将会log buffer中的数据写入文件,同时还会通知文件系统进行与文件同步的flush操作,保证数据确实已经写入磁盘。但是,每次事 务的结束(commit或者rollback)并不会触发log thread将log buffer中的数据写入文件。所以当设置为0时候,在mysql crash或者oscrash或者主机断电的情况,最极端的情况是丢失一秒的数据变更。
  2. nnodb_flush_log_at_trx_commit=1,这也是innodb默认设置,每次事务的结束都会触发log thread将log buffer中的数据写入文件,并通知文件系统同步文件。这个设置是最安全的,能够保证不论是mysql crash,os crash还是主机断电都不会丢失任何已经提交的事务。
  3. innodb_flush_log_at_trx_commit=2,log thread会在每次事务结束后将数据写入事务日志,但是仅仅是调用了文件系统的写入操作,而文件系统都是有缓存的,所以log thread的写入并不能保证将文件系统中缓存写入到物理磁盘进行永久固化。