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

一文精通MVCC机制

MVCC(Multi-Version Concurrency Control)多版本并发控制机制

使用串行化隔离级别时,mysql会将所有的操作加锁互斥,来保证并发安全。这种方式必然降低并发性能。mysql在读已提交可重复读隔离级别下,对一行数据的读和写两个操作默认是不会通过加锁互斥来保证隔离性,避免了频繁加锁互斥。那么具体是如何实现的呢?首先要了解两个概念。

准备

建表语句

CREATE TABLE `product` ( `id` int NOT NULL AUTO_INCREMENT, `name` varchar(255) DEFAULT NULL, `price` int DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8;

undo日志版本链

  1. 我们向product表插入一条数据

INSERT INTO mysql_demo.product (id, name, price) VALUES (1, 'apple', 10);

此时mysql会同时向undo日志里写入一条记录。 trx_id为插入操作的事务id。这里随便写了一个80,意思一下。 roll_pointer后面再说。

  1. 这时候又来了一个事务,对数据进行了修改。比如事务id 300,修改price为20。此时mysql同样会在undo日志里写入一条记录。并且roll_pointer会指向前一条记录

  1. 以此类推,后续又有新的事务来操作这条记录,就会形成一条版本链,这条链就是undo日志版本链

每条数据对应着有一个undo日志版本链。

对于insert和update操作,mysql会向undo日志里添加一条记录。select操作不会产生记录。

对于删除的情况可以认为是update的特殊情况,会将版本链上最新的数据复制一份,然后将trx_id修改成删除操作的trx_id,同时在该条记录的头信息(record header)里的(deleted_flag)标记位写上true,来表示当前记录已经被删除,在查询时按照上面的规则查到对应的记录如果delete_flag标记位为true,意味着记录已被删除,则不返回数据。

在来看下什么是read view。

一致性试图read view机制

read view的生成

  • 可重复读隔离级别:事务开启后,首次执行任何select时会生成当前事务的read-view,在事务结束前不会变化。

  • 读已提交隔离级别:事务开启后,每次执行select时都会重新生成read-view。

read view的组成

这个视图由执行查询时所有未提交事务id数组(数组里最小的id为min_id)已创建的最大事务id(max_id)组成。

我们来举个例子。

  1. Transaction 80: 开启事务,插入一条记录。并且commit;

  1. Transaction 100:开启事务,执行update。生成事务id 100。这里需要注意begin和select不会生成事务id,所以加了一条无关的update,生成事务id。update内容可以忽略。

begin/start transaction 命令并不是一个事务的起点,在执行到它们之后的第一个修改操作InnoDB表的语句,事务才真正启动,才会向mysql申请事务id

mysql内部是严格按照事务的启动顺序来分配事务id的

  1. Transaction 200:同上

  1. Transaction 300:把价格修改成20了。并且commit了。

  1. select 1: select 不生成事务id。 事务开启后,首次执行任何select时会生成当前事务的read-view。

  1. Transaction 400:把价格修改成18了。

read view的组成 = 未提交事务id数组(数组里最小的id为min_id) + 已创建的最大事务id(max_id)组成

此时未提交事务id有100,200(80 已经提交了)。最小的id为100。 已创建的最大事务id为300。(注意read view 是在第5步生成的,此时还没有Transaction 400)

因此 read view为[100,200],300 min_id为100 ,max_id为300。 [100,200] 为视图数组。

此时对应的undo日志版本链如下

那么read view 的作用是什么呢?

read view的作用

根据上面的结果,我们可以将事务进行分类。因为事务的id是有序递增的。所以我们可以得出以下结论

  • 因为未提交事务的最小id(min_id)为100,所以小于100的事务都是已提交的。( Transaction 80)

  • 因为已创建的最大事务id(max_id)为300,所以大于300的区域都是未开启事务。 (Transaction 400) 未开启理解为在执行select的时候没有开启。

  • 介于min_id和max_id之间的事务,包含了未提交和已提交的事务。 (Transaction 100,200,300)

那么mysql是如何通过read view和undo日志版本链实现并发事务之间的隔离的呢?那就需要看下版本链比对规则了。

版本链比对规则

事务里的每一条select都需要从对应版本链里的最新数据开始逐条跟read-view做比对,按照比对规则得到最终的快照结果。下面我们来看下版本链比对规则。

  1. 如果 row 的 trx_id 落在绿色部分( trx_id

  1. 如果 row 的 trx_id 落在灰色部分( trx_id>max_id ),表示这个版本是由将来启动的事务生成的

  1. row 的 trx_id 就是当前自己的事务是可见的;

  1. 否则不可见;

  1. 如果 row 的 trx_id 落在黄色部分(min_id <=trx_id<= max_id),那就包括两种情况

  1. 若 row 的 trx_id 在视图数组中,表示这个版本是由还没提交的事务生成的,

  1. 若 row 的 trx_id 就是当前自己的事务是可见的

  1. 否则不可见;

  1. 若 row 的 trx_id 不在视图数组中,表示这个版本是已经提交了的事务生成的,可见。

知道了版本链的比对规则,下面我们通过实例来看下,mysql的MVCC机制是如何工作的。

实战演练

可重复读Repeatable-Read(RR)

我们先以可重复读Repeatable-Read(RR)为例

可重复读隔离级别:事务开启后,首次执行任何select时会生成当前事务的read-view,在事务结束前不会变化。

案例一

我们先以上面的情况为例来进行分析。此时的情况如下:

  • read view为 [100,200],300

  • undo日志版本链如下

  • 套用版本链比对规则

  1. 首先在版本链中找到最新数据。

  1. Transaction 300,trx_id = max_id。此时继续比对, trx_id 不在视图数组中,可见

  1. 返回Transaction 300记录的数据信息。price = 20;

案例二

Transaction 400,在第10行执行了一次update。

Transaction 100,在第11,12行执行了两次update。然后select 1 13行执行了一次select。 我们来分析下这个select。

  • 因为RR隔离级别首次执行任何select时会生成当前事务的read-view,在事务结束前不会变化。所以read view为 [100,200],300。没有变化。

  • undo日志版本链如下

  • 套用版本链比对规则

  1. 首先在版本链中找到最新数据。

  1. Transaction 100,trx_id = min_id。继续分析 trx_id 在视图数组中,表示这个版本是由还没提交的事务生成的,不可见。

  1. 第二行Transaction 100,分析同上

  1. 第三行Transaction 400, trx_id > max_id,不可见。

  1. Transaction 300,trx_id = max_id。此时继续比对,trx_id 不在视图数组中,可见

  1. 返回Transaction 300记录的数据信息。price = 20;

案例三

继续向下Transaction 100,在第15行commit。Transaction 200,在第15,16行执行了两次update。然后select1 17行执行了一次select。 我们来分析下这个select。

  • 因为RR隔离级别首次执行任何select时会生成当前事务的read-view,在事务结束前不会变化。所以read view为 [100,200],300。没有变化。

  • undo日志版本链如下

  • 套用版本链比对规则

  1. 首先在版本链中找到最新数据。

  1. Transaction 200,min_id < trx_id < max_id。继续分析 trx_id 在视图数组中,表示这个版本是由还没提交的事务生成的,不可见。

  1. 下一行Transaction 200,分析同上.

  1. Transaction 100,trx_id = min_id。继续分析 trx_id 在视图数组中,表示这个版本是由还没提交的事务生成的,不可见。

  1. 下一行Transaction 100,分析同上。

  1. 下一行Transaction 400, trx_id > max_id,不可见。

  1. Transaction 300,trx_id = max_id。此时继续比对,trx_id 不在视图数组中,可见

  1. 返回Transaction 300记录的数据信息。price = 20;

案例四

继续select2 17行执行了一次select。 我们来分析下这个select。

  • RR隔离级别首次执行任何select时会生成当前事务的read-view。read view为 [200,400],400。

  • undo日志版本链如下

  • 套用版本链比对规则

  1. 首先在版本链中找到最新数据。

  1. Transaction 200,trx_id = min_id。继续分析 trx_id 在视图数组中,表示这个版本是由还没提交的事务生成的,不可见。

  1. 下一行Transaction 200,分析同上.

  1. Transaction 100,trx_id < min_id。表示这个版本是已提交的事务生成的,这个数据是可见的;

  1. 返回 price = 16。

案例五

我们再来看一下如果select1 如果有update操作(update操作会创建事务id,我们假设是 500)。Transaction 500 此时是如何读取到更新后的数据的。

来分析下15行。

  • RR隔离级别首次执行任何select时会生成当前事务的read-view,在事务结束前不会变化。read view为 [100,200],300。

  • undo日志版本链如下

  • 套用版本链比对规则

  1. 首先在版本链中找到最新数据。

  1. Transaction 400,trx_id > max_id(read view是第一次select时生成的,此时max_id仍然是 300)。表示这个版本是由将来启动的事务生成的,是不可见的

  1. Transaction 500,trx_id > max_id。表示这个版本是由将来启动的事务生成的,但row 的 trx_id 就是当前自己的事务是可见的;所以可见

  1. 返回 price = 8。

结论:通过以上案例,我们可以知道。 MVCC机制在RR中首次查询时会固定read view。后续和其他事务隔离开了,其他事务对数据的操作不会影响到当前事务。

读已提交Read-Committed(RC)

我们再以读已提交Read-Committed(RC)为例

读已提交隔离级别:事务开启后,每次执行select时都会重新生成read-view。

案例一

第9行没有变化,我们来看第13行。

read view的组成 = 未提交事务id数组(数组里最小的id为min_id) + 已创建的最大事务id(max_id)组成

未提交事务id数组 100,200,400 ; min_id 100 ; max_id 400

  • read view为 [100,200,400],400。

  • undo日志版本链如下

  • 套用版本链比对规则

  1. 首先在版本链中找到最新数据。

  1. Transaction 100,trx_id = min_id。继续分析 trx_id 在视图数组中,表示这个版本是由还没提交的事务生成的,不可见。

  1. 下一行Transaction 100,分析同上.

  1. Transaction 400, trx_id = max_id。继续分析 trx_id 在视图数组中,表示这个版本是由还没提交的事务生成的,不可见。

  1. Transaction 300,min_id < trx_id< max_id。不在视图数组中,表示这个版本是已经提交了的事务生成的,可见。

  1. 返回 price = 20。

案例二

来看第17行。

read view的组成 = 未提交事务id数组(数组里最小的id为min_id) + 已创建的最大事务id(max_id)组成

未提交事务id数组 200,400 ; min_id 200 ; max_id 400

  • read view为 [200,400],400。

  • undo日志版本链如下

  • 套用版本链比对规则

  1. 首先在版本链中找到最新数据。

  1. Transaction 200, trx_id = min_id。继续分析 trx_id 在视图数组中,表示这个版本是由还没提交的事务生成的,不可见。

  1. 同上

  1. Transaction 100, trx_id

  1. 返回 price = 16。

OK,就分析到这里吧。希望对你有所帮助!

读已提交Read-Committed(RC)

我们再以读已提交Read-Committed(RC)为例

读已提交隔离级别:事务开启后,每次执行select时都会重新生成read-view。

案例一

第9行没有变化,我们来看第13行。

read view的组成 = 未提交事务id数组(数组里最小的id为min_id) + 已创建的最大事务id(max_id)组成

未提交事务id数组 100,200,400 ; min_id 100 ; max_id 400

  • read view为 [100,200,400],400。

  • undo日志版本链如下

  • 套用版本链比对规则

  1. 首先在版本链中找到最新数据。

  1. Transaction 100,trx_id = min_id。继续分析 trx_id 在视图数组中,表示这个版本是由还没提交的事务生成的,不可见。

  1. 下一行Transaction 100,分析同上.

  1. Transaction 400, trx_id = max_id。继续分析 trx_id 在视图数组中,表示这个版本是由还没提交的事务生成的,不可见。

  1. Transaction 300,min_id < trx_id< max_id。不在视图数组中,表示这个版本是已经提交了的事务生成的,可见。

  1. 返回 price = 20。

案例二

来看第17行。

read view的组成 = 未提交事务id数组(数组里最小的id为min_id) + 已创建的最大事务id(max_id)组成

未提交事务id数组 200,400 ; min_id 200 ; max_id 400

  • read view为 [200,400],400。

  • undo日志版本链如下

  • 套用版本链比对规则

  1. 首先在版本链中找到最新数据。

  1. Transaction 200, trx_id = min_id。继续分析 trx_id 在视图数组中,表示这个版本是由还没提交的事务生成的,不可见。

  1. 同上

  1. Transaction 100, trx_id

  1. 返回 price = 16。

OK,就分析到这里吧。希望对你有所帮助!

相关文章:

一文精通MVCC机制

MVCC(Multi-Version Concurrency Control)多版本并发控制机制使用串行化隔离级别时&#xff0c;mysql会将所有的操作加锁互斥&#xff0c;来保证并发安全。这种方式必然降低并发性能。mysql在读已提交和可重复读隔离级别下&#xff0c;对一行数据的读和写两个操作默认是不会通过…...

商用ESP32协议采集器源码分享开篇

这是一个关于chatGPT帮助嵌入式程序员开发商业项目的故事. 在开发这个项目的过程中,chatGPT发布了,在它的帮助下,项目开发量减少了10%,所以这个专栏,既是一个关于Micropython开发ESP32的专栏,也是一个程序员在AI的帮助下,提升效率,加速挣钱的案例. 看完之后,你将知道如何用mic…...

代码随想录算法训练营第三十四天 | 860.柠檬水找零,406.根据身高重建队列,452. 用最少数量的箭引爆气球

一、参考资料柠檬水找零https://programmercarl.com/0860.%E6%9F%A0%E6%AA%AC%E6%B0%B4%E6%89%BE%E9%9B%B6.html 根据身高重建队列 https://programmercarl.com/0406.%E6%A0%B9%E6%8D%AE%E8%BA%AB%E9%AB%98%E9%87%8D%E5%BB%BA%E9%98%9F%E5%88%97.html 用最少数量的箭引爆气球ht…...

DDR4介绍01

DDR4&#xff08;第四代双倍数据率同步动态随机存储器SDRAM&#xff09; 关于内存方面知识&#xff0c;大部分人、包括我自己也不是很懂&#xff0c;希望此篇文章能起到点作用&#xff0c;做硬件的就得把相关专业知识学牢了&#xff0c;尤其是专业术语。 下面是DDR4知识做一次…...

扫地机器人行业投资逻辑:国内以价换量元年,海外需求企稳回升

1、国内以价换量元年,投资逻辑由产品迭代转向行业的渗透率提升 2019-2022 年国内扫地机行业主要的投资逻辑是产品迭代的价增带动销额增长。 2019-2022 年国内热销的扫地机产品从单机向自清洁扫地机、全能基站扫地机持续迭 代升级,产品功能日益完善、瞄准用户痛点更新,真正实…...

(考研湖科大教书匠计算机网络)第四章网络层-第七节:IPv4数据报首部格式

获取pdf&#xff1a;密码7281专栏目录首页&#xff1a;【专栏必读】考研湖科大教书匠计算机网络笔记导航 文章目录一&#xff1a;IP数据报首部格式概述二&#xff1a;各字段作用概述&#xff08;1&#xff09;版本&#xff08;2&#xff09;首部长度和可选字段&#xff08;3&am…...

每天10个前端小知识 【Day 18】

前端面试基础知识题 1.如何实现单行&#xff0f;多行文本溢出的省略样式&#xff1f; 在日常开发展示页面&#xff0c;如果一段文本的数量过长&#xff0c;受制于元素宽度的因素&#xff0c;有可能不能完全显示&#xff0c;为了提高用户的使用体验&#xff0c;这个时候就需要…...

【Java集合类】ArrayList

内部结构 ArrayList内部核心是一个Object数组elementDataObject数组的长度&#xff08;length&#xff09;视为ArrayList当前的容量&#xff08;capacity&#xff09;size对象表示ArrayList当前的元素个数 类上的重要注释 内部是Object数组 允许put null值,会自动扩容 size、…...

页面置换算法

页面置换算法 在进程运行过程中&#xff0c;若需要访问的物理块不在内存中&#xff0c;就需要通过一定的方式来将页面载入内存&#xff0c;而此时内存很可能已无空闲空间&#xff0c;因此就需要一定的算法来选择内存中要被置换的页面&#xff0c;这种算法就被称为页面置换算法…...

算法导论【在线算法】—The Ski-Rental Problem、The Lost Cow Problem、The Secretary Problem

算法导论【在线算法】The Ski-Rental Problem问题描述在线算法证明The Lost Cow Problem问题描述在线算法类似问题—寻宝藏The Secretary Problem问题描述在线算法The Best Possible kThe Ski-Rental Problem 问题描述 假设你正在上滑雪课。每节课结束后&#xff0c;你决定&a…...

linux 下怎样给pdf 文件加书签

linux 下怎样给pdf 文件加书签 对于没有书签的pdf文件,怎样给pdf加标签呢? 以方便阅读. 以前总是要借助windows下pdf 工具, 叫什么来者? 忘了 记得是编辑一个用tab表示目录级别的文本文件, 有一种直观的感觉,大目录下嵌套着小目录 ..., 然后导入到文件中 linux 下有没有这种…...

[软件工程导论(第六版)]第2章 可行性研究(课后习题详解)

文章目录1. 在软件开发的早期阶段为什么要进行可行性研究&#xff1f;应该从哪些方面研究目标系统的可行性&#xff1f;2. 为方便储户&#xff0c;某银行拟开发计算机储蓄系统。储户填写的存款单或取款单由业务员输入系统&#xff0c;如果是存款&#xff0c;系统记录存款人姓名…...

[软件工程导论(第六版)]第3章 需求分析(课后习题详解)

文章目录1. 为什么要进行需求分析&#xff1f;通常对软件系统有哪些需求&#xff1f;2. 怎样与用户有效地沟通以获取用户的真实需求&#xff1f;3. 银行计算机储蓄系统的工作过程大致如下&#xff1a;储户填写的存款单或取款单由业务员输入系统&#xff0c;如果是存款则系统记录…...

基于分布鲁棒联合机会约束的能源和储备调度(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客 &#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜…...

ETL和数据建模

一、什么是ETL ETL是数据抽取&#xff08;Extract&#xff09;、转换&#xff08;Transform&#xff09;、加载&#xff08;Load &#xff09;的简写&#xff0c;它是将OLTP系统中的数据经过抽取&#xff0c;并将不同数据源的数据进行转换、整合&#xff0c;得出一致性的数据&…...

ccc-pytorch-回归问题(1)

文章目录1.简单回归实战&#xff1a;2.手写数据识别1.简单回归实战&#xff1a; 用 线性回归拟合二维平面中的100个点 公式&#xff1a;ywxbywxbywxb 损失函数&#xff1a;∑(yreally−y)2\sum(y_{really}-y)^2∑(yreally​−y)2 迭代方法&#xff1a;梯度下降法&#xff0c;…...

【JAVA八股文】框架相关

框架相关1. Spring refresh 流程2. Spring bean 生命周期3. Spring bean 循环依赖解决 set 循环依赖的原理4. Spring 事务失效5. Spring MVC 执行流程6. Spring 注解7. SpringBoot 自动配置原理8. Spring 中的设计模式1. Spring refresh 流程 Spring refresh 概述 refresh 是…...

二叉树的相关列题!!

对于二叉树&#xff0c;很难&#xff0c;很难&#xff01;笔者也是感觉很难&#xff01;虽然能听懂课程&#xff0c;但是&#xff0c;对于大部分的练习题并不能做出来&#xff01;所以感觉很尴尬&#xff01;&#xff01;因此&#xff0c;笔者经过先前的那篇博客&#xff0c;已…...

Java设计模式 - 原型模式

简介 原型模式&#xff08;Prototype Pattern&#xff09;是用于创建重复的对象&#xff0c;同时又能保证性能。这种类型的设计模式属于创建型模式&#xff0c;它提供了一种创建对象的最佳方式。 这种模式是实现了一个原型接口&#xff0c;该接口用于创建当前对象的克隆。当直…...

深度学习中的 “Hello World“

Here’s an interesting fact—Each month, there are 186.000 Google searches for the keyword “deep learning.” 大家好✨,这里是bio🦖。每月有超18万的人使用谷歌搜索深度学习这一关键词,是什么让人们对深度学习如此感兴趣?接下来请跟随我来揭开深度学习的神秘面纱。…...

谷歌浏览器插件

项目中有时候会用到插件 sync-cookie-extension1.0.0&#xff1a;开发环境同步测试 cookie 至 localhost&#xff0c;便于本地请求服务携带 cookie 参考地址&#xff1a;https://juejin.cn/post/7139354571712757767 里面有源码下载下来&#xff0c;加在到扩展即可使用FeHelp…...

【机器视觉】单目测距——运动结构恢复

ps&#xff1a;图是随便找的&#xff0c;为了凑个封面 前言 在前面对光流法进行进一步改进&#xff0c;希望将2D光流推广至3D场景流时&#xff0c;发现2D转3D过程中存在尺度歧义问题&#xff0c;需要补全摄像头拍摄图像中缺失的深度信息&#xff0c;否则解空间不收敛&#xf…...

工业自动化时代的精准装配革新:迁移科技3D视觉系统如何重塑机器人定位装配

AI3D视觉的工业赋能者 迁移科技成立于2017年&#xff0c;作为行业领先的3D工业相机及视觉系统供应商&#xff0c;累计完成数亿元融资。其核心技术覆盖硬件设计、算法优化及软件集成&#xff0c;通过稳定、易用、高回报的AI3D视觉系统&#xff0c;为汽车、新能源、金属制造等行…...

成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战

在现代战争中&#xff0c;电磁频谱已成为继陆、海、空、天之后的 “第五维战场”&#xff0c;雷达作为电磁频谱领域的关键装备&#xff0c;其干扰与抗干扰能力的较量&#xff0c;直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器&#xff0c;凭借数字射…...

tree 树组件大数据卡顿问题优化

问题背景 项目中有用到树组件用来做文件目录&#xff0c;但是由于这个树组件的节点越来越多&#xff0c;导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多&#xff0c;导致的浏览器卡顿&#xff0c;这里很明显就需要用到虚拟列表的技术&…...

MySQL账号权限管理指南:安全创建账户与精细授权技巧

在MySQL数据库管理中&#xff0c;合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号&#xff1f; 最小权限原则&#xf…...

重启Eureka集群中的节点,对已经注册的服务有什么影响

先看答案&#xff0c;如果正确地操作&#xff0c;重启Eureka集群中的节点&#xff0c;对已经注册的服务影响非常小&#xff0c;甚至可以做到无感知。 但如果操作不当&#xff0c;可能会引发短暂的服务发现问题。 下面我们从Eureka的核心工作原理来详细分析这个问题。 Eureka的…...

基于Springboot+Vue的办公管理系统

角色&#xff1a; 管理员、员工 技术&#xff1a; 后端: SpringBoot, Vue2, MySQL, Mybatis-Plus 前端: Vue2, Element-UI, Axios, Echarts, Vue-Router 核心功能&#xff1a; 该办公管理系统是一个综合性的企业内部管理平台&#xff0c;旨在提升企业运营效率和员工管理水…...

为什么要创建 Vue 实例

核心原因:Vue 需要一个「控制中心」来驱动整个应用 你可以把 Vue 实例想象成你应用的**「大脑」或「引擎」。它负责协调模板、数据、逻辑和行为,将它们变成一个活的、可交互的应用**。没有这个实例,你的代码只是一堆静态的 HTML、JavaScript 变量和函数,无法「活」起来。 …...

渗透实战PortSwigger靶场:lab13存储型DOM XSS详解

进来是需要留言的&#xff0c;先用做简单的 html 标签测试 发现面的</h1>不见了 数据包中找到了一个loadCommentsWithVulnerableEscapeHtml.js 他是把用户输入的<>进行 html 编码&#xff0c;输入的<>当成字符串处理回显到页面中&#xff0c;看来只是把用户输…...