用自己的电脑做网站服务器,优化网站的技巧,对网站建设有什么样好的建设意见,网络工程是冷门专业吗MVCC概述
MVCC#xff08;Multi-Version Concurrency Control#xff09;即多版本并发控制。主要是为了提高数据库的并发性能而提供的#xff0c;采用了不加锁的方式处理读-写并发冲突#xff0c;确保了任何时刻的读操作都是非阻塞的。只需要很小的开销#xff0c;就可以…MVCC概述
MVCCMulti-Version Concurrency Control即多版本并发控制。主要是为了提高数据库的并发性能而提供的采用了不加锁的方式处理读-写并发冲突确保了任何时刻的读操作都是非阻塞的。只需要很小的开销就可以实现非锁定读从而大大提高数据库系统的并发性能。所以我们也可以说MVCC是一种用来解决读-写冲突的无锁并发控制。
引入
假设我发表了一篇博客检查的时候发现了有几个错字打算去修改修改完之后要重写发布并审核。那此时对其他正在看文章的读者来说他们是不是看不到了呢答案是否定的他们依然能够看到不过看到的还是有错别字的版本即旧版本。当审核通过之后就用新版本去覆盖了旧版#本此时看到的文章就是已经修改过的新版本。
MVCC多版本并发控制
而对于InnoDB的MVCC机制来说思路也大致相同。并发读写是存在线程安全问题的有可能出现脏读、幻读、不可重复读。而MVCC的读其实是快照读快照读读取到的数据不一定是最新的又可能是历史的版本。也即让并发的事务的读写操作作用于不同的版本比如读老版本写新版本这样无论执行写操作的事务干了啥都不会影响读的事务。 需要注意的是MVCC只在RC和RR两个隔离级别下工作。如果是RU的话允许脏读的存在即一个事务可以读取到另一个事务未提交的数据自然可以读到最新的版本与MVCC冲突。如果是SERIALIZABLE所有的事务都是串行的不存在并发因此也就没有MVCC什么事了。虽然MVCC在RC和RR下工作但是他们的实现方式又是不同的。
实现原理
MVCC主要是通过隐藏字段Undo-log日志以及ReadView来实现的。
隐藏字段
数据库表中的每一行除了我们显示定义的几个字段之外还构建了一些InnoDB引擎的隐藏字段主要的有DB_ROW_ID、DB_Deleted_Bit、DB_TRX_ID、DB_ROLL_PTR。
DB_ROW_ID隐藏主键6Bytes。InnoDB存储引擎是按照主键作为聚簇索引列来构建B树存储的如果表中没有主键那么就选择一个唯一非空的字段。如果两种都没有就InnoDB会自动以DB_ROW_ID产生一个聚簇索引只不过这个索引在上层无法使用仅提供给InnoDB构建树结构存储表数据。DB_Deleted_Bit删除标识1Bytes。这里不做过多赘述。DB_TRX_ID最近修改/插入事务ID6Bytes。MySQL对于每一个创建的事务都会为其分配一个事务ID事务ID同样遵循顺序递增的特性即后来的事务ID绝对会比之前的ID要大。但是对于select查询语句其事务id0。如果是手动开启的事务无论是否是selectMySQL都会为其分配事务ID。而TRX_ID记录的就是最近一次改动当前这条数据的事务ID。DB_ROLL_PTR回滚指针7Bytes。指向这条记录的上一个版本存储于 rollback segment 里。当一个事务对一条数据做了改动后都会将旧版本的数据放到Undo-log日志中而DB_ROLL_PTR就是一个地址指针指向Undo-log日志中旧版本的数据当需要回滚事务时就可以通过这个隐藏列来找到改动之前的旧版本数据而MVCC机制也利用这点实现了行数据的多版本。
Undo-log
在InnoDB中undo日志一共有两种类型分别是Update Undo Log和Insert Undo Log。
Update Undo Log这种类型的Undo日志主要用来存储更新数据之前的原始信息其主要目标是满足在事务执行过程中的数据修改引发的回滚操作并用于在读取数据时保证数据的一致性。Insert Undo Log在插入操作中产生的日志。只在事务回滚时需要InnoDB并不需要保存完整的行数据信息它主要用于标记这条新插入的记录在事务完成之前对其他事务是不可见的。 假设有一个person表表中存储的数据如下
执行这样两个事务
start transaction;
update person set user_name 李四 where u_id 1;
commit;start transaction;
update person set sex 女 where u_id 2;
commit;其实就会出现这样一条版本链旧版本的信息存储在undo日志中新的输入的db_roll_ptr指向的只上一个版本的地址。
值得注意的是新版本数据都会插入到链表头中而不是追加到链表尾部。
update的执行过程
对ID1这条要修改的行数据加上排他锁。将原本的旧数据拷贝到Undo-log的rollback Segment区域。对表数据上的记录进行修改修改完成后将隐藏字段中的trx_id改为当前事务ID。将隐藏字段中的roll_ptr指向Undo-log中对应的旧数据并在提交事务后释放锁。
Undo-log日志要设计出版本链一方面可以实现事务回滚另一方面则可以实现MVCC机制。
ReadView
如果t2事务要查询一条行数据此时这条数据正在被他t1事务修写那么这条行数据也就可能存在多个旧版本数据t2在查询的时候应该查询哪个旧版本的数据呢此时就需要ReadView。具体来说当一个事务需要读取数据时InnoDB会创建一个ReadView实例为该事务提供一个数据的“快照”在这个快照中记录着当前所有活跃事务的ID活跃事务是指还在执行的事务即未结束提交/回滚的事务。
当一个事务启动后首次执行select操作时MVCC就会生成一个数据库当前的ReadView通常而言一个事务与一个ReadView属于一对一的关系不同隔离级别下也会存在细微差异ReadView一般包含四个核心内容
creator_trx_id当前创建这个ReadView的事务idtrx_ids在生成当前的ReadView之前系统内活跃的事务id列表up_limit_id活跃的当前事务列表中最小的事务idlow_limit_id生成当前ReadView时系统要给下一个任务分配的事务id。
看一个ReadView示意图
假设当前数据库中有t1~t5这5个事务其中活跃着的事务时t1t2t4t3几经回滚t5已经提交此时有一条select语句执行时就会生成一个ReadView没有开启事务进行select而是直接select那么就会为其分配trx_id0。所以产生快照的信息是
{creator_trx_id : 0,trx_ids : [1,2,4],up_limit_id : 1,low_limit_id : 6
}MVCC机制的实现原理
当一个事务尝试修改某条数据时会将表中的旧数据放入udno日志中当一个事务查询某条数据的时候MVCC会生成一个ReadView快照读。其中Undo-log主要实现数据的多版本ReadView则主要实现多版本的并发控制。
执行过程
当事务中出现select语句时会生成一个ReadView判断数据行中的隐藏列trx_id与ReadView.creator_trx_id是否相等。 相等表示创建ReadView快照和修改行数据修改行数据这个事务生成的就是trx_id是同一个事务那么这个事务自然可以读取到这行最新版本的数据。不相等代表目前要查询的数据是被其他事务修改过的继续下面的判断 判断数据行中的隐藏列trx_id与ReadView.up_limit_id的大小关系。 前者小于后者表示修改行数据的这个事务在创建快照前就已经完成可以读取最新版本的数据。前者大于等于后者代表改动行数据的事务还在执行继续进行判断 判断数据行中的隐藏列trx_id与ReadView.low_limit_id这个值的大小关系。 前者大于等于后者那么表示这行数据是在创建ReadView即当前事务开始之后被修改的因此不能访问最新版数据。前者小于后者表示改动行数据的事务ID在up_limit_id、low_limit_id之间需要进一步判断。 继续判断trx_id是否在trx_ids中。 在表示改动行数据的事务目前依旧在执行不能访问最新版数据。不在表示改动行数据的事务已经结束可以访问最新版的数据。
这一块我的整理还是比较抽象的建议再去看看文件末尾的参考资料再去理解和消化。
RC、RR下的MVCC
在RC隔离级别下是每个快照读都会生成并获取最新的ReadView在RR隔离级别下则是同一个事务中的第一个快照读才会创建ReadView, 之后的快照读获取的都是同一个ReadView。
参考资料
MySQL之MVCC机制为什么你改了的数据我还看不见MVCC详解深入浅出简单易懂【MySQL笔记】正确的理解MySQL的MVCC及实现原理