MYSQL:MVCC

MVCC

MVCC,全称Multi-Version Concurrency Control,即多版本并发控制。MVCC是一种并发控制的方法,可以实现对数据库的并发访问。

MVCC带来的好处是?

多版本并发控制(MVCC)是一种用来解决读-写冲突的无锁并发控制,也就是为事务分配单向增长的事务ID,为每个修改保存一个版本,版本与事务ID关联,读操作只读该事务开始前的数据库的快照。 所以MVCC可以为数据库解决以下问题

  • 在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能
  • 同时还可以解决脏读,幻读,不可重复读等事务隔离问题,但不能解决更新丢失问题

MVCC的实现原理

MVCC的实现原理主要是依赖记录中的 4个隐式字段,undo日志 ,Read View来实现的。所以我们先来看看这个四个隐藏字段

隐式字段

每行记录除了我们自定义的字段外,还有数据库隐式定义的DB_TRX_ID,DB_ROLL_PTR,DB_ROW_ID等字段

  • DB_TRX_ID
    6byte,最近修改(修改/插入)事务ID:记录创建这条记录/最后一次修改该记录的事务ID
  • DB_ROLL_PTR
    7byte,回滚指针,指向这条记录的上一个版本(存储于rollback segment里)
  • DB_ROW_ID
    6byte,隐含的自增ID(隐藏主键),如果数据表没有主键,InnoDB会自动以DB_ROW_ID产生一个聚簇索引
  • flag
    实际还有一个删除flag隐藏字段, 既记录被更新或删除并不代表真的删除,而是删除flag变了

在这里插入图片描述

undo日志

redo log:重做日志,就是每次mysql在执行写入数据前先把要写的信息保存在重写日志中,但出现断电,奔溃,重启等等导致数据不能正常写入期望数据时,服务器可以通过redo_log中的信息重新写入数据。

undo log:撤销日志,与redo log恰恰相反,当一些更改在执行一半时,发生意外,而无法完成,则可以根据撤消日志恢复到更改之前的壮态。

undo log主要分为两种:

  • insert undo log
    事务在insert新记录时产生的undo log, 只在事务回滚时需要,并且在事务提交后可以被立即丢弃
  • update undo log
    事务在进行update或delete时产生的undo log; 不仅在事务回滚时需要,在快照读时也需要;所以不能随便删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被purge线程统一清除

Read View(读视图)

说read view之前,要先知道当前读
当前读
读取数据的最新版本。常见的 update/insert/delete、还有 select ... for update、select ... lock in share mode都是当前读。

什么是Read View?
Read View就是在该事务进行的快照读的那一刻,会生成数据库系统当前的一个快照,这个快照就是read view。

下面就说一下怎么基于MVCC生成Read View?
在此之前,先说几个ReadView的概念

  • m_ids活跃事务id列表,表示在生成ReadView时系统中活跃的读写事务的事务id列表。
  • min_trx_id活跃事务id列表中最小事务ID表示在生成ReadView时当前活跃的读写事务中最小的事务id,也就是m_ids中的最小值。
  • max_trx_id活跃事务id列表中最大事务ID表示生成ReadView时系统中应该分配给下一个事务(这是解决情况1的核心点)的id值。
  • creator_trx_id当前事务ID表示生成该ReadView的事务的事务id。

判断逻辑:

  • 如果被访问版本的trx_id属性值与ReadView中的creator_trx_id值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。
  • 如果被访问版本的trx_id属性值小于ReadView中的min_trx_id值,表明生成该版本的事务在当前事务生成ReadView前已经提交,所以该版本可以被当前事务访问。
  • 如果被访问版本的trx_id属性值大于或等于ReadView中的max_trx_id值,表明生成该版本的事务在当前事务生成ReadView后才开启,所以该版本不可以被当前事务访问。
  • 如果被访问版本的trx_id属性值在ReadView的min_trx_id和max_trx_id之间,那就需要判断一下trx_id属性值是不是在m_ids列表中,如果在,说明创建ReadView时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建ReadView时生成该版本的事务已经被提交,该版本可以被访问。

MVCC 解决了幻读了没有?
MVCC 解决了部分幻读,但并没有完全解决幻读。

对于快照读,MVCC 因为因为从 ReadView 读取,所以必然不会看到新插入的行,所以天然就解决了幻读的问题。

而对于当前读的幻读,MVCC 是无法解决的。需要使用 Gap Lock 或 Next-Key Lock(Gap Lock + Record Lock)来解决。

Repeatable Read 解决了幻读是什么情况?
SQL 标准中规定的 RR 并不能消除幻读,但是 MySQL InnoDB 的 RR 可以,靠的就是 Gap 锁。在 RR 级别下,Gap 锁是默认开启的,而在 RC 级别下,Gap 锁是关闭的。

深入思考

只有在对表中的记录做改动时(执行INSERT、DELETE、UPDATE这些语句时)才会为事务分配事务id,否则在一个只读事务中的事务id值都默认为0。

q:如果默认select产生的事务id为0的话,那么min_trx_id永远为0,这不是扯犊子吗?
A:m_ids为读写事务列表,而只有select查询的情况下,为只读事务,不会参与计算,并且其实只读事务会产生一个随机事务id,并不是默认为0的事务
在这里插入图片描述

场景举例

CREATE TABLE `test` (
  `id` int(11) DEFAULT NULL,
  `name` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

RR级别

情况1:

事务A事务B
begin;
begin;
Select * from test;
insert into test(id,name) values(2,‘323’);
commit;
Select * from test;
此时无法查询到B事务插入的数据

情况2:

事务A事务B
begin;
begin;
insert into test(id,name) values(2,‘323’);
commit;
Select * from test;
可以查询到B事务插入的数据

情况3:

事务A事务B
begin;
begin;
Update test set name = ‘2’ where id = 4
insert into test(id,name) values(2,‘323’);
commit;
Select * from test;
可以查询到B事务插入的数据

情况4:

事务A事务B
begin;
begin;
Select * from test;
insert into test(id,name) values(2,‘323’);
commit;
Select * from test;
Update test set name = ‘2’ where id = 2
Select * from test;
此时无法查询到B事务插入的数据
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值