当前位置: 首页 > news >正文

一文搞懂后端面试之MySQL MVCC【中间件 | 数据库 | MySQL | 隔离级别 | Read View】

为什么需要MVCC

锁本身就是用于并发控制的,那么为什么InnoDB还要引入MVCC,读写都加锁不就可以控制住并发吗?
锁确实可以,但是性能太差。如果是纯粹的锁,那么写和写、读和写、读和读之间都是互斥的。如果是读写锁,那么写和写、读和写之间依旧是互斥的。
数据库和一般的应用有一个很大的区别,就是数据库即使是读,也不能被写阻塞住
所以数据库要有一种机制,避免读写阻塞。在理解了为什么MVCC必不可少后,现在你需要进一步了解一个和MVCC紧密关联的概念:隔离级别

隔离级别

数据库的隔离级别是一组规则,用来控制并发访问数据库时如何分配、保护和共享资源。不同的隔离级别在不同的并发控制策略之间进行调整,从而提供了不同的读写隔离级别和安全性。隔离级别代表了一个事务是否了解别的事务以及了解程度怎么样。
MySQL的隔离级别有四个。

  1. 读未提交 Read Uncommitted 是指一个事务可以另外一个事务尚未提交的修改
    在这里插入图片描述
  2. 读已提交 Read Committed 是指一个事务只能看到已经提交的事务的修改。如果在事务执行过程中有别的事务提交了,那么事务还是能够看到别的事务最新提交的修改
    在这里插入图片描述
  3. 可重复读 Repeatable Read 是指在这一个事务内部读同一个数据多次,读到的结果都是同一个。这意味着,即使在事务执行过程中有别的事务提交,这个事务依旧看不到别的事务提交的修改。这是MySQL默认的隔离级别。
    在这里插入图片描述
  4. 串行化 Serializable 是指事务对数据的读写都是串行化的

从上到下,隔离性变强了但是性能变差了,所以一个提升MySQL性能最简单的方式,就是把隔离级别往下调,这也是我们的一个亮点方案。

和隔离级别密切相关的概念是脏读、幻读和不可重复读 这三个读异常。

  • 脏读:读到了别的事务还没有提交的数据。之所以叫做脏读,就是因为未提交数据可能会被回滚掉。
  • 不可重复读:在一个事务执行过程中,对同一行数据读到的结果不同
  • 幻读:事务执行过程中,别的事务插入了新的数据并且提交了,然后事务在后续步骤里读到了这个新的数据。
    在这里插入图片描述
    用一个表用来描述隔离级别和三种读异常的关系:
    在这里插入图片描述
    理论上,可重复读是没有解决幻读的,但是因为MySQL因为使用了临键锁,因此它的可重复读隔离级别已经解决了幻读问题。

此外还有一个相似的概念:快照读和当前读。
快照读就是在事务开始的时候创建了一个数据的快照,在整个事务过程中都读这个快照;
当前读,则是每次都去读最新数据。

MySQL在可重复读这个隔离级别下,查询的执行效果和快照读非常接近。

版本链

为了实现MVCC,InnoDB引擎给每一行都加了两个额外的字段trx_idroll_ptr

  • trx_id:事务ID,也叫做事务版本号。MVCC里面的V指的是这个数字,每一个事务在开始的时候就会获得一个ID,然后这个事务内操作的行的事务ID,都会被修改为这个事务的ID
  • roll_ptr:回滚指针,InnoDB通过roll_ptr把每一行的历史版本串联在一起。

实际上,InnoDB引擎还隐式地插入了另外一个列row_id,如果你没有设置任何主键,那么这个列就会被当作主键来使用。但是它其实和MVCC没太大关系,所以不需要关注。

下面用一个例子来说明MVCC是如何利用这两个列地。

假设最开始我插入了一行数据,我插入数据的这个事务的ID是100,那么这个时候数据行看起来是这样的。
在这里插入图片描述
假设有一个事务A拿到了ID 101,然后把x的值修改为15,那么就会变成这样。
在这里插入图片描述
这个时候,事务A修改后的roll_ptr会指向初始状态的数据。假如现在再来一个事务B拿到ID 102,要把数据修改为20,那么就会变成下面这样。

在这里插入图片描述
这条链就是大名鼎鼎的版本链,这个版本链存储在所谓的undolog里。

问题来了:假如这个时候我有一个新的事务 C,我要读 x 的值,那么我该读取 trx_id 为几的数据呢?这就涉及到了另外一个和 MVCC 紧密相关的概念:Read View

Read View

可以理解为一种可见性规则,undolog里存放着历史版本的数据,当事务内部要读取数据的时候,Read View 就被用来控制这个事务应该读取哪个版本的数据

Read View 最关键的字段叫做m_ids代表的是当前已经开始,但是还没有结束的事务ID,也叫做活跃事务ID。
Read View 只用于已提交读和可重复读两个隔离级别,它用于这两个隔离级别的不同点就在于什么时候生成Read view

  • 已提交读:事务每次发起查询的时候,都会重新创建一个新的Read view
  • 可重复读:事务开始的时候,创建Read view

一个很有意思的类比:已提交读就像你的渣男朋友,你每次见到他,他都会换一个新对象;而可重复读就是一个痴情男,你每次见到他,看到的都是他高中时候谈的对象。

Read view与已提交读

在已提交读的隔离级别下,每一次查询都会产生一个新的Read view。这意味着在事务执行过程中,Read view是在不断变动的。假如说现在已经有三个事务了,状态分别是已提交、未提交、未提交。
在这里插入图片描述
假如说现在新开了一个事务A,分配给它的ID是4。如果这个时候A开始查询x的值,那么MySQL会创建一个新的Read view,其中m_ids = 2,3 。事务A发现最后一个已经提交的事务trx_id=1,对应的x是1,于是事务A读到x=1。
在这里插入图片描述
这个时候事务2提交了,事务A再次读取x,这个时候MySQL又会生成一个新的Read viewm_ids=3,因此事务A会读取到x=4
在这里插入图片描述

Read view与可重复读

在可重复读的隔离级别下,数据库会在事务开始的时候生成一个Read view,这意味着整个Read view在事务执行过程中都是稳定不变的。
用前面的例子来说明,就是在事务A开始的时候就会创建出来一个Read view m_ids=2,3
在这里插入图片描述
这个时候事务A去读x的数据,毫无疑问,读出来都是1
在这里插入图片描述
这个时候如果事务2提交了,然后事务A想要再去读x的值,Read view不会发生变化,即m_ids=2,3。所以,虽然事务2提交了,但是事务A不知道这回事,因此还是读到x=1
在这里插入图片描述
万一这时候有一个新事务 ID = 5 开始了,并且也提交了。那么事务 A 并不会读取这个新事务的数据,因为新事务 ID 已经大于事务 A 的 ID 了(5 > 4),事务 A 知道这是一个比它还要晚的事务,所以会忽略新的事务的修改。

Read View 总结

在这里插入图片描述
在这里插入图片描述
实际上和Read View相关的概念还有三个

  • m_up_limit_id 指的是m_ids中的最小值
  • m_low_limit_id 指的是下一个分配的事务ID
  • m_creator_trx_id 当前事务ID

在这里插入图片描述
m_up_limit_id 在左边,而 m_low_limit_id 在右边

面试准备

了解清楚公司数据库的隔离级别,如果公司设置的不是默认的隔离级别,那么要搞清楚为什么不使用默认的隔离级别。尤其是用了未提交读、串行化两个隔离级别,更加要弄清楚。

在面试过程中,面试官会出一些很难让人反应过来的问题,比如说面试官会口头构造一条版本链。

我现在有三个事务,ID 分别是 101、102、103。如果事务 101 已经提交了,但是 102、103 还没提交。这个时候,我开启了一个事务,准备读取数据,那么我读到的是哪个事务的数据?
如果这时候事务 103 提交了,但是 102 还没提交,那么会读到谁的呢?

第一个问题是事务101
第二个问题需要根据隔离级别来回答了

基本思路

有的时候在面了锁之后,将话题引到MVCC,问你为什么有了锁还需要MVCC?回答的关键词是避免读写阻塞

单纯使用锁的时候,并发性能会比较差,即使是在读写锁这种机制下,读和写依旧是互斥的。而数据库是一个性能非常关键的中间件,如果某个线程修改某条数据就让其他线程都不能读到这条数据,这种性能损耗是无法接受的。所以InnoDB引擎引入了MVCC就是为了减少读写阻塞。

大部分的时候,面试官在问MVCC的时候,都直接问你这几个问题

  • 你是否了解MVCC?
  • MVCC是什么
  • MySQL的InnoDB引擎是怎么控制数据并发访问的?
  • 当一个线程在修改数据的时候,另外一个线程还能不能读到数据

按照:基本定义、实现机制、隔离级别的逻辑顺序来回答

MVCC是MySQL InnoDB引擎用于控制数据并发访问的协议。MVCC主要是借助版本链来实现的。在InnoDB引擎里面,每一行都有两个额外的列,一个是trx_id,代表的是修改这一行数据的事务ID;另外一个是roll_ptr,代表的是回滚指针。InnoDB通过回滚指针,将数据的不同版本串联起来,也就是版本链。这些串联起来的历史版本,被放到了undolog里面。当某一个事务发起查询的时候,MVCC会根据事务的隔离级别来生成不同的Read View,从而控制事务查询最终得到的结果。

首先,回答里提到了undolog,面试官可能追问undolog、redolog或binlog的细节,这一部分可以把话题引到下一节课的内容。
其次,回答中提到了隔离级别,并提到了Read View是和隔离级别有关的东西,面试官就会非常深入的问隔离级别的基本定义、MVCC是怎么利用Read View来实现已提交读和可重复读的。

在回答的时候,要先解释清楚四个隔离级别和三个读异常,然后强调一下InnoDB引擎。

在MySQL的InnoDB引擎里,使用了临键锁来解决幻读的问题,所以实际上MySQL InnoDB引擎的可重复读隔离级别也没有幻读的问题。一般来说,隔离级别越高,性能越差,所以我之前在公司做的一个很重要的事情,就是推动隔离级别降低为已提交读。

这个回答的最后,就可以尝试把话题引导到下面的亮点方案中。

亮点方案

重点要描述清楚两方面的内容

  1. 推动公司把隔离级别从默认的可重复读降低为已提交读
  2. 在已提交读的基础上,万一需要利用可重复读的特性,该怎么办?

从前面的内容中你已经知道,MySQL 的默认隔离级别是可重复读,实际上互联网的很多应用都调整过这个隔离级别,降低为已提交读。那么你在面试的时候可以考虑使用这个来作为你的亮点方案。首先你要强调为什么要改

最开始我来到公司的时候,我们的数据库隔离级别都是使用默认的隔离级别,也就是可重复读。但其实我们的业务场景很少利用可重复读的特性,比如说几乎全部事务内部对某一个数据都是只读一次的
并且,可重复读比已提交读更加容易引起死锁的问题,比如说我们之前就出现过一个因为临键锁引发的死锁问题。而且已提交读的性能要比可重复读更好。所以综合之下,我就推动公司去调整隔离级别,将数据库的默认隔离级别降低为已提交读。

在这种情况下,面试官可能会追问你:“在调整了事务级别之后,万一需要可重复读的特性了,你怎么办?”
首先你要理解在什么样的场景下你才会需要可重复读这个隔离级别。

  • 你需要在事务中发起两次同样的查询,并且你希望两次得到的结果是一样的。
  • 你需要避开幻读,也就是事务开始之后,即便有别的事务插入了数据并且提交了,你也不希望读到这个新数据。

但是仔细想想,你真的存在这种场景吗?或者说,你真的没得选,以至于一定要使用可重复读这个隔离级别吗?
答案是几乎没有。大部分出现可重复读的需求都是因为代码没有写好,或者说至少可以通过改造业务来实现。比如说常见的可重复读,既然你需要读多次,那么自然可以在第一次读完之后缓存起来

不过幻读是没有办法通过业务改造来解决的。但是在业务层面上,幻读一般不会被认为是一个问题,原因有两点:一是你分不清是不是幻读。比如说你在事务 A 里面读到了一条数据,你判断不出来它是在事务 A 开始之前就插入的,还是在事务 A 开始之后,事务 B 才插入并且提交的。
在这里插入图片描述
二是事务提交往往意味着业务已经结束,所以读到一个已经提交的事务的数据,不会损害业务的正确性。也就是说,如果事务A在开始之后,事务B才插入数据并且提交,那么这个时候事务A完全可以认为事务B所在的整个业务已经结束了,所以读出来也没什么问题。

回答的关键词是改造业务

正常来说是不推荐使用可重复读的,因为在我们的业务环境下想不到有什么场景非得使用可重复读这个隔离级别。
之前在推动降低隔离级别的时候,其实重构过一些业务。这一类业务就是在一个事务里面发起了两个同样的查询,比如在UPDATE之后又立刻查询,这种查询还必须走主库,不然会有主从延迟的问题。
这种业务可以通过缓存第一次查询的数据来避免第二次查询。但是这种改造一般是避不开幻读的。不过在业务上幻读一般不是问题。一方面是业务层面上区分不出来是否是幻读。另外一方面,事务提交了往往代表业务已经结束,那么发生幻读了,业务依旧是正常的。比如说事务 A 读到了事务 B 新插入的数据,但是事务 B 本身已经提交了,那么事务 A 就认为事务 B 所在的业务已经完结了,那么读到了就读到了,并不会出什么问题。

兜底的手段是:指定隔离级别

万一不能改造业务,那么还有一个方法,就是直接在创建事务的时候指定隔离级别。我前面调整的都是数据库的默认隔离级别,实际上还可以在 Session 或者事务这两个维度上指定隔离级别。

相关文章:

一文搞懂后端面试之MySQL MVCC【中间件 | 数据库 | MySQL | 隔离级别 | Read View】

为什么需要MVCC 锁本身就是用于并发控制的,那么为什么InnoDB还要引入MVCC,读写都加锁不就可以控制住并发吗? 锁确实可以,但是性能太差。如果是纯粹的锁,那么写和写、读和写、读和读之间都是互斥的。如果是读写锁&…...

Mysql执行计划(上)

1、执行计划的概念 执行计划是什么:使用EXPLAIN关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是如何处理你的SQL语句的。 作用:分析你的查询语句或是表结构的性能瓶颈 语法:Explain SQL语句 执行计划输出内容介绍&#…...

使用Python+moviepy截取音频片段

一、使用AudioFileClip对象的subclip函数,截取1秒至3秒的音频 from moviepy.editor import *auAudioFileClip("/home/Download/test.mp3") # 创建对象clipau.subclip(1,3) # 截取1秒至3秒的音频clip.write_audiofile("/home/Download/clip.mp3"…...

Java学习Day19

动态SQL语句标签 1.if 用于根据条件判断是否包括某段 SQL 代码 <if test"checktext !null and check !"> 2.<choose>, <when>, <otherwise>类似于 Java 的 switch 语句&#xff0c;用于在多个条件中选择一个。 <select id"getSt…...

8.达梦数据库常用SQL

文章目录 前言1. 服务器资源1.1 CPU使用率1.2 内存使用率 2 数据库实例管理2.1 查询版本号2.2 查询ini配置2.3 查询归档配置2.4 数据库实例初始化参数2.5 查看数据库信息2.6 查看数据库实例信息2.7 查看数据库实例信息2.8 查看授权信息2.9 查询页大小&#xff0c;字符集大小2.1…...

深入理解接口测试:实用指南与最佳实践(四)IHRM管理系统实战-项目分析

​ ​ 您好&#xff0c;我是程序员小羊&#xff01; 前言 这一阶段是接口测试的学习&#xff0c;我们接下来的讲解都是使用Postman这款工具&#xff0c;当然呢Postman是现在一款非常流行的接口调试工具&#xff0c;它使用简单&#xff0c;而且功能也很强大。不仅测试人员会使用…...

程序编译及链接

你好&#xff01;感谢支持孔乙己的新作&#xff0c;本文就程序的编译及链接与大家分析我的思路。 希望能大佬们多多纠正及支持 &#xff01;&#xff01;&#xff01; 个人主页&#xff1a;爱摸鱼的孔乙己-CSDN博客 ​ ​ 1.翻译译环境与运行环境 当我们进行程序设计时&…...

route 命令介绍及使用方法

route 命令 作用&#xff1a;用于显示和操作 IP 路由表 &#xff08;show/manipulate the IP routing table&#xff09;。 在命令行下执行 route 命令添加路由&#xff0c;不会永久保存&#xff0c;当网卡重启或者机器重启后&#xff0c;该路由就会失效。 命令参数&#xf…...

力扣热题100_二叉树_226_翻转二叉树

文章目录 题目链接解题思路解题代码 题目链接 226. 翻转二叉树 给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 示例 1&#xff1a; 输入&#xff1a;root [4,2,7,1,3,6,9] 输出&#xff1a;[4,7,2,9,6,3,1] 示例 2&#xff1a; …...

Java SpringBoot 集成 MinIO 资料

Java SpringBoot 集成 MinIO 资料 一、文档 官方文档CSDN项目示例解除Spring文件上传大小限制 二、个人实战 注意事项&#xff1a; 部署MinIO时会涉及到两个端口号&#xff0c;一个为endpoint的端口&#xff0c;一个为console的端口&#xff0c;注意不要弄混 比如&#xff1a…...

鸿蒙系统开发【加解密算法库框架】安全

加解密算法库框架 介绍 本示例使用ohos.security.cryptoFramework相关接口实现了对文本文件的加解密、签名验签操作。 实现场景如下&#xff1a; 1&#xff09;软件需要加密存储本地文本文件&#xff0c;需要调用加解密算法库框架选择密钥文本文件&#xff0c;对本地文本文…...

C语言——二维数组和字符数组

二维数组 二维数组本质上是一个行列式的组合&#xff0c;也就是二维数组是有行和列两部分构成。二维数组数据是通过行列进行解读。 定义形式&#xff1a; 类型&#xff08;说明符&#xff09; 数组名[行数&#xff08;常量表达式1&#xff09;][列数&#xff08;常量表达式…...

Python 爬虫入门(九):Scrapy安装及使用「详细介绍」

Python 爬虫入门&#xff08;九&#xff09;&#xff1a;Scrapy安装及使用「详细介绍」 前言1. Scrapy 简介2. Scrapy 的安装2.1 环境准备2.2 安装 Scrapy 3. 创建 Scrapy 项目3.1 创建项目3.2 项目结构简介 4. 编写爬虫4.1 创建爬虫4.2 解析数据4.3 运行爬虫 5. 存储数据5.1 存…...

扩展addr2line程序的功能,group_add2line() 脚本的实现

------------------------------------------------------------ author: hjjdebug date: 2024年 08月 05日 星期一 16:19:07 CST descrition: 扩展addr2line程序的功能,group_add2line() 脚本的实现 ------------------------------------------------------------ 扩展addr2…...

idea中修改项目名称

公司最近有个小项目新加了很多功能&#xff0c;在叫原先的项目名有点不合适了。所以在网上查了下资料&#xff0c;发现步骤都比较复杂。自己研究了一下找到了一个相对简单的方法&#xff0c;只需要两步&#xff0c;特此记录一下。 1.修改项目文件夹名称 关闭当前项目&#xff…...

Flink开发语言使用Java还是Scala合适?

目录 1. Flink简介 1.1 什么是Apache Flink&#xff1f; 1.2 Flink的核心组件 2. Java与Scala在Flink开发中的比较 2.1 语言特性对比 2.2 开发体验对比 3. 实际开发中的应用 3.1 使用Java进行Flink开发 3.2 使用Scala进行Flink开发 4. 关键性能和优化 4.1 性能对比 …...

C++STL专题 vector底层实现

目录 一&#xff0c; vector的手搓 1.构造函数 2. 拷贝构造的实现 3.析构函数 4.begin() end() 的实现 5.reserve的实现 6.size和capacity的实现 7.push_back的实现 8.pop_back的实现 9.empty的实现 10.insert的实现 11.erase的实现 12.resize的实现 13.clear的实…...

【Linux】装机常用配置

文章目录 1. 下载常用软件包2. 更新yum源3. vim编辑器配置4. 安装C语言和C的静态库&#xff08;换root&#xff09;5. git6. sudo给普通用户提权7. 更新git版本&#xff08;centos默认安装1.8.x&#xff0c;我们更新到2.x&#xff09;8. getch9. json10. 升级gcc版本11. 跨系统…...

oracle库PASSWORD_VERSIONS 对应的加密方式

oracle库PASSWORD_VERSIONS 对应的加密方式 10G DES 11G SHA-1 12C SHA-2-based SHA-512官方文档&#xff1a; https://docs.oracle.com/database/121/DBSEG/authentication.htm#DBSEG487...

分享一个基于微信小程序的乡村医疗上门服务预约平台(源码、调试、LW、开题、PPT)

&#x1f495;&#x1f495;作者&#xff1a;计算机源码社 &#x1f495;&#x1f495;个人简介&#xff1a;本人 八年开发经验&#xff0c;擅长Java、Python、PHP、.NET、Node.js、Android、微信小程序、爬虫、大数据、机器学习等&#xff0c;大家有这一块的问题可以一起交流&…...

应用升级/灾备测试时使用guarantee 闪回点迅速回退

1.场景 应用要升级,当升级失败时,数据库回退到升级前. 要测试系统,测试完成后,数据库要回退到测试前。 相对于RMAN恢复需要很长时间&#xff0c; 数据库闪回只需要几分钟。 2.技术实现 数据库设置 2个db_recovery参数 创建guarantee闪回点&#xff0c;不需要开启数据库闪回。…...

NLP学习路线图(二十三):长短期记忆网络(LSTM)

在自然语言处理(NLP)领域,我们时刻面临着处理序列数据的核心挑战。无论是理解句子的结构、分析文本的情感,还是实现语言的翻译,都需要模型能够捕捉词语之间依时序产生的复杂依赖关系。传统的神经网络结构在处理这种序列依赖时显得力不从心,而循环神经网络(RNN) 曾被视为…...

Pinocchio 库详解及其在足式机器人上的应用

Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库&#xff0c;专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性&#xff0c;并提供了一个通用的框架&…...

中医有效性探讨

文章目录 西医是如何发展到以生物化学为药理基础的现代医学&#xff1f;传统医学奠基期&#xff08;远古 - 17 世纪&#xff09;近代医学转型期&#xff08;17 世纪 - 19 世纪末&#xff09;​现代医学成熟期&#xff08;20世纪至今&#xff09; 中医的源远流长和一脉相承远古至…...

七、数据库的完整性

七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...

Redis:现代应用开发的高效内存数据存储利器

一、Redis的起源与发展 Redis最初由意大利程序员Salvatore Sanfilippo在2009年开发&#xff0c;其初衷是为了满足他自己的一个项目需求&#xff0c;即需要一个高性能的键值存储系统来解决传统数据库在高并发场景下的性能瓶颈。随着项目的开源&#xff0c;Redis凭借其简单易用、…...

(一)单例模式

一、前言 单例模式属于六大创建型模式,即在软件设计过程中,主要关注创建对象的结果,并不关心创建对象的过程及细节。创建型设计模式将类对象的实例化过程进行抽象化接口设计,从而隐藏了类对象的实例是如何被创建的,封装了软件系统使用的具体对象类型。 六大创建型模式包括…...

elementUI点击浏览table所选行数据查看文档

项目场景&#xff1a; table按照要求特定的数据变成按钮可以点击 解决方案&#xff1a; <el-table-columnprop"mlname"label"名称"align"center"width"180"><template slot-scope"scope"><el-buttonv-if&qu…...

WEB3全栈开发——面试专业技能点P7前端与链上集成

一、Next.js技术栈 ✅ 概念介绍 Next.js 是一个基于 React 的 服务端渲染&#xff08;SSR&#xff09;与静态网站生成&#xff08;SSG&#xff09; 框架&#xff0c;由 Vercel 开发。它简化了构建生产级 React 应用的过程&#xff0c;并内置了很多特性&#xff1a; ✅ 文件系…...

在Zenodo下载文件 用到googlecolab googledrive

方法&#xff1a;Figshare/Zenodo上的数据/文件下载不下来&#xff1f;尝试利用Google Colab &#xff1a;https://zhuanlan.zhihu.com/p/1898503078782674027 参考&#xff1a; 通过Colab&谷歌云下载Figshare数据&#xff0c;超级实用&#xff01;&#xff01;&#xff0…...