😥 整理不易,此资源只针对正式星主开放,
还请入驻星球后再来观看。

GOLANG ROADMAP

阅读模式

  • 沉浸
  • 自动
  • 日常
首页
Go友会
  • 城市
  • 校园
Go学院
  • Go小课
  • Go小考
  • Go实战
  • 精品课
Go求职
  • 求职辅导🔥
  • Offer收割社群
  • 企业题库
  • 面试宝典
Go宝典
  • 在线宝典
  • B站精选
  • 推荐图书
  • 每日博文
Go仓库
实验区
  • Go周边
  • Go下载
  • Go月刊
消息
更多
  • 用户中心

    • 我的信息
    • 推广返利
  • 玩转星球

    • 星球介绍
    • 角色体系
    • 星主权益
  • 支持与服务

    • 联系星主
    • 成长记录
    • 常见问题
    • 吐槽专区
  • 合作交流

    • 渠道合作
    • 课程入驻
    • 友情链接
author-avatar

GOLANG ROADMAP


首页
Go友会
  • 城市
  • 校园
Go学院
  • Go小课
  • Go小考
  • Go实战
  • 精品课
Go求职
  • 求职辅导🔥
  • Offer收割社群
  • 企业题库
  • 面试宝典
Go宝典
  • 在线宝典
  • B站精选
  • 推荐图书
  • 每日博文
Go仓库
实验区
  • Go周边
  • Go下载
  • Go月刊
消息
更多
  • 用户中心

    • 我的信息
    • 推广返利
  • 玩转星球

    • 星球介绍
    • 角色体系
    • 星主权益
  • 支持与服务

    • 联系星主
    • 成长记录
    • 常见问题
    • 吐槽专区
  • 合作交流

    • 渠道合作
    • 课程入驻
    • 友情链接
  • Go真实面试题汇总系列

    • 《其他篇》
  • 宝典内容

    • 6. Go和java比有什么不同?
    • 17. go 实现不重启热部署
    • 23.了解的gc算法有哪些?
    • 50. 怎么用go实现一个栈
    • 60. Kratos 框架的特性
    • 82. 信令用wss还是ws?
    • 111. 有对项目和系统做性能测试吗?(benchmark 和 pprodf)
    • 140. cgo了解过引入的风险点吗?
    • 142. go使用中遇到的问题
    • 143. go的profile工具
    • 161. Go 性能分析工具
    • 172. go里面比较成熟的日志框架了解过没有
    • 181. golang 性能问题怎么排查
    • 193. RR是如何实现的
    • 194. RR级别下能否读取事务ID靠后且尚未提交的记录?
    • 197. java和golang的一些共同点以及区别
    • 205. client如何实现长连接?
    • 207. Go语言实现set
    • 211. 有缓冲和无缓冲的区别?
    • 213. 怎么做服务注册发现的
    • 214. 服务发现有哪些机制
    • 215. 当go服务部署到线上了,发现有内存泄露,该怎么处理
    • 221. 堆的结构,堆的创建,节点添加与删除
    • 232. 线程yield(),sleep(), wait()的区别
    • 233. 如何让拥有GC的情况下产生OOM
    • 244. go 建堆过程
    • 246. golang的一些常用工具库
    • 247. 谈谈go语言和其他语言的区别
    • 258. go常用的第三方库
    • 270. 网络连接的各层的状态
    • 271. 了解中间件吗?有什么好处?
    • 280. 看过啥底层包?
    • 281. RPC基础
    • 291. 内存对其了解吗?
    • 292. Go中struct组合与Java继承的区别
    • 299. Go依赖管理历史有几次方式
    • 301. 对比下node和go
    • 303. 说说火焰图?如何分析的?
    • 316. 可以从多个角度来讲比如面向对象来说,多态继承等等
    • 318. 从包管理来讲,gomod包括之前的dep等等
    • 320. 用go写rpc框架的具体功能细节
    • 323. CAS
    • 328. GO语言中的协程与Python中的协程的区别?
    • 335. 如果项目里api耗时过久,你会怎么去排查
    • 336. 对比 Go 语言和 Java 语言
    • 342. golang:pprof使用
    • 343. 性能调优怎么做
    • 347. 如何排查线上程序问题
    • 356. java 实例放在哪个区,常量放在哪个区
    • 358. java内存模型,方法区,堆栈的区别
    • 359. go web项目的部署,后台持续运行与优雅退出
    • 360. golang的defer,channel,reflect,多线程 panic recover
    • 361. 使用interface的好处
    • 362. Gin框架的特点和源码问题
    • 363. close-wait作用
    • 372. golang开发用什么框架
    • 378. python、go 语言特点
    • 387. select、poll、epoll说下详情和各自优缺点
    • 388. delete new和malloc free关系
    • 392. 重载和重写的概念辨析?
    • 393. 在继承里,子类能重载父类方法吗?
    • 400. 值溢出(usignedchar最大255)
    • 415. django与其他框架的区别
    • 433. gin框架的路由是怎么处理的?
    • 435. go性能分析工具
    • 438. 比较 gin 框架和其它框架
    • 446. 如果一个包要依赖另一个包,这个时候如何写单元测试
    • 447. micro怎么用
    • 448. micro服务发现
    • 449. 如何通过goclient写代码获取
    • 460. 使用 database/sql 和 使用 gorm 的区别
    • 467. c 与go的区别优劣

😥 整理不易,此资源只针对正式星主开放,
还请入驻星球后再来观看。

193. RR是如何实现的


企业题库解析小组

题目序号:1026

题目来源:滴滴

频次:1

答案:chuckchan

  1. MVCC

MVCC 的英文全称是 Multiversion Concurrency Control ,中文意思是多版本并发控制技术。原理是,通过数据行的多个版本管理来实现数据库的并发控制,简单来说就是保存数据的历史版本。可以通过比较版本号决定数据是否显示出来。读取数据的时候不需要加锁可以保证事务的隔离效果。

MVCC是如何实现的?

我们需要先了解两个知识:

  • Read View 中四个字段作用;

  • 聚簇索引记录中两个跟事务有关的隐藏列;

隐藏列

InnoDB 里面每个事务有一个唯一的事务 ID,叫作 transaction id。它是在事务开始的时候向 InnoDB 的事务系统申请的,是按申请顺序严格递增的。而每行数据也都是有多个版本的。每次事务更新数据的时候,都会生成一个新的数据版本,并且把 transaction id 赋值给这个数据版本的事务 ID,记为 row trx_id。同时,旧的数据版本要保留,并且在新的数据版本中,能够有信息可以直接拿到它。也就是说,数据表中的一行记录,其实可能有多个版本 (row),每个版本有自己的 row trx_id。每个版本之间可以用回滚指针相连接。

对于使用 InnoDB 存储引擎的数据库表,它的聚簇索引记录中都包含下面两个隐藏列:

  • trx_id,当一个事务对某条聚簇索引记录进行改动时,就会把该事务的事务 id 记录在 trx_id 隐藏列里;
  • roll_pointer,每次对某条聚簇索引记录进行改动时,都会把旧版本的记录写入到 undo 日志中,然后这个隐藏列是个指针,指向每一个旧版本记录,于是就可以通过它找到修改前的记录。

Read View

Read View 是事务开启时生成的一组视图数据,其中有四个重要的字段:

  • m_ids :指的是在创建 Read View 时,当前数据库中**「活跃事务」的事务 id 列表**,注意是一个列表,“活跃事务”指的就是,启动了但还没提交的事务。
  • min_trx_id :指的是在创建 Read View 时,当前数据库中**「活跃事务」中事务 id 最小的事务**,也就是 m_ids 的最小值。
  • max_trx_id :这个并不是 m_ids 的最大值,而是创建 Read View 时当前数据库中应该给下一个事务的 id 值,也就是全局最大事务 id + 1;
  • creator_trx_id :指的是创建该 Read View 的事务的事务 id。

在创建  Read View 后,我们可以将记录中的 trx_id 划分这三种情况:

一个事务去访问记录的时候,除了自己的更新记录总是可见之外,还有这几种情况:

  • 如果记录的 trx_id 值小于 Read View 中的 min_trx_id 值,表示这个版本的记录是在创建 Read View 前已经提交的事务生成的,所以该版本的记录对当前事务可见。

  • 如果记录的 trx_id 值大于等于 Read View 中的 max_trx_id 值,表示这个版本的记录是在创建 Read View 后才启动的事务生成的,所以该版本的记录对当前事务不可见。

  • 如果记录的 trx_id 值在 Read View 的 min_trx_id 和 max_trx_id 之间,需要判断 trx_id 是否在 m_ids 列表中:

    • 如果记录的 trx_id 在 m_ids 列表中,表示生成该版本记录的活跃事务依然活跃着(还没提交事务),所以该版本的记录对当前事务不可见。

    • 如果记录的 trx_id 不在 m_ids 列表中,表示生成该版本记录的活跃事务已经被提交,所以该版本的记录对当前事务可见。

这种通过「版本链」来控制并发事务访问同一个记录时的行为就叫 MVCC(多版本并发控制)。

  1. 可重复读RR是如何实现的?

可重复读隔离级别是启动事务时生成一个 Read View,然后整个事务期间都在用这个 Read View。

假设事务 A (事务 id 为51)启动后,紧接着事务 B (事务 id 为52)也启动了,那这两个事务创建的 Read View 如下:

事务 A 和 事务 B 的 Read View 具体内容如下:

  • 在事务 A 的 Read View 中,它的事务 id 是 51,由于它是第一个启动的事务,所以此时活跃事务的事务 id 列表就只有 51,活跃事务的事务 id 列表中最小的事务 id 是事务 A 本身,下一个事务 id 则是 52。

  • 在事务 B 的 Read View 中,它的事务 id 是 52,由于事务 A 是活跃的,所以此时活跃事务的事务 id 列表是 51 和 52,活跃的事务 id 中最小的事务 id 是事务 A,下一个事务 id 应该是 53。

接着,在可重复读隔离级别下,事务 A 和事务 B 按顺序执行了以下操作:

  • 事务 B 读取小林的账户余额记录,读到余额是 100 万;

  • 事务 A 将小林的账户余额记录修改成 200 万,并没有提交事务;

  • 事务 B 读取小林的账户余额记录,读到余额还是 100 万;

  • 事务 A 提交事务;

  • 事务 B 读取小林的账户余额记录,读到余额依然还是 100 万;

事务 B 第一次读小林的账户余额记录,在找到记录后,它会先看这条记录的 trx_id,此时发现 trx_id 为 50,比事务 B 的 Read View 中的 min_trx_id 值(51)还小,这意味着修改这条记录的事务早就在事务 B 启动前提交过了,所以该版本的记录对事务 B 可见的,也就是事务 B 可以获取到这条记录。

接着,事务 A 通过 update 语句将这条记录修改了(还未提交事务),将小林的余额改成 200 万,这时 MySQL 会记录相应的 undo log,并以链表的方式串联起来,形成版本链,如下图:

你可以在上图的「记录的字段」看到,由于事务 A 修改了该记录,以前的记录就变成旧版本记录了,于是最新记录和旧版本记录通过链表的方式串起来,而且最新记录的 trx_id 是事务 A 的事务 id(trx_id = 51)。

然后事务 B 第二次去读取该记录,发现这条记录的 trx_id 值为 51,在事务 B 的 Read View 的 min_trx_id 和 max_trx_id 之间,则需要判断 trx_id 值是否在  m_ids 范围内,判断的结果是在的,那么说明这条记录是被还未提交的事务修改的,这时事务 B 并不会读取这个版本的记录。而是沿着 undo log 链条往下找旧版本的记录,直到找到 trx_id 「小于」事务 B 的 Read View 中的 min_trx_id 值的第一条记录,所以事务 B 能读取到的是 trx_id 为 50 的记录,也就是小林余额是 100 万的这条记录。

最后,当事物 A 提交事务后,由于隔离级别时「可重复读」,所以事务 B 再次读区记录时,还是基于启动事务时创建的 Read View 来判断当前版本的记录是否可见。所以,即使事物 A 将小林余额修改为 200 万并提交了事务, 事务 B 第三次读取记录时,读到的记录都是小林余额是 100 万的这条记录。

就是通过这样的方式实现了,「可重复读」隔离级别下在事务期间读到的记录都是事务启动前的记录。

  1. 读已提交RC是如何实现的?

读提交隔离级别是在每次读取数据时,都会生成一个新的 Read View。

也意味着,事务期间的多次读取同一条数据,前后两次读的数据可能会出现不一致,因为可能这期间另外一个事务修改了该记录,并提交了事务。

假设事务 A (事务 id 为51)启动后,紧接着事务 B (事务 id 为52)也启动了,接着按顺序执行了以下操作:

  • 事务 B 读取数据(创建 Read View),小林的账户余额为 100 万;

  • 事务 A 修改数据(还没提交事务),将小林的账户余额从 100 万修改成了 200 万;

  • 事务 B 读取数据(创建 Read View),小林的账户余额为 100 万;

  • 事务 A 提交事务;

  • 事务 B 读取数据(创建 Read View),小林的账户余额为 200 万;

那具体怎么做到的呢?我们重点看事务 B 每次读取数据时创建的  Read View。前两次 事务 B 读取数据时创建的  Read View 如下图:

图片

我们来分析下为什么事务 B 第二次读数据时,读不到事务 A (还未提交事务)修改的数据?

事务 B 在找到小林这条记录时,会看这条记录的 trx_id 是 51,在事务 B 的 Read View 的 min_trx_id 和 max_trx_id 之间,接下来需要判断 trx_id 值是否在  m_ids 范围内,判断的结果是在的,那么说明这条记录是被还未提交的事务修改的,这时事务 B 并不会读取这个版本的记录。而是,沿着 undo log 链条往下找旧版本的记录,直到找到 trx_id 「小于」事务 B 的 Read View 中的 min_trx_id 值的第一条记录,所以事务 B 能读取到的是 trx_id 为 50 的记录,也就是小林余额是 100 万的这条记录。

我们来分析下为什么事务 A 提交后,事务 B 就可以读到事务 A 修改的数据?

在事务 A 提交后,由于隔离级别是「读提交」,所以事务 B 在每次读数据的时候,会重新创建  Read View,此时事务 B 第三次读取数据时创建的  Read View 如下:

事务 B 在找到小林这条记录时,会发现这条记录的 trx_id 是 51,比事务 B 的 Read View 中的 min_trx_id 值(52)还小,这意味着修改这条记录的事务早就在创建  Read View 前提交过了,所以该版本的记录对事务 B 是可见的。

正是因为在读提交隔离级别下,事务每次读数据时都重新创建 Read View,那么在事务期间的多次读取同一条数据,前后两次读的数据可能会出现不一致,因为可能这期间另外一个事务修改了该记录,并提交了事务。