事务

隔离级别

当数据库上有多个事务同时执行的时候,就可能出现脏读(dirty read)、不可重复读(non-repeatable read)、幻读(phantom read)的问题,为了解决这些问题,就有了“隔离级别”的概念。

SQL标准的事务隔离级别包括:读未提交(read uncommitted)读提交(read committed)可重复读(repeatable read)串行化(serializable )

  • 读未提交:一个事务还没提交时,它做的变更就能被别的事务看到。
  • 读提交:一个事务提交之后,它做的变更才会被其他事务看到。(在执行语句之前创建多版本视图)
  • 可重复读:一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。(在开启事务之前创建多版本视图)
  • 串行化:顾名思义是对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。

通过show variables like 'transaction_isolation';查看隔离级别

还可以通过select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>60查看超过60s的长事务(长事务不仅会让回滚日志很长,还会阻塞mysql)

MVCC及一致性读视图

MVCC(Multi-Version Concurrency Control):多版本并发控制;InnoDB在实现MVCC时用到的一致性读视图,即consistent read view,用于支持RC(Read Committed,读提交)和RR(Repeatable Read,可重复读)隔离级别的实现。

具体实现:

  • InnoDB里面每个事务有一个唯一的事务ID,叫作transaction id。它是在事务开始的时候向InnoDB的事务系统申请的,是按申请顺序严格递增的。而每行数据也都是有多个版本的。

    image-20230416144520269

    实际上,图中的三个虚线箭头,就是undo log;而V1、V2、V3并不是物理上真实存在的,而是每次需要的时候根据当前版本和undo log计算出来的。比如,需要V2的时候,就是通过V4依次执行U3、U2算出来。

  • 当前事务的一致性视图(read-view)

    InnoDB为每个事务构造了一个数组,用来保存这个事务启动瞬间,当前正在“活跃”的所有事务ID。数组里面事务ID的最小值记为低水位,当前系统里面已经创建过的事务ID的最大值加1记为高水位

    image-20230416144845691

    对于当前事务的启动瞬间来说,一个数据版本的row trx_id,有以下几种可能:

    1. 如果落在绿色部分:

      表示这个版本是已提交的事务或者是当前事务自己生成的,这个数据是可见的;

    2. 如果落在红色部分:

      表示这个版本是由将来启动的事务生成的,是肯定不可见的;

    3. 如果落在黄色部分:

      那就包括两种情况:
      a. 若 row trx_id在数组中,表示这个版本是由还没提交的事务生成的,不可见;
      b. 若 row trx_id不在数组中,表示这个版本是已经提交了的事务生成的,可见。

    对于更新语句有一个原则:更新数据都是先读后写的,而这个读,只能读当前的值,称为“当前读”(current read)。所以更新不按照一致性读。(select语句加锁也是当前读)

    而读提交的逻辑和可重复读的逻辑类似,它们最主要的区别是:

    • 在可重复读隔离级别下,只需要在事务开始的时候创建一致性视图,之后事务里的其他查询都共用这个一致性视图;
    • 在读提交隔离级别下,每一个语句执行前都会重新算出一个新的视图。