MySQL MVCC

111 阅读4分钟

事务并发执行时遇到的一致性问题

  • 脏写(Dirty Write)
    • 如果一个事务修改了另一个未提交事务修改过的数据,就意味着发生了脏写现象
  • 脏读(Dirty Read)
    • 如果一个事务读到了另一个为提交事务修改过的数据,就意味着发生了脏读现象
  • 不可重复读(Non-Repeatable Read)
    • 如果一个事务修改了另一个未提交事务读取的数据,就意味着发生了不可重复读现象
  • 幻读(Phantom)
    • 如果一个事务先根据某些搜索条件查询出一些记录,在该记录未提交时,另一个事务写入一些了符合那些搜索条件的记录,就意味着发生了幻读现象

SQL中的四种隔离级别

  • 读未提交(Read Uncommitted)
    • 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
  • 读已提交(Read Committed)
    • 只能读取已经提交的数据。可以阻止脏读,但幻读或不可重复读仍有可能发生。
  • 可重复读(Repeatable Read)
    • 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改。可以阻止脏读和不可重复读,但幻读仍有可能发生。
  • 串行化(Serializable)
    • 最高的隔离级别,事务只能隔离地执行,串行化顺序执行,可以防止脏读、不可重复读以及幻读。也因此效率较低。
隔离级别脏读不可重复读幻读
读未提交(Read Uncommitted)可能可能可能
读已提交(Read Committed)不可能可能可能
可重复读(Repeatable Read)不可能不可能可能
串行化(Serializable)不可能不可能不可能

MVCC原理

版本链

对于InnoDB存储引擎,它的聚簇索引记录中包含以下两个隐藏列:

  • trx_id
    • 一个事务每次对某条聚簇索引进行改动时,都会把该事务的事务id赋值给trx_id隐藏列
  • roll_pointer
    • 每次对某条聚簇索引记录进行改动时,都会把旧版本写入到undo日志中。这个隐藏列就相当于一个指针,可以通过它找到该记录修改前的版本

每条undo日志也都有一个roll_pointer属性,通过这个属性可以将这些undo日志串成一个链表;在每次更新该记录后,都会将旧值放到一条undo日志中,随着更新次数的增多,所有的版本都会被roll_pointer属性连接成一个链表,这个链表被称为版本链;我们会利用这个记录的版本链来控制并发事务访问相同记录时的行为,我们把这种机制称之为多版本并发控制(mvcc)

ReadView(一致性视图)

ReadView中主要包含四个内容:

  • m_ids:在生成ReadView时,当前系统中活跃的读写事务的事务id列表
  • min_trx_id:在生成ReadView时,当前系统中活跃的读写事务中最小的事务id;也就是m_ids中的最小值
  • max_trx_id:在生成ReadView时,系统应该分配给下一个事务的事务id值
  • creator_trx_id:生成该ReadView的事务的事务id

有了这个ReadView后,在访问某条记录时,只需要按照下面的步骤来判断记录的某个版本是否可见

  • 如果被访问版本的trx_id属性值与ReadView中的creator_trx_id值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问
  • 如果被访问版本的trx_id属性值小于ReadView中的min_trx_id值,表明生成该版本的事务在当前事务生成ReadView已经提交,所以该版本可以被当前事务访问
  • 如果被访问版本的trx_id属性值大于或等于ReadView中的max_trx_id值,表明生成该版本的事务在当前事务生成ReadView后才开启,所以该版本不可以被当前事务访问
  • 如果被访问版本的trx_id属性值在ReadView的min_trx_id和max_trx_id之间,则需要判断trx_id属性值是否在m_ids列表中。如果在,说明创建ReadView时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在则说明创建ReadView时生成该版本的事务已经被提交,该版本可以被访问

读未提交(Read Uncommitted)

对于使用Read Uncommitted隔离级别的事务来说,由于可以读到未提交事务修改过的记录,所以直接读取记录的最新版本就好

读已提交(Read Committed)

每次读取数据前都生成一个ReadView

可重复读(Repeatable Read)

在第一次读取数据时生成一个ReadView

串行化(Serializable)

对于使用Serializable隔离级别的事务来说,使用加锁的方式来访问记录