新闻详情

新闻详情

首页 / 资讯中心 / 详情

SpringBoot多线程+CompletableFuture优化MySQL大数据量查询性能实战

发布时间:2026/9/16 4:09:56来源:尧图网络
SpringBoot多线程+CompletableFuture优化MySQL大数据量查询性能实战
先说我为什么会写这个主题。前阵子有个数据迁移需求单表两千多万行用 MyBatis 默认的 selectList 一次性查出来直接内存溢出后来改成流式查询单线程跑还是要二十多分钟。领导说不行晚上上线窗口就半小时。没办法只能上多线程并行查。这次我把完整方案整理出来包含CompletableFuture配合线程池的写法、分片策略、大数据量下的坑、以及一套实测有效的性能调优思路。文章主要以 SpringBoot 2.7 MyBatis-Plus MySQL 为背景但思路完全通用。全文是实战链路不是理论讲义。1. 先搞清楚单线程查询到底慢在哪1.1 全表查询的耗时构成很多人一上来就说用多线程分页查但如果不理解单线程为什么慢分片参数就是拍脑袋定的。我拆解过一次两千三百万行数据的全表查询耗时构成大概这样数据库端扫描 回表占总耗时 50%~60%。MySQL InnoDB 按聚簇索引组织数据全表扫描本质是顺序读但回表走二级索引时是随机读这部分开销很大。网络传输占比 20% 左右。数据从 MySQL 服务器传到应用服务器两千万行哪怕每行 200 字节也是 4 个多 G 的数据量在网络上跑。应用端逐行封装 GC占比 20%~30%。MyBatis 反射映射、ArrayList 扩容、字符串拼接都会产生大量临时对象频繁触发 Young GC。这还只是单线程顺序执行的理想情况。实际上 MySQL 每次查询只用一个连接InnoDB 的读操作在单个连接内部是串行的。也就是说无论你的服务器 CPU 有多强、内存有多大单连接读取的方式都吃不满机器资源。1.2 什么样的场景才值得多线程查多线程查询不是银弹。我在项目中总结过三个适用前提缺一个就建议别折腾数据量足够大。至少百万级别才值得考虑几十万条数据单线程也就几秒钟引入多线程反而增加代码复杂度。查询本身是只读的且业务上允许多次查询。如果你查完还要更新同一批数据事务隔离级别、死锁、数据一致性都会让你怀疑人生。查询不存在强依赖的顺序要求。多线程查出来的结果天然乱序如果业务要求严格按自增主键或时间排序输出你需要额外做归并排序复杂度会上一个台阶。至于查出来之后干嘛常见的是全量导出Excel、CSV、数据迁移、缓存预热、批量数据同步、定时任务里的数据对账。这些场景都有一个共同点读多写少、步骤独立。1.3 什么时候千万别用多线程这里必须先泼一盆冷水。有几种情况你加了多线程不但不加速反而会出事故分页查询走了深分页。比如 limit 1000000, 1000MySQL 会扫描前一百万行再丢弃你开 10 个线程就是 10 倍的成本。业务数据实时更新频繁。你这边多线程快照数据那边业务在改数据查出来的结果本来就带着不一致并发度越高数据越脏。连接池配置没跟上。默认 HikariCP maximumPoolSize 是 10你开 16 个线程查询8 个线程在等连接等的时候还占着线程池资源最后效率反而不如单线程。提示多线程查询解决的是数据库连接内部串行的瓶颈但如果瓶颈本身在数据库端CPU 打满、磁盘 IO 饱和、慢查询日志里全是你的 SQL多线程只会让数据库死得更快。2. 多线程查询的整体设计不是盲目开线程就完事2.1 数据分片策略怎么定多线程查询的核心就一句话把查全表这个任务拆成若干个互不干扰的子任务每个子任务查一部分数据最后合并结果。分片策略我实际用过四种各有适用场景分片方式适用情况优点缺点按主键 ID 范围自增主键、分布均匀最简单SQL 清晰主键空洞会导致数据倾斜按固定批次偏移无主键或联合主键实现简单limit 深分页性能差不推荐大数据量按业务字段取模有 user_id、order_id 等业务键数据很均匀需要额外扫一遍索引确定分布按索引列分段有时序字段如 create_time天然契合时间范围查询需要预估数据密度容易不均我最终选的是第一种按主键 ID 范围分片。原因很直接自增主键可以先用select min(id)和select max(id)拿到一个大概的边界然后等分切成 N 段。虽然自增主键可能有空洞删过数据就会有但只要空洞比例不高分到每个线程的数据量偏差就在可接受范围内。需要注意一个细节不要用select count(*)去算总行数来决定分片大小。MySQL 的 InnoDB 执行 count(*) 需要扫索引数据量大的时候这条 SQL 本身就够喝一壶的。直接用 min/max 步长分段命中就走主键索引速度很快。2.2 任务拆分和合并的核心逻辑拆分的代码其实不复杂但很多人第一次写容易踩一个坑以为id / 步长就能保证不漏数据。比如你定义步长 STEP 100000那第一个线程查id BETWEEN 0 AND 100000第二个查100001 AND 200000。看起来很合理对不对问题出在你不知道 min(id) 是不是从 0 开始也不知道 id 分布是否存在断层。我的做法是先查出 min_id 和 max_id然后根据线程数计算每个线程负责的区间宽度再把区间改成[start, end)的半开区间并且让每个线程的起始 id 等于上一个线程的结束 id。这样即使中间有空洞也能保证每条存在的记录至少被一个线程扫到绝不会漏。2.3 为什么要用 CompletableFuture 而不是手动 new Thread这是老生常谈但必须再谈一次。手动new Thread在 JVM 里属于裸奔线程没有统一管理超过系统可用线程数就会频繁上下文切换而且很难优雅地控制超时和异常。CompletableFuture配合自定义线程池是最稳的组合。它帮你封装了异步任务编排、异常传播、结果收集。尤其是allOf().join()这个操作一行代码就能等所有线程跑完比 CountDownLatch 手动countDown()省心得多也不容易出现忘记 countDown 导致永久阻塞的 bug。注意CompletableFuture.supplyAsync()如果你不传线程池默认使用ForkJoinPool.commonPool()。这个池子被整个 JVM 共享一旦有其他框架尤其是并行流也在用你的查询任务和别人的任务就会互相抢占线程这是线上最容易排查不出来的疑难杂症。3. 完整实现从线程池配置到结果汇总3.1 第一步自定义业务线程池配置类先说结论线程数不要用网上流传的CPU核心数 1公式硬套。这是针对 CPU 密集型任务的经验值但数据库查询属于 IO 密集型任务查询时线程大部分时间在等数据库返回CPU 其实是空闲的。我这里有一个经验公式可以参考线程数 数据库连接池最大连接数 - 2。预留两个连接给其他业务使用避免你的查询线程把连接池全部占满导致别的接口全部卡死。这是一个我在生产环境踩过坑才悟出来的安全系数。Configuration public class QueryThreadPoolConfig { Bean(queryExecutor) public ThreadPoolTaskExecutor queryExecutor() { ThreadPoolTaskExecutor executor new ThreadPoolTaskExecutor(); // 核心线程数数据库连接池10 - 2 8 executor.setCorePoolSize(8); // 最大线程数不要超过连接池上限 executor.setMaxPoolSize(10); // 队列容量有界队列更安全 executor.setQueueCapacity(200); executor.setThreadNamePrefix(query-thread-); // 拒绝策略由调用者线程执行不丢任务 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 等所有任务结束再销毁线程池防止任务执行中被杀 executor.setWaitForTasksToCompleteOnShutdown(true); executor.setAwaitTerminationSeconds(60); executor.initialize(); return executor; } }这里每个参数我都有话要说核心线程数设 8假设你的 HikariCP 连接池最大连接数默认是 108 个线程同时查最多占 8 个连接。剩下 2 个连接是给其他接口的保命名额。既保证了查询性能又不至于因为一次接口调用拖垮整个应用。队列容量设 200而不是 0ThreadPoolExecutor 的核心线程满了之后新任务先进队列等队列满了才开新线程到最大线程数。如果你把队列设成 0任务会直接触发创建新线程极端情况下直接冲到 maxPoolSize。如果 maxPoolSize 又设置太大就可能出现1 亿条数据拆了 1000 个任务瞬间 1000 个线程全部去查数据库的惨案。拒绝策略选 CallerRunsPolicy这个策略的意思是队列满且线程数到上限时任务不被丢弃而是由提交任务的线程你的主线程直接执行。代价是主线程会被征用去查数据其他线程还在跑但保证了任务不会丢。3.2 第二步分片任务核心代码我用 MyBatis-Plus 演示核心代码但思路对任何 ORM 都适用。先定义一个查询参数的 DTO包含分片边界Data Builder public class QuerySlice { private Long startId; private Long endId; private int index; }再看查询 Mapper。注意这里只查询需要的字段千万不要select *。两千万行的表你查 200 个字段出来网络传输和对象映射的时间比 SQL 执行还长这个坑也是最容易被忽略的。public interface OrderMapper extends BaseMapperOrderEntity { Select(SELECT id, user_id, order_no, amount, status FROM t_order WHERE id #{startId} AND id #{endId} ORDER BY id ASC) ListOrderEntity selectSlice(Param(startId) Long startId, Param(endId) Long endId); }核心异步执行逻辑public ListOrderEntity queryAllDataByMultiThread() { // 1. 拿到最小/最大主键 Long minId orderMapper.selectMinId(); Long maxId orderMapper.selectMaxId(); if (minId null || maxId null) { return Collections.emptyList(); } // 2. 拆分成8个分片 int threadCount 8; long step (maxId - minId) / threadCount 1; ListCompletableFutureListOrderEntity futureList new ArrayList(); for (int i 0; i threadCount; i) { long startId minId i * step; long endId (i threadCount - 1) ? maxId 1 : startId step; QuerySlice slice QuerySlice.builder() .startId(startId) .endId(endId) .index(i) .build(); // 3. 异步提交查询任务 CompletableFutureListOrderEntity future CompletableFuture.supplyAsync(() - orderMapper.selectSlice( slice.getStartId(), slice.getEndId()), queryExecutor); futureList.add(future); } // 4. 等待所有线程返回合并结果 CompletableFutureVoid allDone CompletableFuture.allOf( futureList.toArray(new CompletableFuture[0])); allDone.join(); // 5. 按分片顺序收集保证结果整体有序 ListOrderEntity result new ArrayList(); for (CompletableFutureListOrderEntity future : futureList) { result.addAll(future.join()); } return result; }这里我解释几个细节都是折腾了几个晚上的心得体会step的计算要1假设 minId1maxId100线程数8那么 (100-1)/81 13。分片区间就是 [1,14)、[14,27)、[27,40)…… 最后一个分片 [92,101)。直观感受是比理论均分多算了 8 个 id但这保证了最后一个分片一定覆盖到 maxId。最后一个分片的 endId 显式设成maxId 1因为区间是[startId, endId)左闭右开如果 endId 不 1就会漏掉 maxId 那条数据。收集结果时按提交顺序遍历 futureListCompletableFuture.allOf().join()只保证任务都完成不保证完成顺序。但是按 futureList 的顺序遍历调用future.join()拿到的结果就是严格按分片顺序拼接的天然有序。如果要全局有序比如按 id 全量导出这一步就保证了结果集的有序性。3.3 第三步加上超时控制和异常处理上面那段代码有一个明显的隐患如果某个线程执行中出了问题allOf().join()会抛异常但其他线程可能还在跑。更严重的是如果忘了处理单个 future 的异常异常会被 join() 包装成 CompletionException排查起来很费劲。我的做法是给每个 future 都加 exceptionally 兜底并且给整个任务一个超时上限CompletableFutureListOrderEntity future CompletableFuture .supplyAsync(() - orderMapper.selectSlice(slice.getStartId(), slice.getEndId()), queryExecutor) .exceptionally(ex - { log.error(分片查询失败, index{}, startId{}, endId{}, slice.getIndex(), slice.getStartId(), slice.getEndId(), ex); return Collections.emptyList(); });然后主线程统一等try { allDone.get(5, TimeUnit.MINUTES); } catch (TimeoutException e) { log.error(多线程查询超时); // 这里可以做兜底逻辑比如降级为单线程查询 } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(查询被中断, e); }用get(5, TimeUnit.MINUTES)代替join()是我从一次生产事故里学到的。那次 MySQL 主从切换某个分片的 SQL 卡住了 10 分钟不返回如果没用超时控制整个调用方会一直挂着直到下游接口超时熔断。提示exceptionally 返回空列表这个策略适合查不到就算了的统计类、导出类场景。如果业务要求绝对完整你需要在 finally 里检查成功分片数如果有失败分片该告警告警、该重跑重跑。3.4 关于内存溢出的必答疑很多人在评论区问多线程查全表虽然快了但结果都放 List 里内存不是照样炸事实是这样的。如果你一次把所有数据都加载到应用内存里不管单线程还是多线程内存峰值是一样的。多线程优化的只是时间不是空间。如果你原本用单线程查全表就会 OOM那多线程查全表一样会 OOM只是 OOM 来得更快而已。正确的处理方式是消费端一边查一边处理不要攒在 List 里。比如你是全量导出应该每个分片查出来之后直接把结果交个写文件的服务去追加写入。如果只是做数据同步就查一批、同步一批、clear 一批。我的一个优化思路是把查询和消费解耦查询线程负责把数据塞到一个有界队列里消费线程负责写文件或同步数据。队列满了查询线程阻塞等待天然实现了背压内存使用就稳定在一个可控范围内。4. 实测中的性能对比与调优4.1 不同线程数的真实耗时对比说一个我在自己项目里的实测数据供参考环境是 4 核 8G 的测试机MySQL 5.7单表 1350 万行查 6 个常用字段每行数据平均 180 字节左右。线程数耗时说明123.6s基线213.2s提升约 1.8 倍47.8s提升约 3 倍85.9s提升约 4 倍165.8s几乎无提升看到没从 8 线程升到 16 线程几乎没提升。原因很典型数据库连接池最大连接数是 1016 个线程里有 6 个在排队等连接等待的过程白白浪费了线程资源。而且 4 核 CPU 的机器线程超过 8 个之后操作系统要频繁做上下文切换切换本身是要消耗 CPU 的。所以不要迷信线程越多越快。对你的系统来说一定存在一个临界值线程数 连接池连接数时性价比最高再往上加就是边际收益趋近于零。4.2 分片大小对性能的影响分片大小这个参数很多人会忽略但它对性能的影响不亚于线程数。分片太大比如一个线程查 500 万行单线程耗时过长多线程的优势被长尾拖累。八个线程里有七个跑完了在等最后一个等于白开。分片太小比如一个线程查 5 万行额外开销会变大。每条 SQL 都有网络往返、SQL 解析、执行计划生成这些固定开销分片越碎这些开销占比越高。我的经验值是每个分片控制在 50 万到 150 万行之间。当然这跟行的宽度字段数量强相关字段多就该降一档字段少可以适当放宽。如果拿捏不准用max_id - min_id推算出数据量然后按 100 万行一片来反推线程数就行。4.3 一种更均匀的分片思路前面我说按 ID 范围等分最简单但如果主键空洞严重删除大量历史数据会出现一个线程查 10 条另一个线程查 300 万条的极端倾斜。如果摊上这种情况我提供一条换思路的正解用SELECT id FROM t_order WHERE id BETWEEN ? AND ? ORDER BY id LIMIT 1这种方式在每一段的起点重新卡一个实际存在的最小 id。代价是每个分片多花一条索引查询收益是分片的数据量更均匀。数据倾斜严重时这多出来的一次查询成本可以忽略多线程负载均衡带来的收益相比之下非常可观。5. 多线程查询的连带事故近期踩过的三个坑5.1 坑一事务注解引发的连接占用雪崩第一次上线多线程查询的时候我把方法标了Transactional理由是查数据万一失败好回滚。结果呢查询高峰时连接池被打满其他业务接口集体超时。原因其实很好理解Transactional会让整个方法绑定在一个数据库连接上主线程拿着连接不释放。但子线程执行的查询用的是线程池里的其他线程它们要自己从连接池拿新的连接。主线程的连接 子线程的连接一起瓜分连接池。如果任务数量稍微多一点连接池瞬间耗尽。从此以后我给自己定了一条铁律多线程查询的方法永远不要加Transactional。只读场景不需要事务连Transactional(readOnly true)都不建议因为 getConnection 这个环节在每次操作数据库时才获取连接不加才能把连接使用时间压到最短。5.2 坑二连接泄漏导致连接池被缓慢抽干有一次我把线程池核心线程数调到了 15超过了 HikariCP 默认的 10第二天线上开始间歇性出现 Connection is not available, request timed out after 30000ms。排查链路是这样的先看 HikariCP 监控指标active 连接数一直稳定在 10pending 队列里有大量等待。再把线程池的线程数调回 10症状消失。确认逻辑线程数大于连接池上限时必然有线程在排队等连接。如果等待时间超过连接池的 30 秒超时阈值就直接抛异常。这个案例说明的其实是一个简单的道理线程池和连接池的容量必须联动治理线程池决定你开多少车连接池决定你有多少条车道。车道不够车再多也是堵在入口。5.3 坑三MySQL 侧排队的隐藏恶魔还有一次比较隐蔽。多线程把连接打满后每个线程的查询本身并不慢但所有 SQL 都在 InnoDB 的行锁和表锁的 wait 队列里排队MySQL 的整体吞吐不升反降。那次查询的数据刚好和另一个后台任务在更新同一批订单。只读的 select 在 RR 隔离级别下要生成 ReadView和正在更新的行产生版本链竞争。10 个线程把 10 个连接全部占住每个连接里是一条看似协商一致的 select实际上都在等待更新事务的锁释放。这种场景单纯调大连接池没用反而会更糟。正确解法是错峰把多线程查询安排在更新任务之后或者利用上游 binlog 同步出来的只读从库兜底。6. 代码再进阶用 CompletableFuture 串起查询和消费6.1 查询与写文件解耦前面说过内存问题的解法是把查询和消费解耦。这里给出一个可以直接抄的版本用CompletableFuture.thenAcceptAsync把查询结果交给独立的消费线程处理ExecutorService queryPool Executors.newFixedThreadPool(8, new ThreadFactoryBuilder().setNameFormat(query-pool-%d).build()); ExecutorService consumePool Executors.newFixedThreadPool(4, new ThreadFactoryBuilder().setNameFormat(consume-pool-%d).build()); ListCompletableFutureVoid futures new ArrayList(); for (QuerySlice slice : slices) { CompletableFutureVoid future CompletableFuture .supplyAsync(() - orderMapper.selectSlice(slice.getStartId(), slice.getEndId()), queryPool) .thenAcceptAsync(orders - { // 消费端写入CSV、同步数据、发送MQ等 excelExportService.appendToFile(orders); // 每一批处理完主动回收避免大List撑爆内存 orders.clear(); }, consumePool) .exceptionally(ex - { log.error(分片处理失败, slice{}, slice, ex); return null; }); futures.add(future); } CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();这个写法和前面版本的差别是查询和消费在不同的线程池里并行跑查询线程查完一批数据就往消费线程里丢不等消费完成就去查下一批。如果消费端跟不上thenAcceptAsync 的后续任务会在 consumePool 的队列里排着天然形成背压。我当时拿这套方案跑 1350 万行导出稳定内存占用从 2.3G 降到了 400M 左右而且总耗时反而比查完再写文件快了 30%。原因是一个分片查完马上开始消费不用等全部分片查完才处理。6.2 如果结果必须全部汇总怎么办如果你的业务确实需要拿到完整列表比如要做排序、去重、聚合没法用流式处理那就需要考虑分批汇总每个分片内部先做部分聚合只把聚合结果返回给主线程主线程做最终合并。比如你要统计某一天每个商品的总销量完全可以每个线程按商品 ID 先 group by 聚合返回的是小结果集。如果只是要看总数直接用select count(*)配合各个分片条件做部分计数最后 sum。如果必须要全量数据那就尽量把查询字段降到最少查完立刻处理处理完立刻释放引用。这种场景不管怎么优化内存峰值都在那里我的建议是评估一下业务能不能切成流式处理或者干脆用临时表、导出到文件再二次处理绕开应用内存这个瓶颈。6.3 线程池监控排查问题的基础保障多线程查询最怕任务卡住你不知道线程池满了你还往里丢。所以我强烈建议在生产代码里暴露线程池的监控指标至少要把活跃线程数、队列积压数、完成任务数打印到日志或上传到监控平台Scheduled(fixedDelay 60000) public void monitorQueryPool() { ThreadPoolExecutor pool queryExecutor.getThreadPoolExecutor(); log.info(查询线程池 - core{}, active{}, max{}, queueSize{}, completed{}, rejected{}, pool.getCorePoolSize(), pool.getActiveCount(), pool.getMaximumPoolSize(), pool.getQueue().size(), pool.getCompletedTaskCount(), pool.getTaskCount() - pool.getCompletedTaskCount()); }不要小看这一行日志。前面说的线程数和连接数不匹配问题如果你有这行监控看一次活跃线程数和队列积压数就能定位根本不用靠猜。我排查过很多线上问题80% 的性能事故从线程池监控指标里能看出蛛丝马迹。7. 面试常问的多线程查询高频问题梳理这个话题也是 SpringBoot 多线程方面的高频面试题我基于自己的面试和被面的经验整理几个必问点Q1多线程查询和单线程查询的适用边界在哪大数据量、只读场景、无强顺序要求时适合多线程。数据量小、写多读少、对一致性要求极高的场景多线程查询反而增加死锁和连接池耗尽的风险。Q2线程数怎么定看瓶颈在哪。数据库查询属于 IO 密集型线程数应参考数据库连接池的连接数压满连接池即可不要傻傻套用 CPU 核心数 1 的公式。连接数 10线程数就设 8~10留一点余量给非查询业务。Q310 个线程同时查同一张表MySQL 能扛住吗取决于数据库配置和表数据量。30 万行的小表10 个线程同时扫MySQL 没压力。2000 万行的大表10 个线程同时扫全表磁盘 IO 是瓶颈可能需要把多线程拆成的分片 SQL 都走主键索引尽量做到覆盖索引扫描减少回表。Q4查出来的结果顺序是乱的吗是乱的。任务完成顺序不可控。如果你对全局顺序有要求需要给每个分片编号在结果汇总时按编号排序或者用 ConcurrentSkipListMap 按 key 保证最终顺序。Q5怎么保证一个线程失败不影响整体每个子任务用 exceptionally 兜底失败分片单独统计。主线程 allOf().join() 之前判断成功分片数再决定整体是继续还是走补偿逻辑。Q6多线程查询会不会把数据库连接池打满会。所以线程数一定要小于等于连接池最大连接数。还要警惕生产环境有其他服务也在用同一个数据库连接池打满后其他服务也会一起陪葬。8. 最后的实战建议老规矩把我在实际项目中沉淀下来的几条铁律总结一下这些不是理论推演全是真金白银的代价换来的配置线程池之前永远先确认数据库连接池的 maximumPoolSize让查询线程数严格小于这个值。查询的字段能少就少select *在多线程场景下会放大网络和内存压力是一个不值得冒的险。结果集如果很大优先考虑流式消费而非全量汇总到 List。真到了必须全量的地步也请保证分片查询是边查边消费的。异常处理不能省。CompletableFuture 的异常默认是静默抛出不好好处理排查一次够折腾半天。测试环境压测通过不代表生产环境没问题生产环境的数据库数据量、索引状态、连接池负载都不一样上线前最好先在预发环境用真实数据量跑一遍。线程池的参数不是配一次就一劳永逸的。随着业务增长数据量从 500 万涨到 3000 万分片大小和线程数都要重新调优。你的监控日志就是调优的依据。如果这篇文章的代码方案帮你节省了几个小时我的目的就达到了。多线程查询没有太多花哨的东西原理就那几行但每个参数背后的权衡才是真正的经验所在。也欢迎你在评论区聊聊自己用多线程查全表时踩过的坑我保证每条都看。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

LSM6DSL与高稳晶振协同抑制温度漂移的运动传感方案 2026/9/16 5:03:59

LSM6DSL与高稳晶振协同抑制温度漂移的运动传感方案

1. 这不是“又一个运动传感器方案”——LSM6DSL与R7KA8D2KFLCAC组合的真实价值锚点你可能已经看过太多标题里带“高精度”“实时跟踪”“工业级”的传感器方案,点进去发现不过是把LSM6DSL的数据手册参数复制粘贴一遍,再配上一段模糊的加速度曲线图。但这…

阅读更多 →
嵌入式高精度时间中枢:MCP79510+RA8D2硬件协同设计 2026/9/16 5:03:59

嵌入式高精度时间中枢:MCP79510+RA8D2硬件协同设计

1. 项目概述:这不是“时间管理App”,而是一套嵌入式系统级时间中枢看到标题里“卓越的时间管理”这六个字,别急着点开日历软件——这根本不是讲怎么用Notion做周计划,也不是教你怎么番茄钟打卡。它说的是在一块没有操作系统、没有…

阅读更多 →
多时间尺度调度如何出创新点?综合能源系统优化实战指南 2026/9/16 5:03:59

多时间尺度调度如何出创新点?综合能源系统优化实战指南

1. 这个方向为什么能持续出成果:先看懂多时间尺度调度到底在解决什么问题我最早接触综合能源系统优化调度这个方向时,第一反应是"这不就是把电、气、热几个系统的优化问题耦合在一起算一遍吗"。真做下去才发现,事情远没那么简单。整…

阅读更多 →
AI短漫剧全链路生产实战:从ComfyUI部署到成本优化 2026/9/16 5:03:59

AI短漫剧全链路生产实战:从ComfyUI部署到成本优化

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
操作系统课程C语言源码阅读指南:从AbstractMachine到线程切换 2026/9/16 5:03:59

操作系统课程C语言源码阅读指南:从AbstractMachine到线程切换

简介:基于南京大学蒋炎岩教授2024春学期《操作系统》课程的源码包,是操作系统课程配套代码资源,面向高校学生、考研复习者及对OS底层机制感兴趣的自学者,能帮助读者结合真实代码理解操作系统设计与实现。压缩包共19个文件&#xf…

阅读更多 →
TMF8801+R7KA8D2KFLCAC高精度抗干扰ToF测距方案 2026/9/16 5:00:59

TMF8801+R7KA8D2KFLCAC高精度抗干扰ToF测距方案

1. 这不是“测距仪”,而是一套可嵌入、可编程、能穿透烟雾的光学距离感知系统你手头拿到的 TMF8801 和 R7KA8D2KFLCAC,不是两颗普通芯片——它们是当前消费级与工业级边缘设备中,少有的能同时兼顾高精度(1mm)、抗干扰性…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞