Scott's world.

MySQL 事务隔离笔记

Word count: 2.1kReading time: 7 min
2019/10/16 Share

MySQL 事务隔离笔记

说起事务大家都知道为什么需要有事务这一个东西

简单来说,事务就是要保证一组数据库操作,不成功便成仁即要么全部成功要么全部失败

在MySQL中事务支持是在引擎层实现的

而我们知道MySQL是支持多引擎的,但是并不是所有的引擎都支持事务,比如MyISAM引擎就不支持事务,也难怪会被InnoDB取代的原因之一

那么我们便进入文章的主题

隔离性与隔离级别

在讲事务隔离之前,我们首先要知道,事务的四大特性即ACID

  • 原子性(Atomicity)
  • 一致性(Consistency)
  • 隔离性(Isolation)
  • 持久性(Durability)

而事务隔离就是其一的隔离性

当数据库多个事务同时执行的时候,难免会出现各种各样的情况,比如脏读(Dirty read),不可重复读(non-repeatable read),幻读(phantom read)等问题

我们知道有舍就有得,在保证各个事务隔离的任务下,我们就需要舍弃部分效率即隔离度越高则效率越低

因此,两者的平衡点则是我们需要研究的方向

SQL中标准的事务隔离级别,有以下

  • 读未提交(read uncommitted)

    一个事务还没提交时,它做的变更就能被别的事务看到。

  • 读提交(read committed)

    一个事务提交之后,它做的变更才会被其他事务看到。

  • 可重复读(repeatable read)

    一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。

  • 串行化(serializable )

    对于同一行记录,我们在写锁的时候不免会出现读写锁冲突,而串行化则保证后访问的事务必须等前一个事务执行完成,才能继续执行。

接下来我们就用一个例子来理解一下这四个隔离级别造成的影响

比如某数据表T中只有一列,某一行的值为1,下面则按时间顺序执行两个事物的行为

则我们通过对比事物A的V1,V2,V3返回值分别是什么来说明

  • 读未提交

    V1的值就是2。这时候事务B虽然还没有提交,但是结果已经被A看到了。因此,V2和V3也都是2。

  • 读提交

    V1是1而V2的值是2。事务B的更新在提交后才会被A看到则V3的值也是2。

  • 可重复读

    为遵循要求:事务在执行期间看到的数据前后必须是一致的。

    V1、V2是1,V3是2。而V1和V2在事务提交之前必须保证一致

  • 串行化

    在事务B执行更新的时候会出现事务A的读锁。直到事务A提交后,事务B才可以继续执行。所以从A的角度看, V1、V2值是1,V3的值是2。

在实现上,数据库里面会创建一个视图,访问的时候以视图的逻辑结果为准。

而在不同的隔离级别下的视图时机是不同的

  • 可重复读级别下则再事务启动的时候就被创建了,整个事务期间都会用这一个视图
  • 读提交级别下是在每个SQL语句开始执行的时候创建
  • 读未提交级别下则直接返回记录的最新值,没有视图概念
  • 串行化级别下直接用锁的方式来避免并行访问

我们可以看到在不同的隔离级别下,数据库行为是有所不同的。Oracle数据库的默认隔离级别其实就是“读提交”,因此对于一些从Oracle迁移到MySQL的应用,为保证数据库隔离级别的一致,你一定要记得将MySQL的隔离级别设置为“读提交”。

每个隔离级别都有自己的使用场景,这就需要我们根据实际情况来运用

而MySQL的默认隔离级别就是可重复读,即说明可重复读的使用场景应该是最多的,在实际生活中也会经常用到

比如在你对账的时候,你肯定不希望在校对过程中频频更新的数据会影响你的校验结果,这就保证了在执行期间前后看到的数据必须一致,而事务启动时的视图也可以认为是静态的,不受其他事务更新的影响.

事务隔离的实现

说完了事务隔离的概念,那就肯定要提到事务隔离究竟是怎么实现的

这里我们就以可重复读来说明

在MySQL中,实际上每条记录在更新的时候都会同时记录一条回滚操作即记录上的最新值,通过回滚操作,都可以得到前一个状态的值。

假设一个值从1被按顺序改成了2、3、4,在回滚日志里面就会有类似下面的记录。

当前值为4,但是在查询这条记录的时候不同时刻启动的事务就会不同的read-view

就比如视图中看到ABC里面,记录的值分别为1,2,4,同一条记录在系统中可以有几个版本,而这就是数据库的多版本并发控制(MVCC)

对于read-view A,要得到1的话就必须将当前值依次执行图中所有的回滚操作,而且即使现在有另外一个事务正在将4改成5,这个事务跟read-view A、B、C对应的事务是不会冲突的。

这里我们就自然提到回滚日志的存储问题,我们在什么时候删除回滚日志呢

简单来说,就是不需要的时候才删除即系统会判断适当情况,不如当系统中没有比这个回滚日志更早的read-view的时候

关于”长事务”的问题

尽量不要使用长事务,因为这意味着系统会存在很老的事务图,且由于这些事务随时可能访问数据库里面的任何数据,所以在这个长事务提交之前,数据库里面它可能用到的回滚记录都必须保存,这就会造成大量的存储空间被占用

除了对回滚段的影响,长事务还占用锁资源,也可能拖垮整个库

事务的启动方式

MySQL的事务启动方式有以下

  • 显式启动事务语句,beginstart transaction而配套的提交语句是commit,回滚语句是rollback
  • set autocommit=0,这个命令会将这个线程的自动提交关掉。意味着如果你只执行一个select语句,这个事务就启动了,而且并不会自动提交。这个事务持续存在直到你主动执行commitrollback语句,或者断开连接。

所以我们应该总是使用set autocommit=1,通过显示语句来启动事务,不然这会导致所有查询都在事务中,这也有可能造成长事务

对于一个需要频繁使用事务的业务,虽然第二种方式每个事务在开始时都不需要主动执行一次 begin,减少了语句的交互次数。但是我们有时候会忘记关闭该命令.所以如果执行 commit work and chain,则是提交事务并自动启动下一个事务,这样也省去了再次执行begin语句的开销而且同时带来的好处是从程序开发的角度明确地知道每个语句是否处于事务中

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

1
2
> select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>60
>
  • 若隔离级别是“串行化”,则在事务B执行“将1改成2”的时候,会被锁住。直到事务A提交后,事务B才可以继续执行。所以从A的角度看, V1、V2值是1,V3的值是2。
CATALOG
  1. 1. MySQL 事务隔离笔记
    1. 1.1. 隔离性与隔离级别
    2. 1.2. 事务隔离的实现
    3. 1.3. 事务的启动方式