MySQL - 基础概念篇 - 事务

MySQL - 基础概念篇 - 事务

简单来说,事务就是要保证一组数据库操作,要么全部成功,要么全部失败。在 MySQL 中,事务支持是在引擎层实现的

隔离级别

在谈隔离级别之前,你首先要知道,你隔离得越严实,效率就会越低。因此很多时候,我们都要在二者之间寻找一个平衡点。SQL 标准的事务隔离级别包括:
RN(read uncommitted)读未提交、RC 读提交(read committed)、RR 可重复读(repeatable read)和 Se 串行化(serializable )。

MySQL 数据库的四种隔离级别通过视图来实现,详解

mvcc 视图应该是只会查出已提交的内容的

MySQL 默认的隔离级别是可重复读 RR

可重复读 RR 的适用场景

假设你在管理一个个人银行账户表。一个表存了账户余额,一个表存了账单明细。到了月底你要做数据校对,也就是判断上个月的余额和当前余额的差额,是否与本月的账单明细一致。你一定希望在校对过程中,即使有用户发生了一笔新的交易,也不影响你的校对结果。这时候使用“可重复读”隔离级别就很方便。事务启动时的视图可以认为是静态的,不受其他事务更新的影响。
其实我们在进行统计的时候,比如需要进行日统计的时候,最好也开一个视图,配置可重复读的事务级别,因为在我们进行统计的过程中,也会有新的数据插入,我们当然是不希望这个新插入的数据影响我们对过去一天的统计的

选择隔离级别

我们在考虑使用事务的时候,一定要想好并指定适合当前事务的隔离级别。
使用事务并不完全是为了原子性操作(主要时回滚,还可以使用保存点),也可以提供其他的功能,比如读隔离,可用于统计期间不被新增加的数据干扰。

事务隔离的实现

在 MySQL 中,实际上每条记录在更新的时候都会同时记录一条回滚操作,即 undo log。记录上的最新值,通过回滚操作,都可以得到前一个状态的值。
假设一个值从 1 被按顺序改成了 2、3、4,在回滚日志里面就会有类似下面的记录
600
当前值是 4,但是在查询这条记录的时候,不同时刻启动的事务会有不同的 read-view。如图中看到的,在视图 A、B、C 里面,这一个记录的值分别是 1、2、4,同一条记录在系统中可以存在多个版本,就是数据库的多版本并发控制(MVCC)。对于 read-view A,要得到 1,就必须将当前值依次执行图中所有的回滚操作得到。同时你会发现,即使现在有另外一个事务正在将 4 改成 5,这个事务跟 read-view A、B、C 对应的事务是不会冲突的。
你一定会问,回滚日志总不能一直保留吧,什么时候删除呢?答案是,在不需要的时候才删除。也就是说,系统会判断,当没有事务再需要用到这些回滚日志时,回滚日志会被删除。什么时候才不需要了呢?就是当系统里没有比这个回滚日志更早的 read-view 的时候
具体怎么判断的请看 MySQL-基础概念篇-5-深入理解事务和锁
回滚日志对应着视图版本(也对应值的版本),视图版本(值的版本)对应着特定版本的事务(哪个事务更新成这个值),事务没有提交,(和重复读)事务中的一致性视图中的版本的值就需要一直存在,相应的回滚日志也需要一直存在
系统会判断,当没有事务再需要用到这些回滚日志时(undo log),回滚日志会被删除。
即,当系统里没有事务再需要读取特定版本的值的时候,到这个值的回滚日志就会删除。
系统只会保留还未提交的事务的一致性视图中用到的值的最低版本,和到这个值的这个版本的回滚日志

基于上面的说明,我们来讨论一下为什么建议你尽量不要使用长事务。长事务意味着系统里面会存在很老的事务视图。由于这些事务随时可能访问数据库里面的任何数据,所以这个事务提交之前,数据库里面它可能用到的回滚记录都必须保留,这就会导致大量占用存储空间。
避免长事务,具体到代码里就是有可能不要在一个事务里进行过多的数据库操作

在可重复读隔离级别中,表中的数据其实已经改变,需要在视图开始时创建的视图里查找某条记录时,是通过取当前最新的数据,再取视图对应的回滚段,将当前值回滚到该视图的值。
undo log 日志,实际上相当于一个 git 版本

显式启动事务语句, begin 或 start transaction。配套的提交语句是 commit,回滚语句是 rollback。

我会建议你总是使用 set autocommit=1, 通过显式语句的方式来启动事务。

多用 commit work and chain,

你可以在 information_schema 库的 innodb_trx 这个表中查询长事务,比如下面这个语句,用于查找持续时间超过 60s 的事务。

MVCC,全称 Multi-Version Concurrency Control,即多版本并发控制。
https://pdai.tech/md/db/sql-mysql/sql-mysql-mvcc.html

如何避免长事务?
避免不必要的只读事务

业务连接数据库的时候,根据业务本身的预估,通过 SET MAX_EXECUTION_TIME 命令,来控制每个语句执行的最长时间,避免单个语句意外执行太长时间。

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

当 autocommit 为开启状态时,即使没有手动 start transaction 开启事务,mysql 默认也会将用户的操作当做事务即时提交。