MVCC(Multi Version Concurrency Control)
MVCC(Multi Version Concurrency Control)
Mysql在读已提交和可重复读级别下保证事务隔离性,就是靠MVCC(Multi Version Concurrency Control)机制来保证的。对一行数据的读和写操作默认是不会通过加锁互斥来保证隔离性,避免了频繁加锁互斥。MVCC由undo log日志版本链和read-view机制进行比对来实现不同事务在不同版本链中查询出不同版本的数据。
在串行化隔离级别为了保证较高的隔离性是通过将所有操作加锁互斥来实现的。
Read-View机制详解
在可重复读隔离级别,当事务开启,执行任何查询SQL时都会生成当时事务的一致性视图(Read-View),这个视图在事务结束前永远不会发生变化(读已提交在每次执行查询时会重新生成)。
视图有几个区间部分组成,已提交事务、无法确定是否已提交的事务、未开始事务。在执行查询时获取最小的未提交事务ID记为min_id以及已创建的最大事务ID记max_id来描述无法确定是否已提交的事务,如果有事务ID小于min_id说明这个事务已提交,如果事务ID大于max_id说明事务未开始(当时没有这个事务)。
事务里的任何SQL查询结果需要从对应版本链里的最新数据逐条跟read-view作对比从而得到最终的快照结果。
集合undo log日志版本链,再经过以下比对规则来完成:
- 如果trx_id落在绿色部分(trx_id<min_id)表示这个版本是已提交的事务生成的,这个数据是可见的;
- 如果trx_id落在红色部分(trx_id>max_id)表示这个版本是将来启动的事务生成的,是不可见的(如果tex_id就是自己的事务是可见的);
- 如果trx_id落在黄色部分(min_id <= trx_id <= max_id)则会有两种情况:
(1).trx_id在视图数组中,表示这个版本是由还没提交的事务生成的,不可见(如果tex_id就是自己的事务是可见的);
(2).trx_id不在视图中,表示这个版本是已提交的事务生成的,可见;
假设某个查询在执行时的事务trx_id=40,此时undo log版本链中有trx_id为(10,20,30,40,50,60)的事务。其中,10,30,60事务已经提交,此时事务40当时生成的read-view如下:
min_id:20
max_id:60
trx_id<min_id | min_id <= trx_id <= max_id | trx_id>max_id |
---|---|---|
10 | 20,30,40,50,60 | 70,80 |
生成的read_view视图为 [20,40,50],30,60,根据比对规则对于trx_id=40来说,由规则1可得10可见,由规则3.2可得30、60可见。
对于delete,可以认为update的特殊情况,会将版本链上最新的数据复制一份,然后将trx_id修改成删除操作的trx_id,同时在该条记录的头信息(record header)里的deleted_flag标记位上写true,以此表示当前记录已经被删除。
在查询时按照上面的规则查到对应的记录时,如果发现delete_flag标记位为true,意味着记录已经被删除,则不返回数据。
Read-View和可见性算法原理解释
read-view和可见性算法其实就是记录了sql查询那个时刻数据库里所有提交和未提交事务的状态。
要实现RR隔离级别,事务每次执行查询操作时read-view都是使用第一次查询时生成的read-view,且后续所有的查询都是以第一次查询生成的read-view中所有事务的状态来比对数据是否可见,以此来实现每次查询都可重复的效果。
要实现RC隔离级别,事务每次执行查询操作时read-view都会按照数据库当前状态重新生成read-view,后续每一次查询都是跟数据库里当前所有最新的事务状态来比对数据可见性,以此来实现每次都能查到已提交的最新数据的效果。
注意:begin或者start transaction 命令并不是一个事务的起点,在执行到他们之后的第一个修改操作或加排它锁操作(for update)的语句时,事务才真正启动,才会像MySQL申请真正的事务ID,MySQL内部是严格按照事务的启动顺序来分配事务ID的。