Doris 的Explain 和 Profile

news2024/11/25 6:40:08

什么是 explain?

执行计划是对一条 SQL 具体的执行方式和执行过程的描述。例如,对于一个涉及两表连接的 SQL,执行计划会展示这两张表的访问方式信息、连接方式信息,以及各个操作之间的顺序。

在 Doris 系统中提供了 Explain 工具,它可以展示一个 SQL 的具体执行计划的详细信息。通过对 Explain 输出的计划进行分析,可以帮助使用者定位计划层面的瓶颈,从而针对不同的情况进行执行计划层面的调优。

Doris 提供了多种不同粒度的 Explain 工具,如 Explain Verbose、Explain All Plan、Explain Memo Plan、Explain Shape Plan,分别用于展示最终物理计划、各阶段逻辑计划、基于成本优化过程的计划、计划形态等。

获取 Explain 输出后,业务人员或者 DBA 就可以分析当前计划的性能瓶颈。例如,通过分析执行计划发现 Filter 没有下推到基表,导致没有提前过滤数据,使得参与计算的数据量过多,从而导致性能问题。

又如,两表的 Inner 等值连接中,连接条件一侧的过滤条件没有推导到另外一侧,导致没有对推导一侧的表进行提前过滤,也可能导致性能问题等。此类性能瓶颈都可以通过分析 Explain 的输出来定位和解决。

explain的作用

下面几个示例分别演示,explain能看到的主要信息:

1. 通过执行计划 区分新老优化器

自从2.0版本及以后的版本中,我们的sql默认都是走新优化器,如果在某些时候发现sql执行速度过慢,或者有语法兼容性问题,可以先排查一下是否回退到老优化器了,具体排查方法如下:
通过执行 explain + sql 来观察执行结果:
如图所示:
在这里插入图片描述

上图显示的,就是走的新优化器。


老优化器则如下所示:
在这里插入图片描述

正常来说,我们一个sql都会走新优化器,特别是2.1的版本,如果sql走了老优化器,需要把优化器的自动回退关闭掉,具体操作为:set enable_fallback_to_original_planner = false ,如果是一些dml语句走了老优化器,则需要注意 experimental_enable_nereids_dml_with_pipeline 该参数的值是否打开。
注意:生产环境中 请勿关闭新优化器 experimental_enable_nereids_planner

2. 通过explain 判断分区分桶裁剪效果

一般来说,我们查询的sql都会携带过滤条件,这些查询条件都尽可能的优先满足分区分桶的裁剪,这样能尽可能避免 无关数据扫描io的浪费,通过explain能够观察到我们的sql对于分区分桶裁剪的效果,如下图所示:
主要查看 OlapScanNode 节点中的 partitionstablets的扫描情况:
有效的分区裁剪:
在这里插入图片描述

有效的分桶裁剪:
在这里插入图片描述

在分区数量特别多,tablets 数量比较多的场景下,有效的分区分桶裁剪,能大幅加速查询的速度。

3. 物化视图透明改写

在固定维度聚合的场景,我们可以针对明细表构建单表物化视图来加速,如下例所示:
原本的查询如下:

select lo_orderkey,sum(lo_supplycost) from lineorder group by lo_orderkey;

如果是直接查询明细表,需要将所有的 lo_orderkey和lo_supplycost 扫描参与计算,代价较大,我们可以在上面构建物化视图,如下:

CREATE MATERIALIZED VIEW single_mv as select lo_orderkey,sum(lo_supplycost) from lineorder_mv group by lo_orderkey;

这样,如果还是查询相同的逻辑,就能够直接命中物化视图的结果,提供加速,我们可以通过explain来确定,是否命中了物化视图:
具体位置在 OlapScanNode 节点中的table中,会显示命中的物化视图的名字,如下图所示
在这里插入图片描述

4. join优化

selectdb常见的join方式如下面几幅图所示:

Colocated Join

前提:两张表都以等值条件中的字段为distribute key,需要在建表时申明
[图片]

Bucket Shuffle Join

前提:其中一张表以等值条件中的字段为distribute key
在这里插入图片描述

Broadcast Join

前提:其中一张表是小表

在这里插入图片描述

Shuffle Join

缺点:左右两张表都需要重新分布,数据移动量大,性能低
优点:可以处理所有等值join,适应性强
在这里插入图片描述

不同的join方式,对应的计算代价不一样,这里需要根据合适的数据分布特征,选用对应的join方式。

手动Hint控制 join方式

在某些时候,如果优化器生成的执行计划,所走的join方式不是我们认为最好的join方式,我们可以手动通过Hint的方式来控制join的方式,目前提供两种方式供选择:[shuffle] 和 [broadcast]
如下图所示:

explain select count(*) from lineorder join customer on customer.c_custkey = lineorder.lo_orderkey;

上面的sql在执行时,join方式如下所示:
[图片]

最终走了Bucket Shuffle Join,我们如果不想让他走这种join方式,我们可以手动Hint来控制,如下图所示:

explain select count(*) from lineorder join [broadcast] customer  on customer.c_custkey = lineorder.lo_orderkey;

上面的语句中,我们通过指定 [broadcast] 的方式,强制让该sql走 broadcast join ,我们可以观察对应的执行计划:
在这里插入图片描述

手动 hint控制join顺序

某些时候,如果统计信息没有搜集的很好,可能会导致我们的sql在join时,不能够只能调整 join两表的顺序,我们可以通过Leading Hint 的方式指导 Doris 优化器确定查询计划中的表连接顺序。具体示例如下:

explain select count(*) from customer join lineorder on customer.c_custkey = lineorder.lo_orderkey;

该sql默认的join顺序如下:
在这里插入图片描述

即order 在前,customer在后
我们可以使用如下方式来调整join的顺序:

explain select /*+ LEADING(customer lineorder)*/  count(*) from customer join lineorder on customer.c_custkey = lineorder.lo_orderkey;

在这里插入图片描述

  1. Runtime Filter 优化
    Runtime Filter 是一个强大的技术,它能够把参与join表的join条件,下推到其他表的过滤中去,以此来减少数据扫描量。
    如何确定Runtime Filter是否生效:
explain select count(*) from customer join lineorder on customer.c_custkey = lineorder.lo_orderkey where lineorder.lo_orderkey = 13281;

我们可以观察 explain中 join Node和scan Node 中有没有以下的关键信息:
在这里插入图片描述

上图就代表Runtime Filter生效。同时,在2.1的版本中Runtime Filter是默认打开的,如果不确定,可以通过参数runtime_filter_mode是否为global。


profile 介绍

获取profile

在 Doris 执行查询时,当碰到查询性能未达预期时,建议做进一步分析情况。本文将全面阐述如何在 Doris 中对查询进行性能分析。

在 Doris 中,由于 Profile 收集会产生一定的开销,因此默认情况下它是关闭的。若要进行查询性能分析,我们首先需要将其开启,具体操作为在 MySQL Client 中执行以下命令:
set enable_profile = true;

根据query ID 排查慢查询

查询性能问题分析的首要步骤是获取待分析查询的 QueryID。这个 QueryID 可以从fe/log/fe.audit.log日志文件中找到。
以 TPC-H 中的某条特定查询为例,通过查看日志信息,我们可以发现该查询的 QueryID 为

QueryId=704185c15570441b-98ad0634c88584f0。
2024-08-20 14:37:23,729 [query] IClient=127.0.0.1:33570|User=root|Ctl=internal Db=regression_test_tpch_sf0_1_p1I|State=EOF|ErrorCode=0|ErrorMessage=|Time(ms)=153|ScanBytes=0|ScanRows=0|ReturnRows=1|StmtId=1191|QueryId=704185c15570441b-98ad0634c88584f0|IsQuery=true|isNereids=true|feIp=168.45.0.1|StmtType=SELECT|Stmt=SELECT sum(l_extendedprice) / 7.0 AS avg_yearly FROM lineitem, part WHERE p_partkey = l_partkey AND p_brand_ "Brand#23" AND p_container = "MED BOX" AND l_quantity < ( SELECT 0.2*avg(l_quantity) FROM lineitem WHERE l_partkey= p_partkey) |CpuTimeMS=401ShuffleSendBytes=0|ShuffleSendRows=0|SqlHash=ес2e14fac69b9711dc305e218f1e94b8|peakMemoryBytes=33792|SqlDigest=|cloudClusterName=UNKNOWN|TraceId=|WcorkloadGroup=normal|FuzzyVariables=|scanBytesFromLocalStorage=0|scanBytesFromRemoteStorage=0

Profile 分析查询性能

在获取 QueryID 后,可以通过访问对应 FE 的 WebUI 来检索 Profile 文本。例如,通过访问链接http://{fe_ip}:{http_port}/QueryProfile/704185c15570441b-98ad0634c88584f0,即可获取到相应的 Profile 信息,查看更多详细信息,可以下载 profile_704185c15570441b-98ad0634c88584f0.txt 文件。

Profile 文件结构

Profile 文件中包含以下几个主要的部分:

  1. 查询基本信息:包括 ID,时间,数据库等
  2. SQL 语句以及执行计划。
  3. FE 的耗时(Plan Time, Schedule Time 等)。
  4. BE 在执行过程中各个 Operator 的执行耗时(包括 Merged Profile、Execution Profile、以及 Execution Profile 中的每个 PipelineTask)。
    在慢查询中,通常耗时主要集中在 BE 的执行过程,接下来将主要介绍这部分的分析过程。
通过 Merged Profile 进行 BE 执行分析

为了帮助用户更准确地分析性能瓶颈,Doris 提供了各个 Operator 聚合后的 Profile 结果。
EXCHANGE_OPERATOR(id=4)为例:

EXCHANGE_OPERATOR  (id=4):
    -  BlocksProduced:  sum  0,  avg  0,  max  0,  min  0
    -  CloseTime:  avg  34.133us,  max  38.287us,  min  29.979us
    -  ExecTime:  avg  700.357us,  max  706.351us,  min  694.364us
    -  InitTime:  avg  648.104us,  max  648.604us,  min  647.605us
    -  MemoryUsage:  sum  ,  avg  ,  max  ,  min  
        -  PeakMemoryUsage:  sum  0.00  ,  avg  0.00  ,  max  0.00  ,  min  0.00  
    -  OpenTime:  avg  4.541us,  max  5.943us,  min  3.139us
    -  ProjectionTime:  avg  0ns,  max  0ns,  min  0ns
    -  RowsProduced:  sum  0,  avg  0,  max  0,  min  0
    -  WaitForDependencyTime:  avg  0ns,  max  0ns,  min  0ns
        -  WaitForData0:  avg  9.434ms,  max  9.476ms,  min  9.391ms

Merged Profile 对每个 Operator 的核心指标进行了合并。核心指标及其含义如下:
暂时无法在飞书文档外展示此内容
在 Doris 中,每个 Operator 根据用户设置的并发数并发执行。因此,Merged Profile 对每个执行并发的每个指标都计算出了 Max、Avg 和 Min 的值。
其中,WaitForDependencyTime 指标在不同 Operator 对应有不同的值,因为每个 Operator 执行的条件依赖不同。例如,在这个 EXCHANGE_OPERATOR 的例子中,条件依赖是有数据被上游的算子通过 RPC 发送过来。因此,这里的 WaitForDependencyTime 实际上就是在等待上游算子发送数据的时间。

通过 Execution Profile 进行 BE 执行分析

区别于 Merged Profile,Execution Profile 展示的是具体的某个并发中的详细指标。
还是以 EXCHANGE_OPERATOR(id=4)为例:

EXCHANGE_OPERATOR  (id=4):(ExecTime:  706.351us)
      -  BlocksProduced:  0
      -  CloseTime:  38.287us
      -  DataArrivalWaitTime:  0ns
      -  DecompressBytes:  0.00  
      -  DecompressTime:  0ns
      -  DeserializeRowBatchTimer:  0ns
      -  ExecTime:  706.351us
      -  FirstBatchArrivalWaitTime:  0ns
      -  InitTime:  647.605us
      -  LocalBytesReceived:  0.00  
      -  MemoryUsage:  
          -  PeakMemoryUsage:  0.00  
      -  OpenTime:  5.943us
      -  ProjectionTime:  0ns
      -  RemoteBytesReceived:  0.00  
      -  RowsProduced:  0
      -  SendersBlockedTotalTimer(*):  0ns
      -  WaitForDependencyTime:  0ns
          -  WaitForData0:  9.476ms

备注
在该 Profile 中,LocalBytesReceived 是 Exchange Operator 特有的一个指标,其他 Operator 中并不存在,因此它也没有被包含在 Merged Profile 中。

PipelineTask 执行时间分析

在 Doris 中,一个 PipelineTask 由多个 Operator 组成。当分析一个 PipelineTask 的执行耗时时,需要重点关注以下几个方面:

  1. ExecuteTime:表示整个 PipelineTask 的实际执行时间,它大约等于该 Task 中所有 Operator 的 ExecTime 之和。
  2. WaitWorkerTime:表示 Task 等待执行 Worker 的时间。当 Task 处于 runnable 状态时,它需要等待一个空闲的 Worker 来执行,该耗时主要取决于集群的负载情况。
  3. 等待执行依赖的时间:一个 Task 可以执行的依赖条件是每个 Operator 的 Dependency 全部满足执行条件,而 Task 等待执行依赖的时间就是将这些依赖的等待时间相加。
    以上述例子中的其中一个 Task 为例:

- PipelineTask  (index=1):(ExecTime:  4.773ms)
ExecuteTime:  1.656ms
      -  CloseTime:  90.402us
      -  GetBlockTime:  11.235us
      -  OpenTime:  1.448ms
      -  PrepareTime:  1.555ms
      -  SinkTime:  14.228us
WaitWorkerTime:  63.868us
    DATA_STREAM_SINK_OPERATOR  (id=8,dst_id=8):(ExecTime:  1.688ms)
      -  WaitForDependencyTime:  0ns
          -  WaitForBroadcastBuffer:  0ns
          -  WaitForRpcBufferQueue:  0ns
    AGGREGATION_OPERATOR  (id=7  ,  nereids_id=648):(ExecTime:  398.12us)
      -  WaitForDependency[AGGREGATION_OPERATOR_DEPENDENCY]Time:  10.495ms

该 task 包含了 DATA_STREAM_SINK_OPERATOR 和 AGGREGATION_OPERATOR 两个 Operator。其中:
- DATA_STREAM_SINK_OPERATOR 有两个依赖,分别是 WaitForBroadcastBuffer 和 WaitForRpcBufferQueue
- AGGREGATION_OPERATOR 有一个依赖,为 AGGREGATION_OPERATOR_DEPENDENCY
因此,当前 Task 的耗时分布如下:

  1. ExecuteTime(执行总时间):1.656ms(约等于两个 Operator 的 ExecTime 总和)
  2. WaitWorkerTime(等待 Worker 的时间):63.868us(说明当前集群负载不高,Task 就绪以后立即就有 Worker 来执行)
  3. 等待执行依赖的时间:10.495ms(WaitForBroadcastBuffer + WaitForRpcBufferQueue + WaitForDependency[AGGREGATION_OPERATOR_DEPENDENCY]Time)即当前 task 的所有 Dependency 相加得到的总的等待时间。
性能问题通用排查思路

在 Doris 执行查询的过程中,通常可以依据以下四个步骤来排查性能问题:

  1. 定位算子执行性能问题
    算子执行缓慢是日常生产环境中较为常见的一类问题。在定位过程中,可以根据 Merged Profile 中的 Plan Tree,梳理出每个 Operator 的 ExecTime 和 WaitForDependencyTime。
  • 若 ExecTime 较慢,则表明当前算子存在性能问题,这可能是算子本身执行性能不佳,也可能是执行规划的 Plan 不够优化所导致的。
  • 若 ExecTime 很快,但 WaitForDependencyTime 很长,则说明性能瓶颈不在当前算子,需沿着 Plan Tree 继续查找其子节点。
  1. 定位数据倾斜问题
    在定位算子性能问题的过程中,若发现某个算子的 ExecTime 的最小值(Min)和最大值(Max)相差悬殊,则需观察该算子的数据量(RowsProduced)是否同样存在显著差异。若是,则说明发生了数据倾斜。
  2. 定位 RPC 延迟过大的问题
    当遍历完整个 Plan Tree 之后,若未能找到任何执行缓慢的算子,接下来需排查是否因 RPC 延迟过大而导致的性能问题。
    在此过程中,需找到 Execution Profile 中的每个 DATA_STREAM_SINK_OPERATOR,并检查其中的 RpcMaxTime 是否存在异常值。该指标指明了 RPC 过程中耗时最长的一次调用,若其值过大,则代表 RPC 延迟较高,可能是网络问题所致。
  3. 定位集群负载过高导致的性能问题
    在 Doris 的执行引擎中,执行线程数量是固定的。因此,当集群负载很高时,每个 Task 需等待空闲的执行 Worker 来执行。可以通过 Execution Profile 中的每个 PipelineTask 下,查看WaitWorkerTime 指标来获取等待时间的信息,以进一步判断。

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

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

相关文章

QT QVBoxLayout控件 全面详解

本系列文章全面的介绍了QT中的57种控件的使用方法以及示例&#xff0c;包括 Button(PushButton、toolButton、radioButton、checkBox、commandLinkButton、buttonBox)、Layouts(verticalLayout、horizontalLayout、gridLayout、formLayout)、Spacers(verticalSpacer、horizonta…

对sklearn库中的鸢尾花数据集内容和结构的详解认识和load_iris()函数查找学习举例

对sklearn库中的鸢尾花数据集内容和结构的详解认识和load_iris()函数查找学习举例 对sklearn库中的鸢尾花数据集内容和结构的详解认识和load_iris函数查找学习举例 对sklearn库中的鸢尾花数据集内容和结构的详解认识和load_iris()函数查找学习举例一、鸢尾花数据位置二、鸢尾花…

动态反馈控制器(DFC)和 服务率控制器(SRC);服务率和到达率简单理解

目录 服务率和到达率简单理解 服务率 到达率 排队论中的应用 论文解析:队列等待成本动态感知控制模型 动态反馈和队列等待成本意识: 服务速率调整算法: 动态反馈控制器(DFC)和 服务率控制器(SRC) SRC公式4的原理 算力资源分配系统中的调整消耗 举例说明 服务…

微信小程序上传微信官方审核流程(1)

1&#xff0c;打开微信开发者工具 2&#xff0c;微信开发者工具右上角有一个上传按钮&#xff0c;点击上传按钮 3&#xff0c;点击完上传按钮会弹出一个上传成功的提示&#xff0c;点击提示框中的确定按钮 4&#xff0c;点击完确定按钮后会显示填写版本好和项目备注 5&#x…

优先算法 —— 双指针系列 - 复写零

目录 1. 复写零 2. 算法原理 一般情况下 改为就地操作&#xff1a;从左到右&#xff08;错误&#xff09; 从右到左 总结一下解决方法&#xff1a; 如何找到最后一个复写的数 特殊情况 完整步骤&#xff1a; 3. 代码 1. 复写零 题目链接&#xff1a;1089. 复写零 - 力…

Spring源码学习(一):Spring初始化入口

写在前面 ​   作为一个刚步入职场的小白&#xff0c;对Spring&#xff08;SpringBoot&#xff09;的了解只停留在会用&#xff0c;并未对其内部的原理有过学习。在公司导师的指导下&#xff0c;开始进一步学习Spring的源码&#xff0c;毕竟Spring源码是Spring全家桶的基础&…

IntelliJ+SpringBoot项目实战(十三)--在SpringBoot中整合Mybatis-plus

mybatis-plus是基于mybatis基础上的增强的持久层框架&#xff0c;也是目前Java项目开发的主流框架。本文介绍在SpringBoot中集成mybtais-plus的方法以及使用mybatis-plus开发增删改查模块。 一、引入mybatis-plus 在openjweb-core工程中引入mybatis-plus依赖: <dependency&…

Vector底层实现详解

一、vector的介绍 vector是表示可变大小数组的序列容器 就像数组一样&#xff0c;vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素 进行访问&#xff0c;和数组一样高效。但是又不像数组&#xff0c;它的大小是可以动态改变的&#xff0c;而且…

【2024 Optimal Control 16-745】【Lecture4】equality-constraints.ipynb功能分析

代码实现了一个二次优化问题的可视化解法&#xff0c;包括目标函数、约束以及优化路径。提供了两种优化方法&#xff1a;牛顿法和高斯-牛顿法&#xff0c;用于对比效果。利用了自动微分工具 ForwardDiff 来计算约束曲率。 环境初始化并导入依赖库 # 激活当前文件夹下的项目环境…

【国产MCU】-GD32F470-串行外设接口(SPI)

串行外设接口(SPI) 文章目录 串行外设接口(SPI)1、SPI介绍1.1 SPI特性1.2 SPI信号1.3 SPI 时序和数据帧格式1.4 NSS 功能1.5 SPI运行模式2、SPI控制器寄存器列表3、SPI控制器驱动API介绍4、SPI应用4.1 SPI初始化流程4.2 数据发送与接收串行外设接口(Serial Peripheral Int…

Docker安装ubuntu1604

首先pull镜像 sudo docker run -d -P m.daocloud.io/docker.io/library/ubuntu:16.04国内使用小技巧&#xff1a; https://github.com/DaoCloud/public-image-mirror pull完成之后查看 sudo docker images 运行docker sudo docker run -d -v /mnt/e:/mnt/e m.daocloud.io/…

2024 年:Kubernetes 包管理的新前沿

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;历代文学&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;精通Java编程&#xff0c;高并发设计&#xf…

飞凌嵌入式T113-i开发板RISC-V核的实时应用方案

随着市场对嵌入式设备的功能需求越来越高&#xff0c;集成了嵌入式处理器和实时处理器的主控方案日益增多&#xff0c;以便更好地平衡性能与效率——实时核负责高实时性任务&#xff0c;A核处理复杂任务&#xff0c;两核间需实时交换数据。然而在数据传输方面&#xff0c;传统串…

VSCode 汉化教程【简洁易懂】

VSCode【下载】【安装】【汉化】【配置C环境&#xff08;超快&#xff09;】&#xff08;Windows环境&#xff09;-CSDN博客 我们安装完成后默认是英文界面。 找到插件选项卡&#xff0c;搜索“Chinese”&#xff0c;找到简体&#xff08;更具你的需要&#xff09;&#xff08;…

【Mac】VMware Fusion Pro 安装 CentOS 7

1、下载镜像 CentOS 官网阿里云镜像网易镜像搜狐镜像 Mac M1芯片无法直接使用上述地址下载的最新镜像&#xff08;7.9、9&#xff09;&#xff0c;会一直卡在安装界面&#xff08;在 install 界面按 enter 回车无效&#xff09;&#xff0c;想要使用需要经过一系列操作&#…

运维Tips:Docker或K8s集群拉取Harbor私有容器镜像仓库配置指南

[ 知识是人生的灯塔&#xff0c;只有不断学习&#xff0c;才能照亮前行的道路 ] Docker与Kubernetes集群拉取Harbor私有容器镜像仓库配置 描述&#xff1a;在现在微服务、云原生的环境下&#xff0c;通常我们会在企业中部署Docker和Kubernetes集群&#xff0c;并且会在企业内部…

C语言笔记(自定义类型:结构体、枚举、联合体 )

前言 本文对自定义类型的结构体创建、使用、结构体的存储方式和对齐方式&#xff0c;枚举的定义、使用方式以及联合体的定义、使用和存储方式展开叙述&#xff0c;如有错误&#xff0c;请各位指正。 目录 前言 1 结构体 1.1 结构体的声明 1.2 结构体的自引用 1.3 结构体变…

string的实际应用 -- 大数相加 、大数相乘

前言&#xff1a;哎&#xff0c;做题好难o(╥﹏╥)o&#xff0c;有时候想不到&#xff0c;而有时候则是想到了却没办法理清思路&#xff0c;转化为代码。有必要反思了┓(;_&#xff40;)┏&#xff0c;是否是做的太少了&#xff0c;或是自己的基础欠缺。 大学总是有些迷茫~ ​​…

STM32-- keil 的option for target使用

keil版本号 1.device界面 如&#xff1a;stm32f103c8t6的工程&#xff0c;可以直接在device这里修改成stm32f103vct6&#xff0c;虽然引脚不一样&#xff0c;但是很多一样的地方&#xff0c;可以直接使用&#xff0c;有些不修改也可以下载程序。 2.target xtal的设置不起作用了…

shell脚本9完结,保姆篇---春不晚

免责声明 学习视频来自 B 站up主泷羽sec&#xff0c;如涉及侵权马上删除文章。 笔记的只是方便各位师傅学习知识&#xff0c;以下代码、网站只涉及学习内容&#xff0c;其他的都与本人无关&#xff0c;切莫逾越法律红线&#xff0c;否则后果自负。 泷羽sec官网&#xff1a;http…