MySQL进阶04-MVCC实现原理

文章目录

表记录读取的两种方式

  • 快照读:读取的是快照版本。普通的SELECT就是快照读。通过mvcc来进行并发控制的,不用加锁。
  • 当前读:读取的是最新版本。UPDATEDELETEINSERTSELECT ... LOCK IN SHARE MODESELECT ... FOR UPDATE是当前读。

注意注意注意!!!!:快照读情况下,InnoDB在RR隔离级别下通过mvcc机制避免了幻读现象,而mvcc机制无法避免当前读情况下出现的幻读现象。因为当前读每次读取的都是最新数据,这时如果两次查询中间有其它事务插入数据,就会产生幻读。

举个栗子

下面举个例子说明下:t_uesr表

事务A 事务B
start transaction start transaction
select * from t_user where user_name='a'; 假设有一条
insert into t_user(user_name, user_password, user_mail, user_state) values('aismall', 'a', 'a', 0);
commit
select * from t_user where user_name='a'; 此时就能查到两条
commit

MySQL是如何避免幻读的

在快照读情况下:MySQL通过mvcc来避免幻读。

在当前读情况下:MySQL通过next-key来避免幻读(加行锁和间隙锁来实现的),其中,next-key包括两部分,行锁和间隙锁,行锁是加在

索引上的锁,间隙锁是加在索引之间的。

Serializable隔离级别也可以避免幻读,会锁住整张表,并发性极低,一般不会使用。

bin log/redo log/undo log

MySQL日志主要包括查询日志、慢查询日志、事务日志、错误日志、二进制日志等。其中比较重要的是 bin log(二进制日志)和 redo log(重做日志)和 undo log(回滚日志)。

bin log:bin log是MySQL数据库级别的文件,记录对MySQL数据库执行修改的所有操作,不会记录select和show语句,主要用于恢复数据库和同步数据库。

redo log:redo log是innodb引擎级别,用来记录innodb存储引擎的事务日志,不管事务是否提交都会记录下来,用于数据恢复。当数据库发生故障,innoDB存储引擎会使用redo log恢复到发生故障前的时刻,以此来保证数据的完整性。将参数innodb_flush_log_at_tx_commit设置为1,那么在执行commit时会将redo log同步写到磁盘。

undo log:除了记录redo log外,当进行数据修改时还会记录undo log,undo log用于数据的撤回操作,它保留了记录修改前的内容。通过undo log可以实现事务回滚,并且可以根据undo log回溯到某个特定的版本的数据,实现MVCC。

小结:

  • bin log会记录所有日志记录,包括InnoDB、MyISAM等存储引擎的日志;redo log只记录innoDB自身的事务日志。
  • bin log只在事务提交前写入到磁盘,一个事务只写一次;而在事务进行过程,会有redo log不断写入磁盘。
  • bin log是逻辑日志,记录的是SQL语句的原始逻辑;redo log是物理日志,记录的是在某个数据页上做了什么修改。

MVCC概述

MVCC(Multiversion concurrency control) :多版本并发控制

  • 多版本:指MySQL维护着行数据的多个版本。
  • 并发控制:多个事务同时操作某一行记录时,由MySQL控制返回多个版本的行记录中的某个版本。

MVCC实现原理

MVCC实现原理主要通过下面这个三个来实现:隐藏字段,undo log 版本链,read view。

隐藏字段是什么:

  • 在undo log中每条数据的记录大概是这样的:
  • DB_TRX_ID:当前事务id,通过事务id的大小判断事务的时间顺序。
  • DB_ROLL_PTR:回滚指针,指向当前行记录的上一个版本,通过这个指针将数据的多个版本连接在一起构成undo log版本链
  • DB_ROW_ID:主键,如果数据表没有主键,InnoDB会自动生成主键。

使用事务更新行记录的时候,就会生成版本链,执行过程如下:

  • 1.用排他锁锁住该行;
  • 将该行原本的值拷贝到undo log,作为旧版本用于回滚;
  • 修改当前行的值,生成一个新版本,更新事务id,使回滚指针指向旧版本的记录,这样就形成一条版本链。

下面举个例子方便理解:

  • 1、初始数据如下,其中DB_ROW_ID和DB_ROLL_PTR为空。
  • 2、事务A对该行数据做了修改,将age修改为19,效果如下:
  • 3、之后事务B也对该行记录做了修改,将age修改为20,效果如下:
  • 4、此时undo log有两行记录,并且通过回滚指针连在一起。

read view:也可理解为数据的一份快照。上面undo log 中有这么多版本,具体要返回那个版本就是由read view来决定的,其中read view 中维护了下面这些东西:

  • trx1...trxn:当前活跃事务id的集合(未提交事务)。
  • up_limit_id:当前活跃事务的最小事务id。
  • low_limit_id:当前活跃事务的最大事务id。
  • read view创建着事务id。

    不同隔离级别创建read view的时机不同:
  • read committed:每次执行select都会创建新的read_view,保证能读取到其他事务已经提交的修改。
  • repeatable read:在一个事务范围内,第一次select时更新这个read_view,以后不会再更新,后续所有的select都是复用之前的read_view。这样可以保证事务范围内每次读取的内容都一样,即可重复读。

read view是通过一个可见性算法(对比规则)来决定要返回那个版本的,这个算法就是使用当前的事务ID(DATA_TRX_ID)跟 read view进行对比,具体对比规则如下:

  • 如果DATA_TRX_ID < up_limit_id:说明在创建read view时,修改该数据行的事务已提交,该版本的记录可被当前事务读取到。
  • 如果DATA_TRX_ID >= low_limit_id:说明当前版本的记录的事务是在创建read view之后生成的,该版本的数据行不可以被当前事务访问。此时需要通过版本链找到上一个版本,然后重新判断该版本的记录对当前事务的可见性。
  • 如果up_limit_id <= DATA_TRX_ID < low_limit_id:需要在活跃事务链表中查找是否存在ID为DATA_TRX_ID的值的事务。如果存在,因为在活跃事务链表中的事务是未提交的,所以该记录是不可见的。此时需要通过版本链找到上一个版本,然后重新判断该版本的可见性。如果不存在,说明事务trx_id 已经提交了,这行记录是可见的。

总结:InnoDB 的MVCC是通过 read view 和版本链实现的,版本链保存有历史版本记录,通过read view 判断当前版本的数据是否可见,如果不可见,再从版本链中找到上一个版本,继续进行判断,直到找到一个可见的版本。

相关推荐
姓蔡小朋友17 分钟前
redis GEO数据结构、实现附近商铺功能
数据结构·数据库·redis
冉冰学姐25 分钟前
SSM农贸市场摊位管理系统c22ux(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面
数据库·ssm 框架·农贸市场·摊位管理系统
面向星辰30 分钟前
SQL LIKE 相似信息查找语句
数据库·sql
数据库学啊1 小时前
时序数据库选型
数据库·时序数据库
TDengine (老段)1 小时前
强杀服务、重启系统及断电对 TDengine 影响
运维·服务器·数据库·物联网·时序数据库·tdengine·涛思数据
数据库学啊1 小时前
时序数据库怎么选
数据库·时序数据库
baivfhpwxf20231 小时前
SQL Server 创建一个删除分表的作业,每月执行一次,删除表的逻辑放到存储过程里
数据库
清静诗意3 小时前
独立 IoT 客户端绕过 Django 生命周期导致数据库断链:诊断与修复
python·mysql·django·生命周期
不知更鸟6 小时前
Django 项目是什么
数据库·sqlite
有一个好名字9 小时前
MyBatis-Plus 三种数据库操作方式详解 + 常用方法大全
数据库·mybatis