2023年MySQL实战核心技术第一篇

目录

四 . 基础架构:一条SQl查询语句是如何执行的?

 4.1 MySQL逻辑架构图:

4.2 MySQL的Server层和存储引擎层

4.2.1 连接器

4.2.1.1 解释

4.2.1.2 MySQL 异常重启 解决方案:

4.2.1.2.1. 定期断开长连接:

4.2.1.2.2. 初始化资源:

4.2.2 查询缓存

4.2.2.1 解释

4.2.2.2 解释大部分情况下建议不要使用查询缓存

4.2.3 分析器

4.2.4 优化器

4.2.4.1 例子:

4.2.4.2表的连接顺序优化 详细解释:

4.2.4.2.1 优化效果:

4.2.4.2.1 .1内存占用:

4.2.4.2.1.2磁盘IO开销:

4.2.5 执行器 

4.2.5.1 解释

4.2.5.2 T表中ID没有索引

4.2.5.4 有无索引的查询详细解释:

4.2.5.5 rows_examined 详细解释:


四 . 基础架构:一条SQl查询语句是如何执行的?

 我们进行整体的分析,从MySQL的基础架构进行分析。

如我们看到这个查询语句:

mysql> select * from T where ID=10;

我们只能看到输入的一条语句,返回的结果,但是却不知道这条语句在MySQL内部的执行过程。

 4.1 MySQL逻辑架构图:

                                              客服端

 MySQL可以Server层和存储引擎两部分。

4.2 MySQL的Server层和存储引擎层

Server层 :

包括连接器,查询缓存,分析器,优化器,执行器等,Server层还提供了许多内置函数,例如日期、时间、数学和加密函数等,用于方便地进行数据处理和计算,Server层实现的功能不依赖于具体的存储引擎,因此可以在不同的存储引擎之间共享和使用,这些功能包括存储过程、触发器、视图等,它们可以跨存储引擎使用,为数据库提供了更强大和灵活的功能。

详细解释:

存储过程、触发器和视图是数据库中的重要功能,它们可以在数据操作和管理中提供更高级的逻辑和灵活性。

存储过程是一段预定义的代码块,可以被调用执行。它允许开发人员将一系列的SQL语句和逻辑组织起来,形成一个可重复使用的模块。通过存储过程,可以降低网络通信的开销、提高性能,并且可以实现复杂的业务逻辑。

触发器是与表相关联的特殊类型的存储过程。它可以在指定的事件(例如插入、更新或删除数据)发生时自动触发执行一段代码。触发器允许我们在数据的变动发生时,对其进行额外的处理,实现数据完整性约束、审计跟踪等功能。

视图是虚拟的表,是根据查询语句的结果集创建的。它为用户提供了一种简化和抽象的方式来访问和操作数据,隐藏了底层表的复杂性。通过视图,我们可以实现数据的安全性控制(只显示部分字段)、数据的逻辑组织(合并多个表的数据)以及简化复杂的查询操作。

因为存储过程、触发器和视图等功能在数据库中扮演着重要的角色,并能提供更高级的数据处理和管理能力,所以被包括在MySQL的Server层中,以便跨存储引擎统一实现和使用。

存储引擎层:

负责数据的存储和提取。其架构模式是插件式的,支持InnoDB,MyISAM,Memory等多个存储引擎。目前最常用的引擎是InnoDB,从MySQL 5.5.5版本开始成为了默认存储引擎。

例子:

当我们执行create table 建表的时候,我们不进行指定引擎类型,默认使用的就是InnoDB。

不同的存储引擎共用一个Server层,也就是从连接器到执行器的部分,下面将会进行一遍执行流程的讲解,依次看每个组件的作用。

4.2.1 连接器

4.2.1.1 解释

我们一开始将会先进行连接到这个数据库上,这时候,第一个接待你的就是连接器,连接器负责与客服端建立连接,获取权限,维持和管理连接。

连接命令:

mysql -h$ip -P$port -u$user -p

 输入命令后我们将需要在交互对话中输入密码。

也可以直接在-p后写,直接放到命令行,但是这样可能会导致密码泄露,尤其是连接的生产服务器。

 连接命令中的mysql是客户端的工具,用来跟服务器建立连接。在进行经典的TCP握手后,连接器将进行认证身份,使用的是用户名和密码。

不正确的话,会收到Access denied for user的错误,然后客户端程序结束,如果正确的话,连接器将会进入到权限表中查询你拥有的权限,之后的连接里面的权限判断逻辑,都依赖此时读到的权限。

即使用管理员账号对这个用户进行了权限修改,也不会影响已经存在连接的权限。

修改完成后,只有再新建的连接才会使用新的权限设置。
连接完成后,如果你没有后续的动作,这个连接就处于空闲状态,你可以在 show processlist
命令中看到它。文本中这个图是 show processlist 的结果,其中的 Command 列显示为
“Sleep”的这一行,就表示现在系统里面有一个空闲连接

 客户端如果太长时间没动静,连接器就会自动将它断开。这个时间是由参数 wait_timeout 控
制的,默认值是 8 小时。
如果在连接被断开之后,客户端再次发送请求的话,就会收到一个错误提醒: Lost
connection to MySQL server during query。这时候如果你要继续,就需要重连,然后再执
行请求了。

数据库里面,长连接是指连接成功后,如果客户端持续有请求,则一直使用同一个连接。短连
接则是指每次执行完很少的几次查询就断开连接,下次查询再重新建立一个。
建立连接的过程通常是比较复杂的,建议在使用中要尽量减少建立连接的动作,也就
是尽量使用长连接。
但是全部使用长连接后,你可能会发现,有些时候 MySQL 占用内存涨得特别快,这是因为
MySQL 在执行过程中临时使用的内存是管理在连接对象里面的。这些资源会在连接断开的时
候才释放。所以如果长连接累积下来,可能导致内存占用太大,被系统强行杀掉(OOM),
从现象看就是 MySQL 异常重启了。

4.2.1.2 MySQL 异常重启 解决方案:

4.2.1.2.1. 定期断开长连接:

使用一段时间后,或者程序里面判断执行过一个占用内存的大查询后,断开连接,之后再查询重连。

4.2.1.2.2. 初始化资源:

如果是MySQL5.7以上版本,我们可以再执行一个比较大的操作后,通过执行mysql_reset_connection来进行初始化连接资源。过程不需要重连和重新做权限验证,但是会将连接恢复到刚刚创建完时的状态。

4.2.2 查询缓存

4.2.2.1 解释

连接建立完毕后,我们可以进行执行select语句,执行逻辑就会来到第二步,查询缓存

MySQL 拿到一条语句的时候,会先到查询缓存里面看看,看之前是不是执行过这条语句。

之前执行过的语句及其结果可能会以key-value对的形式,被直接缓存在内存中。

key是查询语句,value是查询结果,如果查询能直接在这个缓存里面找到Key,那么这个value就会被直接返回到客户端。

如果语句不在查询缓存中,就会继续后面的执行阶段。执行完成后,执行结果会被存入查询缓存中,如果查询命中缓存,MySQL不需要执行后面的复杂操作,可以直接返回结果,效率很高。

4.2.2.2 解释大部分情况下建议不要使用查询缓存

查询缓存的失效非常频繁,只要有对一个表的更新,这个表上所有的查询缓存都会被清空。因
此很可能你费劲地把结果存起来,还没使用呢,就被一个更新全清空了。

对于更新压力大的数据库来说,查询缓存的命中率会非常低。除非你的业务就是有一张静态表,很长时间才会更新一次。比如,一个系统配置表,那这张表上的查询才适合使用查询缓存。

详细解释:

当对一个表进行更新操作时,包括插入、删除、修改等,该表上所有的查询缓存都会被清空。这意味着即使之前有查询结果被缓存起来了,但只要有更新操作,所有的缓存都会被清空。

这样的缓存失效机制导致了查询缓存命中率的降低,尤其是在更新压力大的数据库环境下。如果数据库经常有更新操作,那么查询缓存很可能无法发挥作用,因为缓存的结果很快就会被清空,需要重新执行查询。

因此,查询缓存更适合应用于静态表或者更新频率较低的表。比如系统配置表这种很长时间才会更新一次的表,查询缓存可以有效地提高查询性能,因为缓存的结果可以长时间保持有效,避免频繁的查询操作。

我们可以在MySQL中将参数query_cache_type设置成为demand,这样对于默认的SQL语句都不在使用查询缓存。

对于你想要进行查询缓存的语句,可以使用SQL_cache 显式指定

例子:

mysql> select SQL_CACHE * from T where ID=10;

 注意:

MySQL 8.0版本直接将查询缓存整块功能进行删掉,8.0开始就没有这个功能了。

4.2.3 分析器

 如果没有命中查询缓存,我们就会进行真正的执行语句。

首先,MySQL需要知道你要做什么,所以会对SQL语句进行解析。

词法分析:

输入的是多个字符串和空格组成的一条SQL语句,MySQL需要识别里面的字符串分别是什么,代表什么,MySQL从你输入的"select"这个关键字识别出来,这是一个查询语句。它也要把字符串“T”
识别成“表名 T”,把字符串“ID”识别成“列 ID”,然后下一步将是语法分析。

语法分析:

根据词法分析的结果,语法分析器会根据语法规
则,判断你输入的这个 SQL 语句是否满足 MySQL 语法。

如果你的语句不对,就会收到“You have an error in your SQL syntax”的错误提醒,比如
下面这个语句 select 少打了开头的字母“s”

mysql> elect * from t where ID=1;
ERROR 1064 (42000): You have an error in your SQL syntax; 

一般语法错误提示将会进行提示第一个错误的位置。 

4.2.4 优化器

经过分析器后,MySQL就知道你要做什么了,在开始执行之前,我们还要进行优化器的处理,优化器是在表里面有多个索引的时候,决定采用哪一个索引,或者在一个语句有多表关联(join)的时候,决定各个表的连接顺序。

4.2.4.1 例子:

SELECT * 
FROM A INNER JOIN B ON A.id = B.user_idWHERE A.name = 'John' AND B.address = '123 Street';

优化器会根据查询语句和表的索引来确定使用哪个索引和连接顺序。

首先,优化器会考虑过滤条件。在这个查询中,我们有两个过滤条件:A.name = 'John' 和 B.address = '123 Street'。其中,A.name = 'John' 使用了表A的列name,并且表A上有一个名为idx_name的索引,因此优化器可能会选择使用idx_name索引来加速查询。

接下来,优化器会考虑连接顺序。在这个查询中,我们有一个INNER JOIN操作,连接条件是A.id = B.user_id。优化器会分析表A和表B之间的关系以及它们的大小、索引等信息,然后决定连接顺序。

假设表A比表B小,那么优化器可能会选择先读取表A,然后根据连接条件A.id = B.user_id来获取匹配的行。由于A.id和B.user_id都没有索引,优化器可能会执行全表扫描来进行连接操作。

但如果表A非常大,而表B非常小,并且idx_user_id索引在表B上能够更好地支持连接操作,那么优化器可能会选择先读取表B,并使用idx_user_id索引来快速定位匹配的行。然后再根据连接条件A.id = B.user_id来获取表A中的相应行。

在多表关联的查询中,优化器会通过分析查询语句、表的大小和索引等信息,来决定使用哪个索引以及表的连接顺序。这样可以最大程度地提高查询性能和效率。

4.2.4.2表的连接顺序优化 详细解释:

多表关联查询中,选择先读取较小的表并根据连接条件获取匹配的行。

4.2.4.2.1 优化效果:
4.2.4.2.1 .1内存占用:

通过先读取较小的表,可以使得该表的数据能够完全加载到内存中。这样,在进行连接操作时,只需要保留相对较小的表的数据和相关索引,可以减少内存占用。而如果先读取较大的表,则可能需要将较大表的数据逐步加载到内存中,会占用更多的内存资源。

4.2.4.2.1.2磁盘IO开销:

通过先读取较小的表,可以减少磁盘IO次数。当进行连接操作时,需要根据连接条件在两个表之间进行数据匹配。如果先读取较小的表,并使用连接条件过滤出匹配的行,可以减少后续连接操作所需处理的数据量。而如果先读取较大的表,则可能需要处理更多的数据行,导致磁盘IO开销增加。

4.2.5 执行器 

4.2.5.1 解释

MySQL通过分析器知道SQl语句要做什么,通过优化器知道了应该怎么做最优,然后进行执行器,开始执行语句。

开始执行的时候将会先进行判断你对这个表T有没有执行查询权限的权限,没有的话,就会返回没有权限的错误,如果进行了命中缓存,我们将在查询缓存返回结果的时候,进行权限验证。

查询也会在优化器之前调用 precheck 验证权限)。

mysql> select * from T where ID=10;
ERROR 1142 (42000): SELECT command denied to user 'b'@'localhost' for table 'T'

 如果具有权限,我们就打开表继续执行,打开表的时候,执行器将会根据表的引擎定义,使用引擎提供的接口。

4.2.5.2 T表中ID没有索引

我们执行器的执行流程就是:


1.调用 InnoDB 引擎接口取这个表的第一行,判断 ID 值是不是 10,如果不是则跳过,如果
是则将这行存在结果集中;
2. 调用引擎接口取“下一行”,重复相同的判断逻辑,直到取到这个表的最后一行。
3. 执行器将上述遍历过程中所有满足条件的行组成的记录集作为结果集返回给客户端。

 至此语句就结束了。

4.2.5.3 有索引的表

则是:

第一次调用的是“取满足条件的第一行”这个接口,
之后循环取“满足条件的下一行”这个接口,这些接口都是引擎中已经定义好的。

4.2.5.4 有无索引的查询详细解释:

有无索引对执行器的执行流程和性能有着重要影响。索引是一种数据结构,用于提高数据库查询操作的速度和效率。在有无索引的情况下,执行器的执行流程会有所区别。

  1. 无索引的表:

    • 执行器会依次读取表中的每一行数据,并判断ID值是否为10。
    • 如果ID不是10,则跳过该行继续读取下一行。
    • 如果ID是10,则将该行数据添加到结果集中。
    • 执行器会持续循环这个过程,直到遍历完整个表。

    由于没有索引的支持,无索引的表需要逐行扫描并判断条件,这称为全表扫描。这种方式在数据量大的情况下,性能较差,因为需要消耗更多的IO和处理资源。

  2. 有索引的表:

    • 执行器会使用索引来加速查询。根据索引的定义和查询条件,执行器可以快速定位满足条件的第一行数据。
    • 之后,执行器会通过引擎接口获取满足条件的下一行数据,直到达到查询结果的要求或遍历完索引。

    有索引的表可以利用索引进行快速定位和筛选数据,避免全表扫描。这样可以大幅提高查询性能,减少IO开销和资源消耗。索引可以根据查询条件的列或列组合来创建,以支持特定的查询操作。

有无索引对执行器的执行流程和性能有显著影响。无索引的表需要逐行扫描并判断条件,而有索引的表可以利用索引快速定位和筛选数据。通过合适地创建和使用索引,可以大幅提高数据库查询的效率。

最后我们可以在数据库慢查询日志中看到一个rows_examined的字段,表示语句执行过程中扫描了多少行,这个值就是执行器每次调用引擎获取数据行的时候累加的。

在有些场景下,执行器调用一次,在引擎内部则扫描了多行,因此引擎扫描行数跟
rows_examined 并不是完全相同的。

4.2.5.5 rows_examined 详细解释:

在某些查询场景下,执行器可能需要扫描多行数据才能完成查询操作。这是因为查询可能需要访问多个表或者涉及复杂的查询条件,执行器需要逐行扫描这些数据以便找到满足查询条件的结果集。可能会出现以下情况:

  1. 聚合查询:当执行聚合查询(例如 SUM、COUNT、AVG)时,执行器需要扫描多行数据并对其进行计算,以得出最终的聚合结果。

  2. 连接查询:当执行连接查询(例如使用 JOIN 操作符连接多个表)时,执行器需要扫描多行来匹配连接条件,并返回满足条件的结果。

  3. 子查询:当执行包含子查询的语句时,在子查询执行过程中可能需要扫描多行数据并将结果传递给外部查询。

这意味着在某些查询场景下,执行器可能需要扫描多行数据以便完成所需的操作,而不仅仅是返回给用户的结果集所包含的行数。

在执行器返回结果集时,引擎会将实际扫描的行数汇总为 rows_examined 并返回给用户。因此,rows_examined 是评估查询性能和优化查询计划的指标之一。如果 rows_examined 远大于实际所需的行数,那么可能需要进一步考虑优化查询条件、添加索引或调整表结构等方法来提高查询性能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/71329.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

EasyPhoto:基于 SD WebUI 的艺术照生成插件来啦!

作者 :wuziheng 背景介绍 最近,基于生成式AI技术批量产出真/像/美的个人写真应用非常受欢迎。同时,随着 Stable Diffusion 领域开源社区的快速发展,社区也涌现了类似 FaceChain 的开源项目,帮助开发者开发个性化的真…

docker-compose 部署 Seata整合nacos,Postgresql 为DB存储

docker-compose 部署 Seata整合nacos,Postgresql 为DB存储 环境 详情环境可参考 https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E 我这里 <spring.cloud.alibaba-version>2021.1</spring.cloud.alibaba-version>所…

高压放大器在机械制造领域的应用有哪些

在机械制造领域&#xff0c;高压放大器扮演着至关重要的角色。它们被广泛应用于各种机械设备和系统中&#xff0c;提供高压力、高精度的电信号放大。下面安泰电子将详细介绍高压放大器在机械制造领域的几个关键应用。 材料测试和强度试验 高压放大器广泛应用于材料测试和强度试…

基于Python开发的DIY字符画程序(源码+可执行程序exe文件+程序配置说明书+程序使用说明书)

一、项目简介 本项目是一套基于Python开发的DIY字符画程序&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的Python学习者。 包含&#xff1a;项目源码、项目文档、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&…

I.MX RT1176笔记(9)-- 程序异常追踪(CmBacktrace 和 segger rtt)

前言 在使用 ARM Cortex-M 系列 MCU时候&#xff0c;有时候会遇到各种异常&#xff08;Hard Fault, Memory Management Fault, Bus Fault, Usage Fault, Debug Fault&#xff09;&#xff0c;这时候我们根据经验查询PC指针&#xff0c;LR寄存器&#xff0c;堆栈数据定位地址然…

Apache Tomcat漏洞复现

文章目录 弱口令启动环境漏洞复现 本地文件包含启动环境漏洞复现 弱口令 启动环境 来到vulhub/tomcat/tomcat8/靶场 cd vulhub/tomcat/tomcat8/安装环境并启动&#xff1a; sudo docker-compose up -d && sudo docker-compose up -d修改端口后启动&#xff1a; su…

2023高教社杯 国赛数学建模E题思路 - 黄河水沙监测数据分析

1 赛题 E 题 黄河水沙监测数据分析 黄河是中华民族的母亲河。研究黄河水沙通量的变化规律对沿黄流域的环境治理、气候变 化和人民生活的影响&#xff0c; 以及对优化黄河流域水资源分配、协调人地关系、调水调沙、防洪减灾 等方面都具有重要的理论指导意义。 附件 1 给出了位…

JavaScript-----DOM元素

目录 前言&#xff1a; 1. DOM介绍 2. 获取节点 3. 操作HTML内容 4. 监听事件 案例 5. 操作节点的标签属性 6. 操作样式 7. 创建、添加、删除节点 前言&#xff1a; 在此之前我们要想去操作网页元素一般是去通过CSS选择器实现的&#xff0c;今天我们就学习JavaScript里…

无法将类型为“Newtonsoft.Json.Linq.JObject”的对象转换为类型“Newtonsoft.Json.Linq.JArray”解决方法

对于“Newtonsoft.Json.Linq.JObject”的对象强制类型转换为类型“Newtonsoft.Json.Linq.JArray”报错 第一的图为对象{“*************”:“********”} 第二个图片为数组[{“…”:“…”}] 在我这里进行强制转换对象转换为类型“Newtonsoft.Json.Linq.JArray”报错. 那我们…

postgresql-集合运算

postgresql-集合运算 并集交集差集集合运算符的优先级 并集 create table excellent_emp( year int not null, emp_id integer not null, constraint pk_excellent_emp primary key(year,emp_id) );insert into excellent_emp values(2018,9); insert into excellent_emp value…

Linux系统中实现便捷运维管理和远程访问的1Panel部署方法解析

文章目录 前言 前言 1Panel 是一个现代化、开源的 Linux 服务器运维管理面板。高效管理,通过 Web 端轻松管理 Linux 服务器&#xff0c;包括主机监控、文件管理、数据库管理、容器管理等下面我们介绍在Linux 本地安装1Panel 并结合cpolar 内网穿透工具实现远程访问1Panel 管理…

性能提升5倍!翼支付基于多租户的降本增效实践

作者&#xff1a;王硕 中国电信翼支付 DBA 翼支付是天翼电子商务有限公司旗下第三方服务平台&#xff0c;面向 7000 万月活用户&#xff0c;提供民生缴费、消费购物、金融理财等服务内容&#xff0c;依托云计算、大数据、人工智能等技术&#xff0c;联合合作伙伴&#xff0c;赋…

Stable Diffusion---Ai绘画-下载-入门-进阶(笔记整理)

前言 注&#xff1a;本文偏向于整理&#xff0c;都是跟着大佬们学的。 推荐两个b站up主&#xff0c;学完他们俩的东西基本就玩转SD为底的ai绘画&#xff1a; 秋葉aaaki&#xff0c;Nenly同学 1.首先SD主流的就是秋叶佬的Webui了&#xff0c;直接压缩包下载即可&#xff0c;下…

深度学习(十一)---zed 调用yolov5 进行识别目标并实时测距

1. 前言 zed 相机测距有2种方式&#xff1a;一种是根据点云数据进行测试&#xff0c;二是根据zed获取深度值进行测距。上篇文章 调用yolov5模型进行实时图像推理及网页端部署 我们讲述了zed调用yolov5进行目标识别&#xff0c;我们在此基础上进一步实现目标测距功能。 2.深度…

数据分析因子评分学习

当多个因素影响一个结果时&#xff0c;我们需要综合考虑这些因素分别对结果德影响。因子评分就是用于比较其对结果德影响程度。 文章目录 前言一、案例背景二、解决方案&#xff08;一&#xff09;分析思路&#xff08;二&#xff09;剔除无关数据&#xff08;三&#xff09;求…

ac7260网卡不能连5g

之前路由器是双频&#xff0c;最近为了连物联网一堆&#xff0c;把双频拆成两个wifi 结果电脑上装的pdd网卡就罢工了&#xff0c;连4g可以&#xff0c;但是连5g网络就不行&#xff0c;连上却没网&#xff0c;导致网盘下东西慢。刚开始以为是tplink的易展问题&#xff0c;结果看…

CSS中如何实现元素的旋转和缩放效果?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 元素的旋转和缩放效果⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏…

element ui 表格组件与分页组件的二次封装

目录 效果图 组件封装 parseTime函数 debounce 函数 页面使用 【扩展】vue 函数式组件 函数式组件特点&#xff1a; 函数式组件的优点&#xff1a; 【扩展】vue中的render函数 一、初步认识render函数 二、为什么使用render函数 三、render函数的解析 【扩展】添加…

HDFS文件删除后,HIVE元数据还存在的问题

一.背景 手动在hdfs上删除了一个表的分区数据(inc_day2023-08-30)&#xff0c;当查询这个表这个分区的数据时报错文件不存在 二.原因 即HDFS数据删除了&#xff0c;但是hive metastore元数据却没有更新&#xff0c;使用show partitions tablename 发现该分区还存在 三.解决办法…

【 Tkinter界面-练习04】 画板作画详细揭示

一、说明 对画布的掌握分三个部分&#xff0c;将图形paint到画布、动画move、鼠标画&#xff1b;本篇将侧重于鼠标画的功能&#xff0c;提起鼠标画实现&#xff0c;将涉及一系列组合操作才能完成&#xff0c;这里将一一加以介绍。 Canvas 小部件具有大量功能&#xff0c;我们不…