隔离级别
数据库上多个事务同时执行就可能会出现:脏读、不可重复读、幻读的问题。为了解决这些问题便有了隔离级别的概念。
首先需要知道的是,隔离的越严实,执行效率就越低。所以两者之间需要一个平衡点。sql的隔离级别有:
- 读未提交:一个事务还没提交,它的更改就能够被其他事务看到。
- 读提交:一个事务提交之后,它做的更改才能够被其他事务看到。
- 可重复读:一个事务执行过程中看到的数据总是与启动时看到的数据是一致的。也就是启动时看到了一个a记录,那么就算有一个事务修改了a记录并提交,那么在这个事务当中看到的还是最初的那个记录a。
- 串行化:对于同一行记录,读加读锁,写加写锁,出现冲突就必须等待前一个事务执行完毕。
这四个隔离级别中,读未提交和串行化比较好理解,一个就是不做任何处理,一个就是使用锁进行处理。而读提交和可重复读则是比较难理解,不过两者的原因也比较接近。
读提交和可重复读隔离级别下,数据库会创建视图,访问的时候以视图的逻辑结果为准。在读提交下,这个视图是在每个sql开始执行的时候创建的;在可重复读下,这个视图是事务启动时创建的,在整个事务执行期间都是用这个视图。
事务隔离的实现
这里以可重复读为展开说,在mysql中每条记录在更新的时候都会同时记录一条回滚记录,记录上的最新值,都可以通过回滚操作得到前一个状态的值。

不同时刻启动的事务具有不同的read-view,就像上图一样,一个记录在系统中存在多个版本,这就是数据库的多版本并发控制MVCC,每个read-view都要进行回滚来得到自己可见的值。
回滚日志肯定不会一直保留的,而其删除的时机就是当系统中不存在比这个回滚日志更早的read- view的时候。换句话说,就是这个比这个回滚日志还要早的事务都提交后。
为了避免很长的回滚日志出现占用存储空间,不推荐使用长事务。
事务的启动
- 通过begin或者start transaction可以显式启动事务,配套的提交语句是commit,回滚语句是rollback。
- set autocommit=0来关闭自动提交,这样直到commit或者rollback都是事务。
在autocommit为1的情况下,用begin显式启动的事务,如果执行commit则提交事务。如果执行 commit work and chain,则是提交事务并自动启动下一个事务,这样也省去了再次执行begin语 句的开销。
MVCC
快照
在可重复隔离级别下,事务启动的时候就创建了一个快照。这个快照是基于整个库的,不过肯定不会复制整个库的数据,那么这个快照是如何实现的呢?
Innodb当中每个事务都有一个唯一的事务id,叫做transaction id,每个事务在开始的时候都要向Innodb事务系统申请,并且按照申请顺序严格递增。
并且每行数据也有多个版本,每次事务更新数据的时候,都会生成一个新的数据版本,并且把数据版本trx_id设置成更新它的事务的transaction id。
一行记录被多个事务连续更新后的状态如下图:
图中的几个虚线箭头就是undo log,因为v1,v2,v3并不是物理上真实存在的,而是每次需要的时候根据当前版本和undo log计算出来的。
按照可重复读的定义,一个事务启动的时候,能够看到所有在它之前提交的事务结果,但是这个事务执行期间,其他事务的更新对它不可见。
在实现上,innodb为每个事务构造了一个数组,用来保存事务启动瞬间,所有活跃(启动了但未提交)的事务id。数组里面最小的记为低水位,最大的id+1记为高水位。 这个视图数组和高水位就组成了当前事务的一致性视图。
这样,对于当前事务的启动瞬间来说,一个数据版本的 row trx_id,有以下几种可能:
- 如果落在绿色部分,表示这个版本是已提交的事务或者是当前事务自己生成的,这个数据是可见的;
- 如果落在红色部分,表示这个版本是由将来启动的事务生成的,是肯定不可见的;
- 如果落在黄色部分,那就包括两种情况
- 若 row trx_id 在数组中,表示这个版本是由还没提交的事务生成的,不可见;
- 若 row trx_id 不在数组中,表示这个版本是已经提交了的事务生成的,可见。
InnoDB 利用了“所有数据都有多个版本”的这个特性,实现了“秒级创建快照”的能力。
一个数据版本对于一个事务视图来说,除了自己的更新可见之外:
- 版本为提交,不可见。
- 版本已提交,但是是在视图创建之后提交的,不可见。
- 版本已提交,并且是在视图创建之前提交的,可见。
当前读
如果事务B在更新之前读k,那么读取到的值就是1,但是更新就不能再在历史版本上更新了,否则事务C的修改就会丢失,所以更新数据都是先读后写的,这个读只能读取当前的值,称为当前读。
在事务中的select默认是快照读,但是也可以加锁将其变为当前读。使用lock in share mode或者for update。
mysql> select k from t where id=1 lock in share mode;
mysql> select k from t where id=1 for update;lock in share mode加的是读锁,for update加的是写锁。
可重复读的核心就是一致性读(consistent read);而事务更新数据的时候,只能用当前读。如果当前的记录的行锁被其他事务占用的话,就需要进入锁等待。
读提交与可重复读的逻辑类似,它们的不同在于:
- 可重复读在事务开始的时候创建一致性视图,并在整个事务的过程中都使用这个视图。
- 读提交在每次执行语句前都会重新创建出一个视图。