事务隔离级别

每个客户端与服务器连接上之后,就可以称之为一个会话(Session)。向服务器发出请求语句,一个请求语句可能是某个事务的一部分。理论上应该应该保证事务的隔离性。又想让服务器在处理访问同一数据的多个事务时性能尽量高些,只能舍一部分隔离性而取性能。

事务并发执行遇到的问题

  • 脏写(Dirty Write):一个事务修改了另一个未提交事务修改过的数据。
  • 脏读(Dirty Read):一个事务读到了另一个未提交事务修改过的数据。
  • 不可重复读(Non-Repeatable Read):一个事务读到另一个已经提交的事务修改过的数据,并且其他事务每对该数据进行一次修改并提交后,该事务都能查询得到最新值。
  • 幻读(Phantom):一个事务先根据某些条件查询出一些记录,之后另一个事务又向表中插入了符合这些条件的记录,原先的事务再次按照该条件查询时,能把另一个事务插入的记录也读出来。

注:
幻读强调的是一个事务按照某个相同条件多次读取记录时,后读取时读到了之前没有读到的记录。
如果先前已经读到的记录,之后又读取不到这种情况相当于对每一条记录都发生了不可重复读。

SQL标准中的四种事务隔离级别(并不是Mysql设计的)

针对不同隔离级别,并发事务可能会发生的问题:

  • Serializable:可串行化。
  • Repeatable read:可重复读。 存在:幻读
  • Read Committed:已提交读。 存在:不可重复读、幻读
  • Read Uncommitted:未提交读。 存在:脏读、不可重复读、幻读

注:
MySQL支持4种隔离级别,MySQL在REPEATABLE READ隔离级别下,可以禁止幻读问题的发生。

Mysql修改事务的隔离级别(针对5.8及之后)

mysql> SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL level(具体四种级别一个);

SET关键字后可以放置GLOBAL关键字、SESSION关键字的影响范围:

  • 使用GLOBAL关键字(在全局范围影响):比如:SET GLOBAL TRANSACTION ISOLATION LEVEL SERIALIZABLE; 则:
    • 只对执行完该语句之后产生的会话起作用。
    • 当前已经存在的会话无效。
  • 使用SESSION关键字(在会话范围影响):比如:SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE; 则:
    • 对当前会话的所有后续的事务有效
    • 该语句可以在已经开启的事务中间执行,但不会影响当前正在执行的事务。
    • 如果在事务之间执行,则对后续的事务有效。
  • 两个关键字都不用(只对执行语句后的下一个事务产生影响):比如:SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; 则:
    • 只对当前会话中下一个即将开启的事务有效。
    • 下一个事务执行完后,后续事务将恢复到之前的隔离级别。
    • 该语句不能在已经开启的事务中间执行,会报错。

如果在服务器启动时想改变事务的默认隔离级别,可以修改启动参数transaction-isolation的值,比如在启动服务器时指定—transaction-isolation=SERIALIZABLE,那么事务的默认隔离级别就从原来的REPEATABLE READ变成了SERIALIZABLE。

查看当前会话的隔离级别可以通过查看系统变量transaction_isolation的值来确定:
mysql> SHOW VARIABLES LIKE ‘transaction_isolation’;

MVCC原理(多发版本控制)

MMCC(Multi-Version Concurrency Control)原理
多个并发版本的数据实现并发控制,每次事务生成新版本的数据(实际就是保存了在某个时间节点的数据快照)

相关概念:

版本链

事务版本号:
每次更新事务开启前都会从数据库获得一个自增长的事务ID,可以从事务ID判断事务的执行先后顺序。

数据表的隐藏列:每行记录包含的隐形字段
对于使用InnoDB存储引擎的表来说,它的聚簇索引记录中都包含两个必要的隐藏列(row_id并不是必要的,表中有主键或者非NULL的UNIQUE键时都不会包含row_id列):

  • trx_id:每次一个事务对某条聚簇索引记录进行改动时,都会把该事务的事务id赋值给trx_id隐藏列。
  • roll_pointer:每次对某条聚簇索引记录进行改动时,都会把旧的版本写入到undo log中,然后这个隐藏列指向上一个版本数据在undo log里的指针位置。
  • (row_id: 隐藏ID 并不是必要的 ,当表中有主键或者非NULL的UNIQUE键时都不会包含row_id列 或者说:当表没有合适的索引作为聚集索引时,会用row_id创建聚集索引)

Undo log:记录不同事务版本号的数据快照
(1)保证事务进行rollback时的原子性和一致性,当事务进行回滚的时候可以用undo log的数据进行恢复。
(2)用于MVCC快照读的数据,在MVCC多版本控制中,通过读取undo log的历史版本数据可以实现不同事务版本号都拥有自己独立的快照数据版本。
MVCC.png
每次对记录进行改动,都会记录一条undo日志,每条undo日志也都有一个roll_pointer属性(INSERT操作对应的undo日志没有该属性,因为该记录并没有更早的版本),可以将这些undo日志都连起来,串成一个链表就是版本链。
image.png

ReadView

SELECT查询时生成
组成:未提交的事务id的列表 和 当前事务id 和 将分配各下一个事务的id

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

    注意max_trx_id并不是m_ids中的最大值,事务id是递增分配的。比如现在有id为1,2,3这三个事务,之后id为3的事务提交了。那么一个新的读事务在生成ReadView时,m_ids就包括1和2,min_trx_id就是1,max_trx_id的值是4。

  • creator_trx_id:表示生成该ReadView的事务的事务id。

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

MVCC的ReadView只针对”可重复度”,”读已提交”事务隔离级别起作用,两个事务隔离级别最大的区别就是它们生成ReadView的时机不同。
事务隔离级别是”读已提交“时:每一次查询数据前都生成一个ReadView
事务隔离级别是”可重复读“时:第一次查询时生成一个ReadView,同一个事务下后边在查的时候复用第一次生成的ReadView。
不同的事务隔离界别按照读取规则得到的结果不同。

查询操作时会生成或读取ReadView,并根据ReadView从Undo log日志中最新记录往下找判断记录的某个版本是否可见:

  • 如果被访问版本的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时生成该版本的事务已经被提交,该版本可以被访问。

简述读取规则:从最新的Undo log记录开始找,用日志记录中的事务id和ReadView做比较,找到已提交事务的最大id为止:
当前记录的事务id<未提交数组中最小的事务id,可读。
当前记录的事务id >= 未提交数组中最小事务id <= 事务的最大id,若在数组中,不可读,若不在数组中,可读。
当前记录的事务id>最大的事务id,不可读。

MVCC小结

所谓的MVCC(Multi-Version Concurrency Control ,多版本并发控制)指的是在使用READ COMMITTD、REPEATABLE READ这两种隔离级别的事务在执行普通的SELECT操作时访问记录的版本链的过程,这样子可以使不同事务的读-写、写-读操作并发执行,从而提升系统性能。READ COMMITTD、REPEATABLE READ这两个隔离级别的一个很大不同点是:生成ReadView的时机不同,READ COMMITTD在每次进行普通SELECT操作前都会生成一个ReadView,而REPEATABLE READ只在第一次进行普通SELECT操作前生成一个ReadView,之后的查询操作都重复使用这个ReadView。

注:MVCC只是在我们进行普通的SEELCT查询时才生效

关于purge

两件事:

  • insert undo在事务提交之后就可以被释放掉了,而update undo由于还需要支持MVCC,不能立即删除掉。
  • 为了支持MVCC,对于delete mark操作来说,仅仅是在记录上打一个删除标记,并没有真正将它删除掉。

随着系统的运行,在确定系统中包含最早产生的那个ReadView的事务不会再访问某些update undo日志以及被打了删除标记的记录后,有一个后台运行的purge线程会把它们真正的删除掉。

理解例子