news 2026/9/28 9:18:56

Doris查询缓存调优实战:从重复查询烧掉集群到命中率95%

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Doris查询缓存调优实战:从重复查询烧掉集群到命中率95%

1. 重复查询是怎么把集群资源烧掉的:一个常见的性能事故场景

接手公司数据平台性能优化之后,我做的第一件事不是调参数,而是拉审计日志。原因很简单:不看实际查询,永远不知道集群资源到底被什么吃掉了。当时线上跑的是一套基于 Apache Doris 构建的报表平台,服务着业务方二十多张固定看板和若干临时分析查询。看起来规格不低——三个 BE 节点、每个 64G 内存、SSD 存储——但每天早上八点到十点,P95 延迟能从 800ms 一路飙到 8 秒以上,CPU 经常冲到 90% 以上。

第一反应是分桶或者分区分得不够细,于是去查了大查询和慢查询日志,结果发现所有慢查询都不是什么特别复杂的 SQL,只是普通的SELECT SUM(amount) FROM orders WHERE dt BETWEEN ... AND ...之类的分组聚合。真正的问题出在重复次数上——同一张订单表,32 亿行数据,一条日报 SQL 在早高峰两小时内被执行了 2100 多次。也就是说,同样的计算逻辑、同样的数据范围、同样的聚合操作,集群在短短两小时内白算了 2100 遍。

这种场景在大数据平台里实在太常见了。BI 报表、可视化大屏、数据门户,本质上都是同一批固定 SQL 被反复触发。每一次执行,Doris 都要重新读取底层数据、重新执行谓词下推、重新做聚合和排序,完全不做任何“记忆”。于是 CPU 被无意义的重复计算耗尽,查询延迟随之恶化,而真正需要资源的临时分析查询反而抢不到资源。

1.1 从数据库压力曲线看到的规律:慢查询和重复查询强相关

把审计日志按 SQL 文本去重之后,你会发现一个残酷的事实:线上查询的活跃 SQL 面往往很窄,但每个 SQL 的执行次数高得惊人。我当时统计过,活跃 SQL 中文本完全相同的查询占了总查询次数的 62%,这些查询贡献了 70% 以上的累计扫描行数。压力曲线上每一个尖峰,几乎都对应着固定报表的刷新时刻。

更麻烦的是,这类查询很难通过“扩大并发”来解决。你扩的是并发,但它消耗的是 CPU 和磁盘 IO,每个查询扫一遍几亿行数据,并发越高,争抢越严重。就算加机器,也只是把同样的浪费从一个节点搬到三个节点,资源利用效率依旧低下。我当时就想:如果能把重复计算的结果直接复用,问题就转化成了“查一次缓存”,而不是“算一次查询”。

这其实就是 Doris 查询缓存机制想解决的核心问题。相比 Presto、Hive 这类引擎,Doris 的缓存设计更贴近 OLAP 场景的重复查询特征,后面会详细拆解它的两条缓存路径和适用边界。

1.2 为什么"加机器""加索引"都治不了重复查询的根

先说加索引。索引解决的是“扫描量太大”的问题,它能让查询更快地定位到目标数据,但聚合计算本身——比如对几亿行做 SUM、AVG、COUNT——依然每次都要跑。对于SELECT dt, SUM(amount) FROM orders GROUP BY dt这类查询,索引生效之后还是要把所有涉及行的 amount 取出来做累加,CPU 消耗并不会因为索引而消失。

再说加机器。这是一个纯线性投入:查询次数翻倍,CPU 消耗翻倍,那就把机器翻倍。短期能扛住,但业务是增长的,查询频次也是增长的,每个月的资源成本都在往上走。而且机器加多了,单节点扫描的数据量虽然降了,但重复计算的总量并没有减少,本质上是用更多硬件去抵消重复劳动。

物化视图能解决一部分问题,前提是你能提前预测哪些查询需要被加速。报表查询还好说,但临时分析和探索性查询没办法提前建模,而且物化视图的维护本身也要消耗资源。相比之下,查询缓存是一种“事后复用”的思路:第一次查询老老实实计算,结果存下来,之后再遇到一模一样的查询,直接返回结果。它不要求你提前预判查询模式,对固定报表和高频重复查询几乎是无脑收益。所以在我个人的优化优先级里,查询缓存是排在“加机器”和“无脑加索引”之前的。

2. 缓存到底缓存了什么:SQL缓存与分区缓存的取舍

Doris 的查询缓存不是一种笼统的“查询结果缓存”,它实际提供了两条路径,分别对应两种不同的缓存粒度:SQL Cache 和 Partition Cache。理解这两者的区别,决定了你在实际业务中能不能吃到缓存的红利。

2.1 SQL Cache:把整道菜做好的成品直接端上来

SQL Cache 的思路最简单——把一条 SQL 的最终结果集缓存起来。当用户执行的 SQL 满足命中条件时,Doris 在第一次执行后会把结果存在 BE 节点本地,之后遇到完全相同的 SQL,并且查询涉及的表分区版本没有变化,就直接把缓存结果返回给客户端,跳过整个计算过程。

用生活里的事打比方,SQL Cache 就像你把整道红烧肉做好之后拍张照片放在菜单上,顾客点什么你直接端出成品,不需要重新下厨。它适合的场景非常明确:固定报表、BI 看板、定时任务里那些“SQL 文本不变、数据范围不变、查询时间相近”的重复查询。

命中条件上,SQL Cache 有几个硬性要求:

  • SQL 文本必须是确定性的,不能包含rand()、now()、uuid()这类非确定性函数。
  • 查询涉及的表分区版本不能有变化,只要底层数据发生过导入、插入、更新,缓存就自动失效。
  • 结果集大小不能超过阈值,超大结果集的缓存收益会显著下降,Doris 也会在满足一定条件下放弃缓存。

对应到代码层面,开启方式很简单,会话级别设置:

SET enable_sql_cache = true;

之后的查询如果满足条件,Doris 会自动尝试命中缓存。需要注意的是,这个变量在较新版本中可能默认开启,但具体默认值随版本迭代会有变化,生产环境要结合版本确认。

2.2 Partition Cache:半成品复用,只计算真正变化的那部分

Partition Cache 是更精细的一条路径,也是 Doris 在 OLAP 引擎里比较有辨识度的设计。它的核心思想是:如果查询涉及多个分区,而其中一部分分区的数据没有变化,那么这些分区可以直接复用上一次的中间计算结果,Doris 只需要计算“这次新变化/新增的分区”,然后把缓存结果和计算结果合并,返回最终结果。

继续用做饭打比方:SQL Cache 是整道菜做好备着,Partition Cache 是提前把食材洗好切好、料汁调好,客人点菜的时候只需要炒一下新下单的那部分。对于按天分区、按小时分区的时间序列类数据,这个机制格外有用。

举一个实际场景。有一张按天分区的订单表,平台每天凌晨导入前一天的数据。某个业务方要查“近 30 天销售汇总”,这条查询每天都会被触发。没有分区缓存时,每次都要扫 30 个分区的数据做聚合;有了分区缓存之后,之前 29 天的分区结果都还留在缓存里,只有新导入的那个分区需要真正计算,其余直接拼接。查询耗时理论上可以降到原来的几十分之一。

Partition Cache 的开启同样在会话级别:

SET enable_partition_cache = true;

但有一个容易被忽略的前提:查询必须能够被有效裁剪到分区级别。如果你的表设计没有合理分区,或者查询条件不带分区键,Partition Cache 很难发挥作用。这也是为什么我接下来一定要讲表的分区设计,它直接决定了缓存能不能吃到。

2.3 两种缓存的边界:一张表看清适用场景

把两种缓存放到一张表里对比,决策的时候就清晰多了:

对比维度SQL CachePartition Cache
缓存粒度整个查询的最终结果集分区级别的中间结果
命中前提SQL 文本一致、分区版本无变化查询可分区裁剪、未变化分区版本不变
典型场景固定报表、BI 看板、高频同文本查询近 N 天汇总、区间聚合、增量导入场景
失效代价整体重新计算只重新计算变化的分区
对表结构要求低,普通表即可高,必须合理设计分区
适合数据形态低频更新、读多写少按时间增量写入、历史分区基本不变

从这张表能看出一个关键结论:如果你的表没有分区,或者分区设计不合理,Partition Cache 带来的收益基本为零,只能靠 SQL Cache 兜底。而 SQL Cache 的命中又很依赖“SQL 文本不变”这个条件——这恰恰是很多团队忽略的细节,后面我会单独讲一次线上排查经历,正好卡在这个环节上。

3. 开启缓存之前:参数、内存与命中规则的三重准备

很多人在集群上直接SET enable_sql_cache = true;就以为完事了,结果一跑发现命中率低得可怜,要么缓存频繁失效,要么 BE 内存被撑爆。缓存不是开关,开启前必须做一套组合准备。

3.1 关键参数速查与推荐配置

以下几组参数是我在实际项目中验证过、比较有参考价值的配置方向。Doris 版本迭代较快,不同版本参数名可能略有差异,生产环境要以官方对应版本的文档为准。

参数/变量作用推荐设置
enable_sql_cache会话级开关,开启 SQL Cachetrue(高频报表场景)
enable_partition_cache会话级开关,开启分区缓存true(分区表增量导入场景)
cache_medium缓存存储介质,SSD 或 HDD优先SSD,收益显著高于 HDD
cache_ttl缓存过期时间根据数据更新频率设置,常见 1-7 天
cache_result_max_rows单次缓存结果集行数上限按查询特征调整,一般默认即可

这里重点说一下cache_medium。缓存可以放在内存也可以放磁盘,Doris 会按 LRU 策略淘汰旧数据。我建议优先使用 SSD,因为 SQL Cache 存的已经是计算结果,读取不需要再做二次聚合,SSD 的顺序读性能完全够用;而把缓存全放内存虽然更快,但会挤压查询自身的 buffer 空间,可能导致查询性能不升反降。对于内存比较紧张的 BE 节点,用 SSD 做缓存介质是更稳妥的方案。

会话级变量可以在 JDBC 连接串或者连接池初始化时统一设置。如果你用的是较新版本,还可以在表属性里直接配置缓存策略。配置完成后,一定要验证生效——很多团队配置完就忘了查,默认参数下缓存可能根本没跑起来。

3.2 分区设计是分区缓存的生命线

Partition Cache 对分区设计的依赖再怎么强调都不为过。如果你用的是无分区大表,或者分区覆盖度很差,这个缓存路径就是空的。理想的表结构至少满足三点:

  • 按时间列分区,比如dt字段按天分区。
  • 数据写入集中在最新分区,历史分区基本不会被修改。
  • 查询过滤条件尽量带上分区键,让查询能精确裁剪到小范围分区。

有团队问过我:“Doris 数据量只有几 MB,是不是不需要分桶?”这个问题的答案是——数据量小确实可以不纠结分桶数量,但分区是否合理和分桶是两回事。几 MB 的表,你随便怎么分桶影响都不大,但如果你要让查询缓存生效,分区设计依然值得花十分钟想清楚。因为缓存命中的核心不是数据量大小,而是“分区版本是否稳定”这个机制特性。

反过来,分区粒度也不宜过细。小时分区虽然裁剪更精细,但数据导入会频繁改变分区版本,导致缓存频繁失效。我的经验是:对于日报级别的查询,天分区通常是性价比最高的粒度;只有在查询对延迟极度敏感,且数据导入次数极少的情况下,才考虑更细的分区。

3.3 缓存内存怎么估算,才不会挤掉查询性能

缓存不是白嫖的资源,它吃的是 BE 节点内存或磁盘。如果你把缓存全塞内存,就要做好预算。一个经验性的参考是:BE 节点 JVM 之外的内存中,查询执行本身需要留足 buffer,缓存占用的内存建议控制在节点可用内存的 10%~20% 以内。比如一台 64G BE 节点,留给缓存的空间在 6G~12G 左右比较合理,具体取决于查询并发和数据集特征。

估算方式可以这样粗略推:单条查询结果集平均大小 × 预计缓存条目数,就是缓存占用的空间。如果发现缓存命中率很高但内存压力也大,优先做两件事——一是把cache_medium切换成 SSD 分担内存压力,二是检查是否把超大结果集也缓存了。超大结果集的缓存命中率往往不高,因为每次数据一更新就要全部重算,收益小、成本高。

还有一个容易忽略的问题:缓存生效依赖 BE 节点的本地存储,节点重启后缓存会丢失。所以不要指望缓存能替代物化视图,它只是一种短周期的加速手段。集群一旦滚动重启,缓存命中率会瞬间归零,需要一段时间慢慢恢复,这也是平台规划时要注意的运维细节。

4. 从命中率 60% 到 95%:一次线上缓存调优的完整排查链路

配置开好了,参数也按文档设置了,结果打开监控一看,缓存命中率只有堪堪 60%。这在当时的项目里已经算不错了,但距离我预期还有差距。更麻烦的是,命中率不稳定,早高峰有时高有时低,完全不知道问题出在哪。这里我把完整的排查过程写出来,很多坑是文档里不会写的。

4.1 第一层排查:SQL 文本不一致,是最隐蔽的杀手

我先拉了一小时内的审计日志,把命中缓存的查询和未命中的查询做了对比。结果发现,未命中的 SQL 里有一大批文本上看着相似、实际完全不同的查询。最典型的就是 BI 工具生成的 SQL——它对时间过滤条件做参数化时,会生成带具体日期字符串的 SQL,每次刷新报表日期变了,SQL 文本就变了。

比如业务方看的是“昨日销售”,但 BI 工具传入的是dt >= '2025-01-05' AND dt <= '2025-01-05'这种带具体日期的条件。今天看的是 1 月 5 号,明天看的是 1 月 6 号,SQL 文本完全不同,SQL Cache 自然永远命中不上。

这类问题的解法不是改 Doris,而是改业务查询规范:把“相对时间”写进 SQL 里,比如dt >= CURDATE() - INTERVAL 1 DAY AND dt < CURDATE()。这样无论哪一天执行,SQL 文本都一模一样,缓存才能稳定命中。如果你控制不了 SQL 生成端,也可以在接入层做一层 SQL 规范化,但这需要额外开发成本,多数团队不如直接推动查询规范。

另外注意,now()、CURDATE()这类函数虽然能让 SQL 文本一致,但它们本质是非确定性函数——今天执行和明天执行的结果不同,Doris 会直接拒绝缓存。这里有一个折中:如果查询能接受 TTL 过期,可以配合缓存过期时间来处理变化频率,但如果你需要的是高实时性查询,那缓存本来就不合适。

4.2 第二层排查:分区版本频繁变化,把刚缓存的数据全部失效

SQL 文本问题解决之后,命中率提升到了 78%,但距离 95% 还有距离。继续看监控,发现一个规律:缓存命中率的下跌时段,和 ETL 导入任务的执行时段高度重合。一查才发现,这对应着上面说的分区版本变化问题。

我们的订单表按天分区,但 ETL 任务会在白天对当天分区做多次覆盖写入(比如修正数据)。每次写入都会更新分区版本,而 SQL Cache 的失效逻辑是:查询涉及的表的分区版本一旦变化,整个查询缓存全部失效。这意味着白天每次修正导入,都会把当前所有涉及该分区的缓存结果清掉。更隐蔽的是,分区缓存虽然只重新计算变化的分区,但如果查询涉及一个频繁变化的分区,它的中间结果也会被一再无效化。

解决方案有两个方向。一是把 ETL 导入时间集中到凌晨,白天尽量避免对同一分区反复写入;二是使用分区缓存代替 SQL 缓存,这样只有被修改的那个分区需要重算,其他历史分区还能复用。我们最终做了组合改进:把修正数据的导入窗口固定到凌晨三点到六点,同时把高频查询切到 Partition Cache 路径,命中率明显回升。

4.3 第三层排查:结果集过大和非确定性函数,是最后的两块短板

到这一步,命中率在 80%~90% 之间徘徊。继续深挖 Profile,发现两类查询始终无法命中:一类是结果集特别大的明细查询,另一类是带了RAND()、UUID()的抽样查询。前者容易理解——结果集会超过缓存阈值,Doris 判断缓存成本太高就不缓存了。后者也很合理——RAND()让每次查询结果都不同,缓存没有意义。

这两个问题的处理思路完全不同。明细查询我会建议业务方拆小查询范围,或者改到专门的明细查询服务,不要试图拿缓存硬扛;而抽样查询本身就是低确定性查询,缓存不该管它,直接排除在外。

最终我得到的缓存命中率稳定在 95% 左右,P95 延迟从优化前的 3.2 秒降到了 210 毫秒,早高峰 CPU 使用率下降了近 40%。这个结果说明,Doris 查询缓存的优化不只是开个开关那么简单,而是要把 SQL 规范化、分区设计、ETL 调度和缓存路径选择组合在一起做。

4.4 排查链路小结:一份可直接使用的检查清单

如果你也遇到“缓存开了但命中率不高”的问题,按这个顺序排查最快:

  1. 核对 SQL 文本——通过审计日志查看未命中查询和命中查询的文本差异,重点排查带具体日期的参数化查询。
  2. 核对非确定性函数——搜索所有包含RAND()、NOW()、UUID()等函数的 SQL,这些查询天然不适合缓存。
  3. 核对分区版本变化——观察命中率下跌时段是否与 ETL 导入任务重合,修正导入策略或切换缓存路径。
  4. 核对结果集大小——查看 BE 节点上缓存占用的存储空间,确认是否有明细大查询在反复冲刷缓存。
  5. 核对会话变量——确认连接池或 JDBC 连接串中是否真的设置了enable_sql_cache或enable_partition_cache,很多问题是配置没传进去。

5. 缓存不是银弹:哪些查询别指望缓存,以及三层加速的完整架构

做了这么多优化之后,我得泼一盆冷水:查询缓存不是万能的,它只对特定形态的查询有显著效果。在实际平台架构里,查询缓存只是加速体系中的一层。如果你在规划一个 Doris 集群的查询加速方案,建议把缓存放在一个更完整的框架里理解。

5.1 不适合 Doris 缓存的几类查询

第一类是对实时性要求极高的查询,比如实时大屏上精确到秒级的指标。缓存的前提是数据版本稳定,数据每秒钟都在写入的查询场景,缓存结果刚生成就失效了,命中意义不大,反而白白消耗存储和淘汰资源。

第二类是随机性强的分析查询,比如带大量RAND()抽样的探索式分析。这类查询每次执行路径不同,结果不同,缓存永远无法命中。

第三类是超大结果集的导出查询,比如一次性拉几千万行明细。这类查询即使缓存下来,再次复用的概率也极低,还白白占空间,应该依赖 Doris 的查询结果导出机制,而不是查询缓存。

判断一条查询能不能吃缓存,我在内部定过一个简单的准则:如果一条 SQL 一周内被执行超过 20 次,且相邻执行之间表数据没有变化,缓存就是高收益的;如果一条 SQL 一天只跑一两次,或者数据每秒都在变,就别在缓存上浪费精力。

5.2 三层加速方案:查询缓存 + 物化视图 + 业务层缓存

第一层是 Doris 本身的查询缓存,解决“相同 SQL 重复计算”的问题。第二层是物化视图,解决“同维度不同聚合口径的重复扫描”问题——比如GROUP BY dt和GROUP BY dt, province是两条不同 SQL,但底层都是同一批数据的聚合,物化视图可以把中间结果预计算好,多条 SQL 共用。第三层是应用层缓存,比如在 BI 服务外面套一层 Redis,把已经渲染好的报表数据和查询结果缓存起来,用户访问时连查询都不发,直接返回渲染结果。

我在实际项目里最推荐这个组合:

  • 高频、固定、同文本的 SQL——用 Doris SQL Cache。
  • 按天分区、增量写入、历史分区稳定的聚合——用 Doris Partition Cache。
  • 多口径、多维度的重复聚合——建物化视图。
  • 看板级的最终渲染结果——在应用层加 Redis 缓存。

这套方案搭配下来,真正打到 Doris 集群上的重复计算量可以降到原来的十分之一以下。要注意的是,层与层之间不是替代关系,而是互补:查询缓存覆盖不了多口径聚合,物化视图又不擅长处理“同样一条 SQL 被频繁执行”的细粒度场景,各管一段,整体效果才最好。

5.3 集群部署与运维侧的配套动作

查询缓存想要稳定发挥效果,集群部署时就要想好配套。首先是 BE 节点的存储介质:既然缓存介质支持 SSD,部署时优先选择 SSD 节点,或者至少把缓存目录放到 SSD 上,否则磁盘读延迟依然会拖慢缓存命中后的响应速度。

其次是监控指标。Doris 的 Profile 里能看到缓存命中情况,但生产环境建议把缓存命中率、缓存占用空间、缓存淘汰次数这三个指标单独接进监控系统,每天观察趋势。缓存命中率的突然下跌,往往对应着一次新的 ETL 任务上线,或者业务方改了查询模板——这些信息对平台稳定性管理非常有用。

最后是版本规划。Doris 不同版本的缓存能力差异不小,比如早期版本的 Partition Cache 支持度还不够完善,SQL Cache 的行为也不完全一致。如果你正在规划 Doris 集群部署,尽量选择较新的、缓存能力完善的版本,部署前把官方文档里的缓存章节完整读一遍,确认参数名和默认行为再动手。

5.4 和 Hive、Presto 相比,Doris 缓存机制到底强在哪

我在之前的项目里同时维护过 Hive 和 Presto 两套查询引擎,对这个问题感触很深。Hive 的缓存主要体现在执行计划复用和结果物化上,需要人工介入,LLAP 组件虽然能在内存里缓存热点数据,但配置复杂,运维成本不低;Presto 更多依赖集群计算能力和查询优化器,跨查询的结果缓存能力相对较弱,通常要靠外部工具或 Presto 的企业版特性来实现。

Doris 把查询缓存做进了引擎自身,而且是面向 OLAP 重复查询场景专门设计的。SQL Cache 和 Partition Cache 两级缓存配合起来,对固定报表和增量分区数据这类典型的数据平台场景,几乎是开箱即用的加速手段。这也是我在多个项目里最终选择以 Doris 作为统一 OLAP 引擎的重要原因——它把很多以前需要外部组件才能解决的问题,做成了引擎内置能力,运维复杂度显著降低。

就我个人这几年的实操体会,查询缓存这步棋,投入产出比远比加机器高。但千万记住:它不是让你把 SQL 写得更随意,而是要求你把 SQL 规范做得更细致。所有缓存命中率高的团队,背后一定有一个严格的查询规范在支撑。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 9:18:44

Stormworks微控组件全攻略:翻译对照与逻辑搭建实战

我玩Stormworks也有七八百个小时了&#xff0c;从最早瞎折腾气压传感器到现在用微控组件写逻辑&#xff0c;最大的门槛其实不是物理引擎有多真实&#xff0c;而是那个微控制器面板里密密麻麻的英文组件名。国内玩家圈里管这个叫“微控”&#xff0c;你要是没个四六级水平&#…

作者头像 李华
网站建设 2026/9/28 9:17:43

701张瓶子数据集YOLO系列实战:标签对齐与训练避坑指南

简介&#xff1a;面向YOLO系列目标检测模型训练与验证场景的瓶子数据集&#xff0c;专为需要快速获取带标签图像集的开发者准备&#xff0c;适合入门至进阶水平使用。该数据集包含701张标注图像&#xff0c;同时提供YOLO格式&#xff08;txt&#xff09;与VOC格式&#xff08;x…

作者头像 李华
网站建设 2026/9/28 9:13:58

ESP32-S3与LVGL深度适配原理与实战

1. 为什么是ESP32-S3 LVGL&#xff1f;这不是凑热闹&#xff0c;而是有硬逻辑的组合你搜“ESP32-S3 LVGL”时&#xff0c;满屏都是“手把手”“附完整代码”——但真正能讲清楚“为什么非得用S3、为什么LVGL不是唯一选择、为什么你的UI跑起来卡成PPT”的人&#xff0c;少之又少…

作者头像 李华
网站建设 2026/9/28 9:13:52

JS4阶段进阶指南:从语法到全栈开发的关键跨越

最近把全栈开发的学习推进到 JS_4 这个阶段&#xff0c;说是"学习"&#xff0c;其实更像一次从"跟着教程抄示例"到"按需求写出能用的东西"的过渡。JavaScript 学到这个份上&#xff0c;很多人会有一种感觉&#xff1a;知识点都见过&#xff0c;但…

作者头像 李华
网站建设 2026/9/28 9:13:40

输电线路金具检测:YOLO数据集制作与训练全流程指南

简介&#xff1a;这是一份面向YOLO目标检测实战的输电线路电力金具检测数据集&#xff0c;适用于电力巡检、工业视觉检测相关的研究人员与工程技术人员。数据集基于真实场景拍摄&#xff0c;包含一千张图片对应的VOC&#xff08;XML&#xff09;、COCO&#xff08;JSON&#xf…

作者头像 李华
网站建设 2026/9/28 9:12:42

C语言进阶实战:指针、内存与文件操作避坑指南

学C语言有个很有意思的分水岭&#xff1a;前几篇笔记里的变量、分支、循环、函数&#xff0c;上课时大家都觉得能跟上&#xff0c;一到指针、字符串和文件操作&#xff0c;例子看得懂&#xff0c;作业写不出来。这篇是《C语言学习笔记》系列的第五篇&#xff0c;我打算把进入“…

作者头像 李华