一、mvcc:
- 多版本并发控制:使用多个版本来控制事务并发
- 一种能够达到不加锁提高并发读写效率的方法
二、并发场景:
- 问题:
- 读 - 读:无问题
- 读 - 写:出现并发问题,可能会出现脏读、幻读、不可重复读等问题
- 写 - 写:出现并发问题,可能会出现更新丢失问题
- 使用mvcc可以解决以上问题
三、有无MVCC的区别:
-
MVCC
- 两个事务并发执行,一个写,一个读;读事务直接使用MVCC进行快照读,获取的是版本链中的可见版本数据;写事务则是是使用当前读,获取最新数据进行更新,二者不冲突,因此读写同一数据可并发。
- 读:快照读
- 读取,某一个版本的数据
-
无MVCC
- 读写会使用锁🔐,互相冲突
- select lock in share mode(共享锁)
- select for update(排他锁)
- update(排他锁)
- insert(排他锁)
- delete(排他锁)
- 读:当前读
- 读取,最新数据
- 读写会使用锁🔐,互相冲突
四、MVCC的实现:
- Springboot--事务详解
- 回顾事务详解:事务特征中的隔离性就是通过当前读和快照读实现
- 隔离性的隔离级别:其中读已提交(RC)和可重复读(RR)则是由MVCC实现
- MVCC的实现原理:
- 基于undo log版本链、readview实现。
- undo log版本链:
- 某条数据的多个版本log,通过log中roll_pointer字段进行连接,形成一条链条结构式且多版本的日志版本链;
- 其中日志格式中主要两个字段trx_id和roll_pointer,前者记录当前事务id,后者记录前一个版本的日志数据(也就是实际的回滚日志数据)
- readview:
- 定义:
- 一个存储事务id的列表。
- 功能:
- 用来判断当前事务应该读取哪个版本或者是否生成一个新版本
- 生成时机:
- RC隔离级别:每次读取数据前,都生成一个readview;
- RR隔离级别:在第一次读取数据前,生成一个readview;
- readview中四个关键字段:
- m_ids:记录当前活跃(未提交)的事务id集合;
- min_trx_id:m_ids(活跃)里的最小事务id。
- max_trx_id:已创建(不活跃)最大事务id。
- creator_trx_id:当前本身事务id。
- 定义:
- 小提示:
- insert操作不会存在版本链,事务提交,undo log就会删除insert操作日志;
- update和delete操作会存在版本链,detele操作并非数据真实删除,而是对flag字段赋值为true;
- select不会形成版本链,但会使用到版本链;
- undo log版本链:
-
RC使用场景:
-
RC隔离级别:每次读取数据前,都生成一个readview;
-
创建版本链节点:
-
此时版本链所有版本数据[1,2,3,4],当前事务A(id:5)读取一行数据,生成一条readview数据,会读取readview中m_ids[3,4],去查看是否存在id:5的活跃事务。不存在,则通过max_trx_id(最新不活跃事务id:2),在版本链中找到版本数据进行拷贝,并在版本链最后插入一条版本链节点(trx_id为5,数据则是从trx_id为2的数据中拷贝),且将5放入m_ids活跃列表中;
-
max_trx_id是怎么定的值:mysql会对比版本链所有版本数据[1,2,3,4],和readview中m_ids活跃列表数据[3,4],说明[3,4]是未提交的事务,[1,2]是已提交事务,则最新已提交的事务就是max_trx_id值;
-
-
-
使用版本链节点:
-
当前事务A(id:5)继续操作查询,则直接读取版本链中trx_id为5的版本数据。且隔离其他未提交的事务,避免了脏读,实现了读已提交;
-
-
-
RR使用场景:
-
RR隔离级别:在第一次读取数据前,生成一个readview,后续都直接使用当前readview。;
-
创建版本链节点:
-
此时版本链所有版本数据[1,2,3,4],当前事务A(id:5),在活跃列表中查询有事务[3,4],且不存在当前事务,则会根据最新不活跃事务id:2去创建一个版本链节点使用。
-
-
使用版本链节点:
-
假设后面活跃事务3进行了事务提交,且当前事务A(id:5)后续继续操作查询,因为RR的原因,会直接使用最开始的readview,且此readview中事务3还是活跃事务。避免了脏读,不可重复读的问题,可能也解决了幻读问题,实现了可重复读;
-
-
上述提到可能解决了幻读问题:
-
快照读:默认隔离级别RR,使用mvcc,则解决了幻读问题
-
当前读:默认隔离级别RR,使用锁,则解决不了幻读问题
-
当前事务后续在mvcc中途执行了更新操作,就会使用当前读,导致后续的再使用查询时,获取的数据会是在其他并发已提交事务修改的数据上进行更新后的数据,会存在出入,造成了幻读问题;
-
总结:
-
事务到提交过程中,只操作查询,则使用mvcc快照读,可解决幻读问题;
-
事务到提交过程中,只操作查询,会使用mvcc快照读,一旦执行了更新操作,则会使用当前读,则解决不了幻读问题,且称为伪mvcc;
-
-
-
- 基于undo log版本链、readview实现。
五、undo log、redo log、binlog日志:
- undo log:
- 记录逻辑变化日志:当前操作的反向还原操作;
- 例如:
- 执行一条update的sql,undo log则会记录一条相反update日志;
- 执行一条insert的sql,undo log则会记录一条delete日志;
- 例如:
- 总结:
- 回滚:用于保证事务的原子性,事务中一旦出现异常,则会通过记录在undo log中的逻辑日志进行回滚;
- 多版本控制:配合readview和版本链,实现mvcc快照读;
- 记录逻辑变化日志:当前操作的反向还原操作;
- redo log:
- 记录物理变化日志:物理记录什么被改变;
- 例如:
- 执行update的sql,redo log则会记录某页上进行了什么修改;
- 数据会先在内存修改,并在redo log中记录,(日志)持久到磁盘;如果数据库宕机,内存数据未持久到磁盘,数据库会根据redo log来进行恢复;
- redo log使用的顺序io会比随机io速度快,同时数据体积小,恢复速度也快;
- redo log会通过buffer再落到磁盘,而日志落磁盘的时间,可由用户进行配置;
- 内存数据落磁盘后,redo log则会清除日志;
- 总结:用于保证事务的持久性,数据库一旦发生宕机,则会通过记录在redo log中的物理日志进行恢复内存,再将数据持久化到磁盘;
- 例如:
- 记录物理变化日志:物理记录什么被改变;
- bin log:
- 记录着每条对数据进行操作的sql,除查询sql;
- 例如:
- 主从数据库,从服务器可以直接通过bin log,复制主服务器的数据;
- 主从数据库,某个服务器宕机,可以通过bin log,恢复数据;
- 总结:用于数据库数据整体恢复或主从复制使用;
- 例如:
- 记录着每条对数据进行操作的sql,除查询sql;
- 注意:
- redo log在事务开始就会开始记录;bin log则在事务提交后才会记录;
- 注意:其实真正事务结束是redo log 和 bin log记录完成,才算成功;
- 只要bin log为记录完成前发生异常,redo log 和bin log都会进行回滚;
- 如果redo log记录失败,bin log记录成功,在主从服务器时,从服务器可从bin log日志中获取全部数据,但是主服务器宕机重启后,最新数据只能从redo log中恢复数据,此时没有最新数据,则会导致主从数据不一致
- redo log 和 bin log记录必须是一致的;
- 解决不一致的方式:
-
mysql通过两阶段提交保证一致;
-
阶段1:redo log持久化,InnoDB事务进入prepare状态
-
阶段2:bin log持久化,InooDB事务进入commit状态
- 每个事务binlog的末尾,会记录一个 XID event,标志着事务是否提交成功
-
- 解决不一致的方式:
- redo log 和 bin log记录必须是一致的;
- redo log在事务开始就会开始记录;bin log则在事务提交后才会记录;
MVCC是一种多版本并发控制技术,用于解决并发场景下的读写冲突,如脏读、幻读和不可重复读问题。它通过undolog和readview实现读已提交(RC)和可重复读(RR)的隔离级别。在RC中每次读取数据前生成readview,而在RR中则只在第一次读取时生成。redolog用于保证事务的持久性,binlog则用于数据库的整体恢复和主从复制。
2432

被折叠的 条评论
为什么被折叠?



