ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Navicat执行计划分析:数据库性能调优的核心技能

Navicat执行计划分析:数据库性能调优的核心技能 1. 项目概述为什么数据库老手都离不开执行计划分析如果你用过Navicat大概率知道它能连数据库、写SQL、导数据。但很多朋友可能没意识到Navicat里藏着一个对数据库性能调优至关重要的“神器”——执行计划查看器。我干了十多年后端开发和DBA的活儿处理过无数慢查询可以很负责任地说看不懂执行计划数据库优化就相当于蒙着眼睛开车。Navicat把这个功能做得非常直观图形化界面降低了上手门槛但它背后代表的是一整套SQL语句在数据库内部如何被拆解、优化和执行的逻辑。今天我就以一个老司机的视角带你彻底搞懂如何在Navicat里看执行计划不仅告诉你按钮在哪点更要讲清楚每一行输出背后的含义以及如何根据这些信息去优化你的SQL让查询速度提升一个数量级。简单来说执行计划就是数据库优化器为你提交的SQL语句制定的一份“作战地图”。它决定了数据库是先扫描全表还是走索引是多个表先关联谁是用嵌套循环还是哈希连接。在Navicat里查看这份地图能让你直观地发现SQL的“瓶颈”在哪里。无论是刚入行的开发还是负责系统稳定性的运维掌握这个技能都能让你在定位数据库性能问题时从“猜”变成“有据可依”。接下来我会从原理到实操从解读到优化手把手带你玩转Navicat的执行计划分析功能。2. Navicat中执行计划功能的入口与界面解析2.1 如何找到并启用执行计划分析在Navicat中查看执行计划主要有两种最常用的方式适用于不同的场景。第一种方式也是我最推荐的日常分析方式是在查询编辑器中直接操作。你打开Navicat连接到目标数据库比如MySQL然后新建一个查询窗口或者打开一个已有的SQL文件。写好或者粘贴进你要分析的SQL语句后别急着点那个绿色的“运行”按钮。注意看工具栏通常紧挨着“运行”按钮你会找到一个类似“闪电”旁边有个“放大镜”图标的按钮它的提示文本一般是“解释”或“解释已选择的”。选中你的SQL语句或者把光标放在语句中点击这个按钮。另一种方式适用于你已经运行了一个慢查询想回头分析它的场景。在Navicat的“历史记录”或者“服务器监控”面板里找到那条执行缓慢的SQL语句右键点击通常也能在上下文菜单中找到“解释”或“查看执行计划”的选项。这种方式能让你对生产环境已经发生的问题进行事后分析非常有用。注意不同数据库类型MySQL、PostgreSQL、Oracle等和不同版本的Navicat按钮位置和名称可能略有差异但核心功能图标放大镜、闪电、图表通常是一致的。如果找不到可以直接在Navicat的帮助菜单里搜索“解释”或“EXPLAIN”。点击“解释”后Navicat不会执行你的SQL去修改或获取数据而是会向数据库发送一个EXPLAIN命令对于MySQL或等价的语句。数据库会返回该SQL的执行计划Navicat则将其以图形化或表格的形式展示在结果面板的下方。这里有一个关键细节对于MySQLEXPLAIN后面可以跟FORMATTRADITIONAL表格、FORMATJSON详细JSON或FORMATTREE树形8.0.16等格式。Navicat的图形化界面本质上是将这些格式的信息进行了可视化渲染让你看得更直观。2.2 执行计划结果面板的布局与核心信息区Navicat展示执行计划的结果面板通常分为几个关键区域理解每个区域的作用是正确解读计划的前提。最经典的是“表格视图”和“图形视图”很多版本支持切换。1. 表格视图这是最原始、信息最全的视图。它严格对应数据库EXPLAIN语句返回的每一列。以MySQL为例你会看到类似下面的列id: 查询中SELECT语句的执行顺序号。id相同执行顺序从上到下id不同id值越大优先级越高越先执行。select_type: 查询类型。这是判断查询复杂度的关键比如是简单查询SIMPLE、子查询SUBQUERY、派生表DERIVED还是联合查询UNION。table: 当前行正在访问哪个表。partitions: 匹配的分区如果表没分区就是NULL。type:访问类型这是性能判断的重中之重。它表示数据库决定如何查找表中的行。从最优到最差大致是systemconsteq_refrefrangeindexALL。我们追求至少是range级别避免出现ALL全表扫描。possible_keys: 可能用到的索引。key: 实际决定使用的索引。如果为NULL则表示未使用索引。key_len: 使用的索引的长度。可以用来判断索引是否被完全利用。ref: 显示索引的哪一列被使用了如果可能的话是一个常数。rows:数据库估算需要扫描的行数。这个数字是预估值但它是判断查询效率的直接依据越小越好。filtered: 表示存储引擎返回的数据在服务器层过滤后剩余多少百分比的数据。越低越好。Extra:额外信息包含SQL执行的一些重要细节。比如Using where在存储引擎层检索行后再进行过滤、Using index使用了覆盖索引性能极佳、Using temporary使用了临时表需警惕、Using filesort使用了文件排序性能杀手。2. 图形视图这是Navicat的亮点它将复杂的表格数据转化为流程图。每个方框代表一个操作如全表扫描、索引查找、连接方框之间的箭头代表数据流的方向。图形视图的优势在于一目了然地看清整个查询的执行顺序和数据流向。通常执行成本最高的操作比如全表扫描会用更醒目的颜色如红色或深色标出。你可以点击每个图形节点查看其详细的属性这些属性就对应表格视图中的各列信息。对于复杂的多表关联查询图形视图比表格视图友好得多。3. 原始信息/JSON视图一些高级版本的Navicat还会提供EXPLAIN语句返回的原始JSON或文本格式。这对于深度调优非常有用因为图形和表格视图可能省略了一些极其细节的信息。当你需要精确分析索引使用情况、成本估算细节时查看原始JSON是最终手段。我的实操心得是先用图形视图快速定位瓶颈操作那个颜色最深、块最大的部分然后切换到表格视图聚焦到对应行仔细分析它的type、key、rows、Extra列。两者结合效率最高。3. 深度解读执行计划中的关键性能指标3.1 访问类型type判断数据检索效率的黄金标准type列是执行计划中第一个要看的指标它直接反映了数据库引擎为了找到所需数据打算如何“翻找”这张表。理解每种类型的含义和性能差异是优化SQL的基石。system const这是最优级别。system是const的特例表示表只有一行数据。const表示通过主键或唯一索引的一次查找就能定位到唯一一行。例如SELECT * FROM user WHERE id 1id是主键。这类查询速度极快性能开销可忽略不计。eq_ref在多表连接时出现对于前一个表的每一行在当前表中只匹配到唯一一行。通常出现在使用主键或非空唯一索引进行关联时。例如SELECT * FROM orders JOIN users ON orders.user_id users.id其中users.id是主键。这也是非常高效的连接类型。ref比eq_ref稍差表示使用非唯一索引进行查找可能返回多个匹配行。例如SELECT * FROM users WHERE name ‘张三‘name字段上有一个普通索引。如果返回行数不多性能也很好。range表示只检索给定范围内的行使用了索引来限制扫描的范围。常见于BETWEEN、、、IN()等操作。例如SELECT * FROM logs WHERE create_time BETWEEN ‘2023-01-01‘ AND ‘2023-01-31‘create_time上有索引。这是需要争取达到的级别因为它避免了全索引扫描。index全索引扫描。它比全表扫描ALL好一点因为只需要遍历索引树而索引文件通常比数据文件小。但它仍然是扫描了整个索引当需要的数据量很大时效率也不高。Extra列如果出现Using index则可能是覆盖索引扫描性能尚可否则就需要警惕。ALL全表扫描性能最差。意味着数据库需要逐行读取整个表的数据来找到匹配的行。对于大表这将是灾难性的。优化SQL的首要目标就是通过添加合适的索引或改写查询条件将ALL优化为range或更好的类型。实操心得在图形视图中如果某个表操作的type是ALLNavicat通常会将其标记为红色或深色警告。你的优化工作就应该从这个红色的“瘤子”开始切。检查这个表是否在WHERE、JOIN ON、ORDER BY或GROUP BY子句中用到了字段并为这些字段考虑建立复合索引。3.2 扫描行数rows与过滤比例filtered量化查询成本rows和filtered这两个值放在一起看能帮你更精确地估算查询的实际开销。rows是MySQL优化器预估的需要检查的行数。注意是预估不是实际值。这个值基于表的统计信息如索引基数。如果统计信息过期这个预估值可能会严重偏离实际导致优化器选择错误的执行计划。你可以通过ANALYZE TABLE table_name;命令来更新统计信息。filtered列表示存储引擎返回的数据在Server层经过WHERE条件等其他过滤后剩下多少百分比的数据。它是一个百分比值范围从0到100。这个值越低说明在存储引擎层返回的无效数据越多查询效率可能越差。举个例子假设一个查询计划显示对表A的访问typeindexrows10000filtered10%。这意味着优化器预估会扫描索引的10000行但最终只有大约1000行10000 * 10%满足所有条件。如果这个filtered值很低比如只有1%那你就要思考是不是在存储引擎层利用索引过滤掉的条件太少了是否可以通过调整索引顺序在复合索引中把过滤性高的字段放前面或者优化查询写法让更多的过滤工作在索引层面完成从而减少向上层传递的数据量我的经验是一个理想的执行计划应该是在type尽可能优的前提下rows * (filtered/100)的乘积尽可能小。这个乘积可以粗略理解为最终需要处理的有效数据行数。优化就是要减小这个乘积。3.3 额外信息Extra揭示隐藏的性能细节与陷阱Extra列包含了许多执行过程中的细节信息是发现潜在性能问题的“显微镜”。这里列举几个最常见且关键的值Using index覆盖索引。这意味着查询的所有列都包含在所使用的索引中因此引擎只需读取索引而无需回表查询数据行。这是性能最好的情况之一。例如有一个索引idx_name_age (name, age)查询SELECT name, age FROM users WHERE name ‘Tom‘就可以用到覆盖索引。Using where这表示Server层在从存储引擎拿到行数据后还需要应用WHERE子句中的其他条件进行过滤。如果type是ALL或index且Using where说明索引没能完全覆盖查询条件性能有优化空间。你应该检查是否能为WHERE中的条件建立更合适的索引。Using temporary表示为了执行查询需要创建一张临时表来保存中间结果。这通常发生在GROUP BY和DISTINCT操作且没有索引可以利用时。创建临时表通常涉及磁盘I/O如果临时表很大会严重降低性能。优化思路是为GROUP BY或ORDER BY的字段建立索引。Using filesort文件排序。当无法利用索引完成排序操作时MySQL就需要在内存或磁盘上进行排序。filesort是性能杀手尤其是当排序数据量很大时。优化目标是让ORDER BY和GROUP BY子句能够利用索引的有序性。例如查询SELECT * FROM logs ORDER BY create_time DESC如果在create_time上有索引就可能避免filesort。Using join buffer表示连接查询时使用了连接缓冲区Block Nested-Loop或Batched Key Access。当被驱动表非第一张表没有可用索引时可能会出现这个提示。它虽然是一种优化手段但也暗示了连接条件可能缺少索引理想情况应该是通过索引直接定位。在Navicat的表格视图中Extra信息会直接显示在对应行的列里。在图形视图中你可能需要点击具体的操作节点在属性详情中查看。看到Using temporary和Using filesort一定要打起十二分精神它们往往是拖慢查询的元凶。4. 基于Navicat执行计划的SQL优化实战案例4.1 案例一告别全表扫描为高频查询字段添加索引假设我们有一张用户订单表orders约100万行数据。经常需要查询某个用户最近一个月的订单。初始SQL如下SELECT * FROM orders WHERE user_id 10086 AND create_time ‘2024-03-01‘;在Navicat中查看其执行计划发现对orders表的访问type是ALLrows预估为98万key为NULLExtra显示Using where。图形视图里这个表操作被高亮为红色。问题诊断这明显是全表扫描。因为WHERE条件中的user_id和create_time字段上没有建立任何索引数据库只能遍历所有行来筛选。优化方案为user_id和create_time创建复合索引。这里有个顺序选择的问题。由于user_id是等值查询而create_time是范围查询根据最左前缀原则和等值优先的经验将等值条件user_id放在前面更高效。CREATE INDEX idx_user_time ON orders(user_id, create_time);创建索引后再次在Navicat中查看执行计划。你会发现type变成了range因为create_time是范围查询key显示为idx_user_timerows预估值大幅下降可能只有几十或几百行取决于用户10086的订单数Extra可能变为Using index condition。图形视图中的红色警告也消失了。查询速度会有数百甚至上千倍的提升。注意事项创建索引不是免费的它会增加写操作INSERT/UPDATE/DELETE的开销因为索引也需要维护。同时索引也占用磁盘空间。需要权衡读写比例。对于这个案例如果user_id的筛选度很高即每个用户的订单数远小于总订单数这个复合索引的收益会非常巨大。4.2 案例二消除文件排序与临时表优化GROUP BY和ORDER BY假设我们需要统计每天的下单用户数并按日期倒序排列。初始SQLSELECT DATE(create_time) as order_date, COUNT(DISTINCT user_id) as user_count FROM orders GROUP BY DATE(create_time) ORDER BY order_date DESC;在Navicat中查看执行计划可能会看到type是index或ALL取决于是否有索引并且在Extra列中同时出现了Using temporary和Using filesort。问题诊断GROUP BY DATE(create_time)操作无法利用现有索引如果索引是(create_time)对DATE(create_time)函数计算后的结果分组索引失效导致需要创建临时表来分组。ORDER BY order_date DESC也无法利用索引排序导致额外的文件排序。优化方案有两种思路。建立表达式索引MySQL 8.0或生成列索引如果数据库版本支持可以为DATE(create_time)创建索引。-- MySQL 8.0 可以创建函数索引 CREATE INDEX idx_date ON orders ( (DATE(create_time)) );或者新增一个生成列ALTER TABLE orders ADD COLUMN order_date DATE AS (DATE(create_time)) STORED; CREATE INDEX idx_order_date ON orders(order_date);然后SQL改为GROUP BY order_date。利用现有索引改写查询如果create_time上有索引我们可以利用索引本身就是按时间排序的特性。但GROUP BY和ORDER BY顺序不同。一个变通方法是如果业务允许先按日期正序分组在应用层再反转顺序。或者如果数据量可以接受使用子查询或变量来避免排序。更通用的优化是确保GROUP BY和ORDER BY的字段和顺序完全一致并且有索引支持。例如如果索引是(create_time)那么GROUP BY create_time ORDER BY create_time就可以利用索引。对于这个案例假设我们采用生成列方案。优化后再次查看执行计划Using temporary和Using filesort应该会消失type变为index全索引扫描但因为是覆盖索引且数据量可能不大可以接受查询效率显著提升。4.3 案例三优化多表JOIN理解驱动表与被驱动表考虑一个博客系统的查询获取某用户的所有文章及其评论数。涉及users、articles、comments三张表。SELECT u.username, a.title, a.content, COUNT(c.id) as comment_count FROM users u JOIN articles a ON u.id a.user_id LEFT JOIN comments c ON a.id c.article_id WHERE u.id 123 GROUP BY a.id;在Navicat中查看执行计划图形视图会清晰地展示三张表的连接顺序和数据流向。你需要关注哪张表是驱动表通常优化器会选择rows预估最小的表作为驱动表第一张表。这里因为WHERE u.id 123users表通过主键查找rows1它极有可能被选为驱动表。连接效率如何查看articles表与users表的连接类型。理想情况是eq_ref通过a.user_id关联u.id主键。查看comments表与articles表的连接类型理想是ref通过c.article_id关联a.id。是否有性能瓶颈检查每张表的type和rows。如果comments表针对article_id的查询是ALL那就需要为comments.article_id添加索引。优化实践根据执行计划我们确保users.id是主键。articles.user_id上有索引外键通常自带索引但需确认。comments.article_id上有索引。如果GROUP BY a.id导致临时表考虑articles.id是否是主键已经是或者查询是否可以避免GROUP BY例如在应用层聚合。在Navicat中你可以通过调整JOIN顺序虽然优化器最终会决定、添加缺失索引然后反复对比执行计划的变化来深刻理解优化器的工作逻辑。图形视图在这里的优势巨大它能让你直观地看到添加一个索引后数据流是如何从“肥胖”的全表扫描箭头变成“纤细”的索引查找箭头的。5. 高级技巧与Navicat特定功能助力深度调优5.1 对比分析保存与比较不同版本SQL的执行计划这是Navicat一个非常实用的功能尤其在进行A/B测试优化方案时。当你对一条SQL进行了多种改写比如调整了WHERE条件顺序、添加了HINT、新建了不同结构的索引如何科学地对比哪种方案最优光凭感觉和单次执行时间是不准的因为缓存和系统负载会影响时间。在Navicat中你可以这样做在查询编辑器写好原始SQL点击“解释”得到执行计划A。修改SQL或修改表结构如添加索引再次点击“解释”得到执行计划B。Navicat通常会在新的标签页或面板中打开新的解释结果。你可以并排打开两个结果窗口直接对比rows、type、key等关键指标。更细致的方法是将两个执行计划的结果特别是表格视图导出为CSV或文本用对比工具进行逐行比较。关注rows预估值的下降幅度、type的优化如从ALL到ref、以及Extra中警告信息的消失如Using filesort。通过这种对比你可以量化优化效果并理解每一种修改是如何影响优化器决策的。例如你可能会发现为(a,b)建复合索引比分别为a和b建两个单列索引在执行计划上带来的提升是质的飞跃。5.2 结合性能分析工具EXPLAIN ANALYZE 的实际应用标准的EXPLAIN展示的是优化器预估的执行计划。从MySQL 8.0.18开始引入了EXPLAIN ANALYZE语句它会实际执行查询并返回实际执行过程中的详细统计数据包括各步骤的实际耗时、实际返回行数等。这对于验证优化器预估是否准确、发现实际执行中的瓶颈至关重要。在Navicat较新的版本中通常支持MySQL 8.0你可能可以直接运行EXPLAIN ANALYZE语句。在查询窗口输入EXPLAIN ANALYZE SELECT * FROM your_table WHERE your_condition;然后执行。结果会以表格和树形文本结合的形式返回其中包含了诸如actual time0.1..100.5表示该节点实际执行时间范围、rows1000实际返回行数等关键信息。如何解读对比EXPLAIN的rows预估和EXPLAIN ANALYZE的rows实际。如果差异巨大说明表的统计信息可能已经过时需要运行ANALYZE TABLE。查看actual time找到耗时最长的节点那就是你下一步要重点优化的目标。例如你可能发现一个预估rows100的嵌套循环连接实际执行却花了90%的时间因为内层查询每次都要扫描大量数据这提示你需要为内层查询添加更好的索引。实操心得EXPLAIN ANALYZE会真正执行查询所以千万不要在生产环境的大表上直接运行一个可能很慢的查询。最好在测试环境或者使用LIMIT子句限制数据量进行测试。Navicat的这个功能让你可以在一个安全的 GUI 环境下直观地获得真实的性能数据是高级调优的利器。5.3 可视化查询构建与执行计划预估对于不熟悉复杂SQL写法的开发者Navicat的“查询构建器”功能非常友好。你可以通过拖拽表、勾选字段、可视化设置连接条件和过滤条件来构建查询。但很多人不知道的是在构建查询的过程中你同样可以随时查看这个“半成品”SQL的执行计划。操作流程打开查询构建器添加表并设置好初步的关联和条件后不要直接生成SQL并执行。可以先点击“解释”按钮如果构建器界面有或者将构建器生成的SQL代码复制到查询编辑器窗口再进行解释。这能让你在早期阶段就发现潜在的性能问题。比如你刚把两个大表拖进来建立连接还没加任何WHERE条件查看执行计划可能就已经是可怕的ALLALL的笛卡尔积预估。这会立刻提醒你必须添加有效的关联条件和筛选条件。这个功能的价值在于将性能优化的意识前置到了查询设计阶段而不是等到SQL写完、跑起来慢吞吞的时候才去补救。尤其对于复杂报表的SQL编写边设计边检查执行计划能避免很多后期返工。6. 常见问题排查与Navicat使用技巧实录6.1 为什么执行计划显示用了索引查询还是很慢这是最常遇到的困惑之一。在Navicat里明明看到key列显示了索引名type也可能是ref或range但查询响应就是不尽如人意。可能的原因有索引选择性差索引列的值重复度太高。例如在“性别”字段上建索引因为只有‘M‘/‘F‘两种值索引树无法有效过滤数据数据库可能宁愿全表扫描。在Navicat的执行计划里虽然用了索引但rows预估值可能依然很大。解决方案是考虑在选择性高的列上建索引或建立复合索引。回表开销大虽然使用了索引查找typeref但查询需要返回的列SELECT *或包含非索引列不在索引中。数据库需要根据索引找到主键ID再回表到主键索引聚簇索引中取出整行数据。如果满足条件的行数很多比如几千上万行大量的随机I/O会拖慢速度。这就是为什么提倡使用覆盖索引Extra: Using index。索引碎片化表经过大量增删改后索引页可能变得不连续导致物理读增多。可以在Navicat中通过维护工具或执行OPTIMIZE TABLE table_name;对于InnoDB实质是重建表来整理碎片。统计信息不准优化器基于rows的预估值选择索引。如果统计信息过期优化器可能错误地选择了一个低效的索引。在Navicat中你可以对表右键 - “维护” - “分析表”来更新统计信息然后重新查看执行计划。排查时在Navicat中关注rows和filtered的值。如果rows很大或者filtered很小但rows不小即使用了索引性能也可能不佳。同时查看Extra列是否有Using index conditionICP索引条件下推是好的或Using where需要回表后过滤是潜在瓶颈。6.2 Navicat图形视图中的“成本”百分比是什么意思在Navicat的图形化执行计划视图中每个操作节点旁边或内部经常会显示一个百分比数字例如“Cost: 100%”或“Cost: 0.2%”。这个“成本”是Navicat根据数据库返回的执行计划信息主要是rows等计算出的一个相对估算值用于直观地告诉你哪个操作是查询中最耗资源的部分。重要提示这个成本百分比不是数据库优化器内部使用的绝对成本值也不是实际的执行时间占比。它是Navicat为了可视化效果而做的一种归一化呈现。通常它将整个查询的总成本视为100%然后根据各步骤的预估行数、操作类型等因子分配一个相对的权重。它的核心作用是快速定位瓶颈。你不需要纠结于“为什么这个节点是76.5%而不是78%”而应该关注那个成本占比最高的节点比如一个占80%成本的全表扫描。优化工作就应该聚焦于这个“最胖”的节点。尝试为它添加索引、改写查询条件然后再次查看执行计划你会发现这个节点的成本百分比会显著下降整个查询的图形颜色也会变得更“健康”。6.3 执行计划不稳定有时快有时慢怎么办这种现象通常源于统计信息不准确或优化器选择了不同的执行计划。在Navicat中你可能会发现同一条SQL在不同时间执行得到的执行计划尤其是type、key不一样。排查与解决步骤确认现象在Navicat中多次运行EXPLAIN最好在业务低峰期和高峰期各试几次保存不同的执行计划结果进行对比。更新统计信息对相关表执行ANALYZE TABLE。这是第一步也是最简单的一步。过时的统计信息是导致优化器“抽风”的主要原因。检查数据分布如果WHERE条件中的值分布不均匀优化器可能因为传入不同的值而选择不同的计划。例如WHERE status ‘active‘可能返回90%的行优化器选择全表扫描而WHERE status ‘deleted‘只返回1%的行优化器选择索引扫描。这是正常现象。考虑使用优化器提示如果经过分析你确信某一种执行计划始终是最优的而优化器偶尔会选错可以考虑在SQL中使用优化器提示Hints来固定执行计划。例如在MySQL中你可以使用FORCE INDEX (index_name)来强制使用某个索引。但这是最后的手段因为数据分布变化后强制索引可能反而会变慢。在Navicat中编写SQL时可以直接加入这些提示。检查系统变量某些MySQL系统变量如optimizer_switch会影响优化器的行为。除非你非常了解否则不要轻易修改生产环境的全局设置。我的经验是对于核心的、频繁执行的SQL定期比如每周在测试环境用真实数据快照更新统计信息并检查其执行计划是一个好的习惯。Navicat的任务调度功能可以帮你自动化这个过程定期运行ANALYZE TABLE和特定的EXPLAIN语句并将结果保存或发送通知。掌握在Navicat中查看和分析执行计划就像是拿到了数据库查询的“X光片”。它不能直接治病优化但能精准地告诉你病根在哪里。从看懂type和key开始到理解rows和Extra的深意再到利用图形化界面快速定位瓶颈最后通过实战优化让SQL飞起来这是一个数据库使用者走向精通的必经之路。下次当你遇到慢查询时别急着抱怨硬件或数据库先打开Navicat点一下那个“解释”按钮答案很可能就在里面。
返回列表