MySQL事务隔离级别和MVCC

2022/04/18 posted in  事务

事务隔离级别

事务并发执行问题

  • 脏写:一个事务修改了另一个未提交事务修改过的数据,就是脏写
  • 脏读:一个事务读到了另一个未提交事务修改过的数据,就是脏读
  • 不可重复读:如果一个事务只能读到另一个已提交事务修改过的数据,并且其他事务每次对该数据修改并提交后,该事务都能查到最新值,这就是不可重复读
  • 幻读:如果一个事务按照一定条件查询出一些记录,之后另一个事务插入了符合该条件的记录,原有的事务再按照该条件进行查询时,能把刚事务插入的记录查询出来,这就是幻读。幻读是强调一个事务按照某个相同条件多次查询时,之后的查询读到了之前没有的数据

SQL标准中的四种隔离级别

  • READ UNCOMMITTED : 未提交读
  • READ COMMITTED : 已提交读
  • REPEATABLE READ : 可重复读
  • SERIALIZABLE : 可串行化
隔离级别 脏读 不可重复读 幻读
未提交读 ✔️ ✔️ ✔️
已提交读 ✔️ ✔️
可重复读 ✔️
可串行化

MySQL 隔离级别

MySQL 支持四种隔离级别,但是MySQL 对于 REPEATABLE READ 可以禁止幻读问题的发生。默认隔离级别是 REPEATABLE READ

设置MySQL 隔离级别

SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL level;
level 有四种选择

level: {
     REPEATABLE READ
   | READ COMMITTED
   | READ UNCOMMITTED
   | SERIALIZABLE
}
  • GLOBAL
    • 只对执行完该语句之后的会话有效
    • 当前已存在的会话无效
  • SESSION
    • 对当前会话的所有后续事务有效
    • 该语句可以在已经开启的事务中间执行,但不会影响当前正在执行的事务
    • 如果在事务之间执行,则会影响之后的事务
  • 省略
    • 只对当前会话中下一个开启的事务起作用
    • 下一个事务执行完后,又恢复到之前的隔离级别
    • 不可在已开启的事务中执行

MVCC 原理

所谓的MVCC(Multi-Version Concurrency Control ,多版本并发控制)指的就是在使用READ COMMITTD、REPEATABLE READ这两种隔离级别的事务在执行普通的SELECT操作时访问记录的版本链的过程,这样子可以使不同事务的读-写、写-读操作并发执行,从而提升系统性能

版本链

InnoDB 存储引擎中,他的聚簇索引记录包含两个必要隐藏列:trx_id,roll_pointer;

  • trx_id: 每一个事务对某条聚簇索引记录改动时,都会把该事务的事务ID赋值给 trx_id
  • roll_pointer: 每次对某条聚簇索引记录该董事,都会把旧的数据写到 undo日志 中,可以通过 roll_pointer 找到该记录修改前记录

每次对记录进行改动,都会记录一条undo日志,每条undo日志也都有一个roll_pointer属性,可以将这些undo日志都连起来,串成一个链表,这个链表就是 版本链

ReadView

对于使用READ COMMITTED和REPEATABLE READ隔离级别的事务来说,都必须保证读到已经提交了的事务修改过的记录,也就是说假如另一个事务已经修改了记录但是尚未提交,是不能直接读取最新版本的记录的,为此提出 ReadView 概念

ReadView 属性

  • m_ids :生成ReadView时,当前系统中活跃的读写事务的 事务ID 集合

  • min_trx_id :生成ReadView时,当前系统中活跃的读写事务中,最小的 事务ID

  • max_trx_id :生成ReadView时,系统应该分配给下一个事务的 事务ID

  • creator_trx_id :生成该ReadView的 事务ID

    对表中的记录做改动时(执行INSERT、DELETE、UPDATE这些语句时)才会为事务分配事务id,否则在一个只读事务中的事务id值都默认为0

事务是否可见判断准则:

  1. 如果被访问版本的 trx_id 的属性值与ReadView中 creator_trx_id 一致,意味着当前事务访问他自己修改过的值,所以该版本可以被当前事务访问
  2. 如果被访问版本的 trx_id 的属性值小于ReadView中 min_trx_id ,意味着该版本的事务在当前事务生成 ReadView 前已提交,所以该版本可以被当前事务访问
  3. 如果被访问版本的 trx_id 的属性值大于或等于ReadView中 max_trx_id ,意味着该版本的事务在当前事务生成 ReadView 之后才开启,所以该版本不可以被当前事务访问
  4. 如果被访问版本的 trx_id 属性值在 ReadViewmin_trx_idmax_trx_id 之间,那就需要判断一下 trx_id 属性值是不是在 m_ids 列表中,如果在,说明创建 ReadView 时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建 ReadView 时生成该版本的事务已经被提交,该版本可以被访问

READ COMMITTED和REPEATABLE READ所谓的生成ReadView的时机

  • READ COMMITTED —— 每次读取数据前都生成一个ReadView
  • REPEATABLE READ —— 在第一次读取数据时生成一个ReadView