MVCC版本并发控制、bufferpool缓存机制

MVCC多版本并发控制机制

在MySQL中可重复读隔离级别下保证事务较高的隔离性,同样的sql查询语句在一个事务里多次执行查询结果相同,就算其它事务对数据有修改也不会影响当前事务sql语句的查询结果。这个就是靠MVCC机制来保证的。MySQL在读已提交和可重复读的隔离级别下实现了MVCC。

undo日志版本链与read view机制

 undo日志版本链

是指一行数据被多个事务依次修改过后,在每个事务修改完后,Mysql会保留修改前的数据undo回滚日志,并且用两个隐藏字段trx_id和roll_pointer把这些undo日志串联起来形成一个历史记录版本链

 一致性视图read-view

在可重复读隔离级别,当事务开启,执行任何查询sql(不管是不是这张表,只要是InnoDB引擎的就会这样)时会生成当前事务的一致性视图read-view,该视图在事务结束之前都不会变化(如果是读已提交隔离级别在每次执行查询sql时都会重新生成),这个视图由执行查询时所有未提交事务id数组(数组里最小的id为min_id)和已创建的最大事务id(max_id)组成,事务里的任何sql查询结果需要从对应版本链里的最新数据开始逐条跟read-view做比对从而得到最终的快照结果。

版本链比对规则:

 1. 如果 row 的 trx_id 落在绿色部分( trx_id<min_id),表示这个版本是已提交的事务生成的,这个数据是可见的;

2. 如果 row 的 trx_id 落在红色部分( trx_id>max_id ),表示这个版本是由将来启动的事务生成的,是不可见的(若row 的 trx_id 就是当前自己的事务是可见的);

3. 如果 row 的 trx_id 落在黄色部分(min_id <=trx_id<= max_id),那就包括两种情况:

a. 若 row 的 trx_id 在视图数组中,表示这个版本是由还没提交的事务生成的,不可见(若 row 的 trx_id 就是当前自己的事务是可见的);

b. 若 row 的 trx_id 不在视图数组中,表示这个版本是已经提交了的事务生成的,可见。

注意::begin/start transaction 命令并不是一个事务的起点,在执行到它们之后的第一个修改操作InnoDB表的语句,事务才真正启动,才会向mysql申请事务id,mysql内部是严格按照事务的启动顺序来分配事务id的。

所以,为什么另一个事务提交了事务本事务看不到修改?因为事务内部有个undo日志版本链,在执行第一次查询的时候会生成一个read view一致性视图,后面的每次查询都会根据undo日志版本链去查询read view一致性视图比对tx_id,查找对应的数据。

更新逻辑

假设k的初始值为1. 

面对图中事务B中的更新语句,根据可重复读,此时应该是1+1还是2+1呢?如果是查询,那么读取到的应该就是事务开启时的一致性试图,为1 ,但是在更新的语句下,事务C已经提交了事务完成了k值的更新,B再更新之后查出来的k值为3,这就跟查询时的一致性不同了,但是,当它要去更新数据的时候,就不能再在历史版本上更新了,否则事务 C 的更新就丢失了。因此,事务 B 此时的 set k=k+1 是在k = 2的基础上进行的操作。

所以这里就用到了这样一条规则:更新数据都是先读后写的,而这个读,只能读当前的值,称为“当前读”(current read)。其实,除了 update 语句外,select 语句如果加锁,也是当前读。如:

如果事务C不是马上提交,而是变成了下面的事务C':

 事务 C’的不同是,更新后并没有马上提交,在它提交前,事务 B 的更新语句先发起了。前面说过了,虽然事务 C’还没提交,但是 (1,2) 这个版本也已经生成了,并且是当前的最新版本。那么,事务 B 的更新语句会怎么处理呢?

根据两阶段锁:行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。这个就是两阶段锁协议。

也就是事务C'再更新的时候获取到了行锁,,这个锁直到事务C‘提交了才释放;而事务B是执行更新操作,需要当前读,必须读到最新版本,而且必须加锁,此时拿不到锁就只能等到事务C’的提交,才能继续当前读。

综上,事务的可重复读的核心是一致性读;而当更新数据时,只能用到当前读。如果当前记录的行锁被其他事务占用的话,就需要进入锁等待。

bufferpool缓存机制

 假设要对id=1的数据进行修改,InnoDB会在idb数据文件中找到这个数据,并把所在的整页数据都加载到内存的buffer pool缓存池中;把旧的值写入undo日志里面;把内存的buffer pool中的数据更新;写redo日志,准备提交事务;写binlog日志,准备提交事务;写入commit标记到redo日志文件里,提交事务完成,该标志为了保证事务提交后redo和Binlog数据一致。

吧commit标志写到redo日志文件之后就可以认为是提交成功了。此时修改的页可能还有bufferpool中,只有在IO随机写入磁盘后才会认为是在磁盘了,但是就算bufferpool的内容没来得及写入磁盘此时宕机了,也可以通过写好了的redo日志恢复.

为什么Mysql不能直接更新磁盘上的数据而且设置这么一套复杂的机制来执行SQL了?既然要写redo文件到磁盘,不会影响效率吗?

因为来一个请求就直接对磁盘文件进行随机读写,然后更新磁盘文件里的数据性能可能相当差。磁盘随机读写的性能是非常差的,所以直接更新磁盘文件是不能让数据库抗住很高并发的。

Mysql这套机制看起来复杂,但它可以保证每个更新请求都是更新内存BufferPool,然后顺序写日志文件,同时还能保证各种异常情况下的数据一致性。

更新内存的性能是极高的,然后顺序写磁盘上的日志文件的性能也是非常高的,要远高于随机读写磁盘文件。

正是通过这套机制,才能让我们的MySQL数据库在较高配置的机器上每秒可以抗下几干的读写请求。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值