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 判断当前版本的数据是否可见,如果不可见,再从版本链中找到上一个版本,继续进行判断,直到找到一个可见的版本。

相关推荐
剑客狼心2 分钟前
Oracle:什么是存储过程
数据库·oracle
小王努力学编程5 分钟前
【MySQL篇】表的操作
数据库·mysql
汤汤upup6 分钟前
面试八股文--数据库基础知识总结(2) MySQL
数据库·mysql·面试
TangKenny7 分钟前
information_schema.processlist 表详解
mysql·processlist
m0_748250933 小时前
mysql数据被误删的恢复方案
数据库·mysql
m0_748238423 小时前
MySQL Workbench菜单汉化为中文
android·数据库·mysql
一小路一3 小时前
从0-1学习Mysql第五章: 索引与优化
数据库·后端·学习·mysql·面试
老朋友此林3 小时前
浅析 Redis 分片集群 Cluster 原理、手动搭建、动态伸缩集群、故障转移
java·数据库·redis
huaqianzkh5 小时前
基于 ‌MySQL 数据库‌对三级视图(用户视图、DBA视图、内部视图)的详细解释
数据库·mysql·dba