MySQL进阶04-MVCC实现原理

表记录读取的两种方式

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

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

举个栗子

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

事务A事务B
start transactionstart transaction
select * from t_user where user_name=‘%a’ for update; 假设有一条
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’ for update; 此时就能查到两条
commit

MySQL是在RR隔离级别下是如何避免幻读的

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

在当前读情况下:MySQL通过临键锁(next-key:行锁+间隙锁)来避免幻读。如果没有正确地锁定读取范围,其他事务仍然可以在这些未锁定的范围之间插入新的行,导致幻读‌

  • 注意:MySQL中的锁,锁的是索引,行锁是加在索引上的锁,间隙锁是加在索引之间的锁

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.用排他锁锁住该行;
  • 2.将该行原本的值拷贝到undo log,作为旧版本用于回滚;
  • 3.修改当前行的值,生成一个新版本,更新事务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。
  • DATA_TRX_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的值的事务。
    • 1.如果存在,因为在活跃事务链表中的事务是未提交的,所以该记录是不可见的。此时需要通过版本链找到上一个版本,然后重新判断该版本的可见性。
    • 2.如果不存在,说明事务trx_id 已经提交了,这行记录是可见的。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

宇宇小跟班

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值