目录
MVCC(Multi-Version Concurrency Control)
介绍
MySQL在实现读已提交和可重复读隔离级别的时候通过MVCC来实现。
例如上一篇介绍事务隔离级别的中可重复读。对一行数据的读和写两个操作默认是不会通过加锁互斥来保证隔离性,避免了频繁加锁互斥,而在串行化隔离级别为了保证较高的隔离性是通过将所有操作加锁互斥来实现的。
什么是MVCC 多版本并发控制
主要核心原理,利用mysql的undo日志版本链。和当前已经提交、正在进行、还未生成的事务生成一个事务快照(read-view)。通过undo日志版本链和read-view的比照,来确定哪些数据能查询到。哪些数据查询不到。
undo日志版本链
undo日志版本链是指一行数据被多个事务依次修改过后,在每个事务修改完后,Mysql会保留修改前的数据undo回滚日志,并且用两个隐藏字段trx_id和roll_pointer把这些undo日志串联起来形成一个历史记录版本链
有了版本链。我们就能知道那些事务已提交,我当前的事务id处在哪里。
read-view(一致性视图)
很多人都知道一致性视图。但有一部分同学,想不明白。以为每次生成一致性视图的时候难道就是把mysql的数据拷贝一个快照?(不瞒大家说我第一次接触的时候也有这么想过)
如果把mysql整个都拷贝一份,每次查询都要拷贝。mysql不得炸了。其实做快照并不一定要将原来的数据复制出一份新的。我只要保证后面的更新我感知不到。那不就是快照了。
mysq怎么做一致性视图的呢?
其实mysq在生成一致性视图的时候,主要是确定上面三个事务段
- 已经提交的事务
- 正在进行的事务(还没提交)
- 还未开始的事务
那么我们分别来看啊可能怎么找到这三个段?
正在进行中的事务
这个简单,mysql有地方维护正在进行的所有事务。直接就可以拿
已经提交的事务
此时很多人就说直接看undo版本链里里面最大的已提交的事务id,只要比他小就可以了啊。
其实这样是不对的。例如 90肯定比89大。但是90这个事务可能执行的非常快。提前完成了。那么undo链最大的是90,但是 比他小的89还属于未提交。
所以已提交的事务 = 比已提交最大事务id小的那部分 - 正在进行中的事务
还未开始的事务id
这个简单,只要找到最大的事务id,比他大肯定就是还是开始的事务
可重复读实现原理
首先可重复读是: 在一个事务里面,对同一条数据的读,每次数据都是一样的。别的事务修改并提交了在自己的事务里面也是读不到的。
怎么实现呢?
其实我们只要在事务开启第一次查询的时候就记录当时已提交的事务id是那些。整个事务过程中我们不去重新计算已提交的事务ids。就可以做到。查询的时候我们也只查询我们一致性视图记录的那些事务id。
上面提到:已提交的事务 = 比已提交最大事务id小的那部分 - 正在进行中的事务
读已提交实现原理
读已提交: 在一个事务里面,对同一条数据,每次读可能会不一样。 别人事务修改了并且提交我们能感知到,并把它读到。
怎么实现呢?
相对于上面是在事务开启是每次查询的时候,都去计算一下那些事务已经提交了。
计算的方式不变。
只是生成一致性视图的时机变了。变成每次查询的时候就生成
事务id= 80 | 事务id=90 | 事务id=100 |
---|---|---|
begin ; | begin ; | begin; |
select * from account where id = 1; | \ | \ |
\ | update account set balance = 100 where id =1; | \ |
\ | commit; | \ |
\ | \ | select * from account where id = 1; |
update account set balance = 200 where id =1 | \ | \ |
commit ; | \ | \ |
\ | select * from account where id = 1; | \ |
\ | \ | select * from account where id = 1; |
上面的每次select 查询 得到结果集在可重复读和读已提交的隔离级别下分别是什么?
请根据上面的计算方式思考写一下
可重复读的解析
读已提交的解析
留给爱学习的你来完成把。如果实在计算不出。那么就私信我
注意:begin/start transaction 命令并不是一个事务的起点,在执行到它们之后的第一个修改操作InnoDB表的语句,事务才真正启动,才会向mysql申请事务id,mysql内部是严格按照事务的启动顺序来分配事务id的。
总结:
MVCC机制的实现就是通过read-view机制与undo版本链比对机制,使得不同的事务会根据数据版本链对比规则读取同一条数据在版本链上的不同版本数据。
BufferPool 以及mysq整个执行过程
为什么Mysql不能直接更新磁盘上的数据而且设置这么一套复杂的机制来执行SQL了?
因为来一个请求就直接对磁盘文件进行随机读写,然后更新磁盘文件里的数据性能可能相当差。
因为磁盘随机读写的性能是非常差的,所以直接更新磁盘文件是不能让数据库抗住很高并发的。
Mysql这套机制看起来复杂,但它可以保证每个更新请求都是更新内存BufferPool,然后顺序写日志文件,同时还能保证各种异常情况下的数据一致性。
更新内存的性能是极高的,然后顺序写磁盘上的日志文件的性能也是非常高的,要远高于随机读写磁盘文件。
正是通过这套机制,才能让我们的MySQL数据库在较高配置的机器上每秒可以抗下几干的读写请求。
写在最后
阿里巴巴-菜鸟网络末端团队常年招人。欢迎有志之士。联系内推。
邮箱:jiadun.wt@alibaba-inc.com
招聘JD: https://job.alibaba.com/zhaopin/position_detail.htm?trace=qrcode_share&positionCode=GP050521