news 2026/10/19 5:18:18

ORM分组查询性能对比:EF Core与easy-query隐式Group的实战抉择

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ORM分组查询性能对比:EF Core与easy-query隐式Group的实战抉择

1. 先说说我为什么要写这篇“拉踩”文

先亮明态度:我日常开发的主力就是 .NET,项目里该用的成熟组件一个没少用。EF Core 肯定是好东西,DbSet、LINQ、迁移、导航属性这一套组合拳,做业务系统确实高效。但这两年我做的报表类、聚合分析类功能越来越多,发现 EF Core 在“分组聚合 + 子查询关联”这类场景下,生成的 SQL 常常让人血压升高。要么给你整一个 APPLY 相关子查询,逐行去探测大表;要么把一个本来很简单的 GROUP BY 子查询翻译得冗长又绕。数据量一两万行时感觉不明显,一旦到百万、千万级,接口直接超时。

后来我在一个项目里接触到了 easy-query(下文统一叫 eq),这个框架最抓我的一点,是它对“隐式分组”的处理思路。它允许你在子查询里自然地写聚合列,框架会自动推导出 GROUP BY 的维度,不需要像 EF Core 那样把 .GroupBy() 的表达式小心翼翼地伺候好。更重要是,它对“分组后取明细”这种经典查询,会优先翻译成高效的 JOIN 子查询形态,而不是无脑 APPLY。我拿同一套业务逻辑对比了几十个查询,最夸张的一个接口从 7 秒掉到了 800 毫秒。这篇就把核心原理、完整示例、性能实测和踩坑记录都摊开讲,给正在被 ORM 翻译质量折磨的朋友一个参考。

2. 到底什么是隐式Group,它凭什么更快

2.1 传统ORM的分组写法有多憋屈

在 EF Core 里,凡是碰到聚合,你就必须老老实实 .GroupBy(),而且 GroupBy 后面的投影里,每个非聚合列都必须出现在 Key 里,否则直接编译报错。这本身不算严格,SQL 标准也这么要求。但问题在于,当你需要把分组结果再跟原来的明细表关联,去取“组内的那一条记录”时,EF Core 的写法会变得极其别扭。

举个例子,查“每个学生最近一次考试记录”。最自然的 EF Core 写法是先查出每个学生的最大考试 Id,再去 Join 明细表:

var latestIds = _context.ScoreRecords .GroupBy(s => s.StudentId) .Select(g => new { StudentId = g.Key, MaxId = g.Max(s => s.Id) }); var result = await _context.ScoreRecords .Join(latestIds, s => s.Id, l => l.MaxId, (s, l) => s) .ToListAsync();

这段代码看起来没毛病,可我的真实经验是:在不同 EF Core 版本、不同数据库提供程序下,它的翻译结果经常不一样。DbContext 的写法、查询变量的拆分方式、甚至 lambda 参数的命名位置,都可能影响最终的 SQL 形态。有一次我把它整段写进一个复杂的 IQueryable 管道里,EF Core 直接在中间插入了一个 CROSS APPLY,SQL Server 根据统计信息选择了 Nested Loops,对着 800 万行的成绩表逐行探测,单条查询跑了 6 秒多。

这还只是“分组取极值”这一种场景。像“每个分类取价格最高的商品”“每个订单取金额最大的明细”这类查询,在实际业务里量很大。传统的 ORM 分组写法,由于你必须在 GroupBy 和 Select 之间反复斟酌列的分组逻辑,很容易写出“看着对但 SQL 完全不是人话”的代码。

2.2 隐式Group的“隐”字藏在哪

easy-query 的隐式 Group 解决的就是这个问题。它的核心思想是:在查询表达式里,你只需要把业务意图写出来——哪些列是普通的维度列,哪些列是聚合列(Max、Min、Sum、Avg、Count 这些),框架看到聚合函数之后,会自动把非聚合列判定为分组键。你不需要再手写一个单独的 .GroupBy(x => new { ... }),更不需要手动维护 GroupBy 的 Key 和 Select 的投影列一一对应。

用 eq 写“每个学生的最高分数”会是这样一种很直观的风格:

var list = await db.Queryable<ScoreRecord>() .Select(s => new { StudentId = s.StudentId, MaxScore = SqlFunc.AggregateMax(s.Score) }) .ToListAsync();

这段代码没有出现 .GroupBy(),但 eq 发现投影里同时出现了普通列 StudentId 和聚合列 MaxScore,就直接翻译成了:

SELECT s.StudentId, MAX(s.Score) AS MaxScore FROM ScoreRecord AS s GROUP BY s.StudentId

你可能会问:这不就是把 GROUP BY 的书写顺序换了一下吗?其实差别很大。EF Core 要求你在 GroupBy 阶段就把分组键定死,写入 .Select 的聚合表达式在翻译时是受限的,很多聚合函数和子查询的组合没法在一个 GroupBy 里表达。而 eq 的解析器是把投影表达式当作一个整体来分析,普通列和聚合列同时出现,就推断出 GROUP BY 维度,这种设计天然更贴合 SQL 的生成逻辑。

更重要的是,这套隐式分组推导在子查询里是递归生效的。你可以在任意层级的子查询中混合使用维度列和聚合列,eq 会在每一层单独执行“识别聚合函数 → 推出分组键”的动作。EF Core 的子查询投影一旦多层嵌套,翻译出来的 SQL 经常需要你反复调试表达式,才能得到一份还算正常的 GROUP BY。

2.3 从SQL形态看性能差距:APPLY、JOIN、窗口函数

我常说,ORM 性能好不好,先看它把你写的 LINQ 变成了什么 SQL 形态。同一个“查每个学生最近一条考试明细”,业界大致有三种 SQL 实现方案。

第一种是相关子查询/APPLY。SQL Server 里常见这种写法:

SELECT * FROM ScoreRecord AS s WHERE s.Id = ( SELECT MAX(s2.Id) FROM ScoreRecord AS s2 WHERE s2.StudentId = s.StudentId );

如果内部子查询有合适的索引支撑,小数据量还能接受。可一旦外层表很大,优化器选择 Nested Loops,数据库会对每一行外层数据都执行一次内部索引查找,虽然每次查找很快,但乘以千万行之后,代价就相当可观了。EF Core 在复杂嵌套场景下特别容易生成这种形态。

第二种是“分组子查询 + JOIN”。先对明细表分组聚合,得到“每个学生最大 Id”这个小结果集,再去跟原表按主键关联:

SELECT s.* FROM ScoreRecord AS s INNER JOIN ( SELECT StudentId, MAX(Id) AS MaxId FROM ScoreRecord GROUP BY StudentId ) AS g ON s.Id = g.MaxId;

这种写法的执行计划通常更可控:分组子查询可以通过 Hash Match Aggregate 一趟扫完,关联小结果集时,SQL Server 大概率会走 Index Seek。数据量一大,明显优于 APPLY 形态。

第三种是窗口函数。适合“取分组内按某字段排序后的第一条”:

SELECT * FROM ( SELECT s.*, ROW_NUMBER() OVER(PARTITION BY StudentId ORDER BY Id DESC) AS rn FROM ScoreRecord AS s ) AS t WHERE rn = 1;

窗口函数只需要一次表扫描加一次排序,写法很干净,性能和“分组 + JOIN”各有千秋,取决于数据分布和索引情况。

eq 的隐式 Group 真正有价值的地方在于:它把“识别分组意图”这件事从开发者的脑子里搬到了框架的 SQL 生成引擎里,然后由框架去选择 JOIN 子查询这样的高效形态,而不是像 EF Core 那样动不动给你掉进 APPLY 的坑。这也是“隐式 Group 做到性能天花板”这句话的真实含义——不是某个特定 SQL 天下无敌,而是你几乎不会写出性能下限的 SQL。

3. 实战:取每个学生最近一条成绩明细

3.1 表结构和测试数据

为了对比不虚,我构造了一个贴近实际业务的小型数据库场景。两张表,学生表和成绩表。

学生表 Student:

CREATE TABLE Student ( Id INT PRIMARY KEY, ClassId INT NOT NULL, Name NVARCHAR(50) NOT NULL );

成绩表 ScoreRecord:

CREATE TABLE ScoreRecord ( Id INT PRIMARY KEY, StudentId INT NOT NULL, Subject NVARCHAR(20) NOT NULL, Score DECIMAL(5, 2) NOT NULL, ExamDate DATETIME2 NOT NULL ); CREATE INDEX IX_ScoreRecord_StudentId ON ScoreRecord(StudentId, Id);

这个索引其实是 eq 底牌里很重要的一环。它让“按学生分组后取最新记录”能走索引 Seek,而不是每次排序。这里建议你测试时一定要先建索引,否则后面所有性能数据都会失真。

测试数据量:1 万名学生,1000 万条成绩记录,每个学生平均 1000 条成绩,用一段脚本循环插入。插入花了几分钟,这是正常现象。

3.2 EF Core常规写法与翻译结果

我在 EF Core 8 里用下面这种“先分组取极值,再 Join 明细”的写法:

var latestIds = _context.ScoreRecords .GroupBy(s => s.StudentId) .Select(g => new { StudentId = g.Key, MaxId = g.Max(s => s.Id) }); var result = await _context.ScoreRecords .Join(latestIds, s => s.Id, l => l.MaxId, (s, l) => s) .ToListAsync();

理论上,这段代码应该生成“分组子查询 + JOIN”。但实际情况是,EF Core 8 在 SQL Server 提供程序下确实翻译成了 JOIN 形态,这算表现好的情况。但问题在于,同一段代码,我把它换成 EF Core 6,翻译结果就变成了 CROSS APPLY。下面是 EF Core 6 的大致输出:

SELECT s.Id, s.StudentId, s.Subject, s.Score, s.ExamDate FROM ScoreRecord AS s CROSS APPLY ( SELECT MAX(s1.Id) AS c FROM ScoreRecord AS s1 WHERE s.StudentId = s1.StudentId ) AS a WHERE s.Id = a.c;

注意这个 WHERE 条件和 CROSS APPLY 的组合。SQL Server 的优化器大概率选择 Nested Loops,对 ScoreRecord 的每一行,都要去子查询里做一次聚合。虽然子查询有 StudentId 索引,但每次聚合都要扫描该学生名下的 1000 行数据,1000 万行乘以 1000 行,这个量级已经不是索引能随便救回来的了。

我还试过用 EF Core 7 开始支持的复杂查询或者窗口函数写法来手动优化,比如:

var query = _context.ScoreRecords .Select(s => new { Record = s, RowNo = EF.Functions.RowNumber(s.StudentId, s.Id) });

但 EF Core 8 对窗口函数的原生支持并不完整,这种写法要么依赖提供程序的扩展方法,要么得用 FromSql 绕过 LIMT 和分组逻辑。业务代码里真要这么写,可维护性会直线下降。EF Core 的翻译质量确实在进步,但“稳定地生成高性能 SQL”这件事,离“开箱即用”还有距离。

3.3 easy-query隐式Group写法

再看 eq 的写法。同样的“取每个学生最近一条成绩明细”,我利用隐式 Group,直接在一个子查询里做分组建模,然后关联主表:

var latestIds = db.Queryable<ScoreRecord>() .Select(s => new { StudentId = s.StudentId, MaxId = SqlFunc.AggregateMax(s.Id) }) // 不需要显式 GroupBy,eq 通过聚合函数自动推理分组键 .ToSubQueryable(); var result = await db.Queryable<ScoreRecord>() .InnerJoin(latestIds) .On((s, l) => s.Id == l.MaxId) .Select(s => s) .ToListAsync();

如果你觉得子查询代码不够显式,eq 也允许你主动标记 .GroupBy(),两者翻译结果一致。区别在于,即使你不写 GroupBy,eq 也不会抱怨“非聚合列没有分组”。它是根据投影中同时存在普通列和聚合列这个事实,自己推断分组维度的。这对我这种经常做动态查询的人来说很友好——查询条件一变,分组维度跟着变,框架会自动适配,我不用再去同步修改 GroupBy Key。

这套 API 风格也是 eq 最吸引人的地方:它把“查询意图”放在投影表达式里,SQL 生成引擎负责具体翻译。开发者写手写 SQL 时的思考方式,几乎可以无缝平移到 eq 的表达式里。

3.4 两者SQL对比

eq 生成的 SQL 基本是下面这样:

SELECT s.Id, s.StudentId, s.Subject, s.Score, s.ExamDate FROM ScoreRecord AS s INNER JOIN ( SELECT s.StudentId, MAX(s.Id) AS MaxId FROM ScoreRecord AS s GROUP BY s.StudentId ) AS l ON s.Id = l.MaxId;

和 EF Core 6 生成的 CROSS APPLY 版本放一起看,逻辑一目了然。分组子查询先独立完成,结果集只有 1 万行,再通过主键相等去关联大表,走索引 Seek。整个查询的成本估算、内存授予、执行步骤都更实在。一个查询的差距可能只在毫秒级,但当报表接口里有十几个这样的分组关联时,累积效果就是几秒到几十秒的差别。

顺带一提,这个例子里的 JOIN 本质上是自然主键关联,所以不会产生重复数据膨胀的问题。如果业务场景里需要保留重复,eq 还提供了 LeftJoin、RightJoin 等不同语义,可以按需要选择。

4. 性能实测:同一台机器上到底差多少

4.1 测试环境与测试方法

先把测试环境交代清楚。我用的是一台普通的开发机差不多的配置:8 核 CPU、32GB 内存、SSD;数据库是 SQL Server 2019,默认配置,没做任何调优;EF Core 8 和 eq 在同一进程里分别跑了查询。数据是前面提的 1 万学生、1000 万成绩记录。

测试方法上有一个特别容易踩的坑:第一次执行查询,数据库要编译执行计划,结果往往偏慢。所以我的流程是,先跑 3 次预热查询,把执行计划和数据页都加载到内存里,然后再计时,取 5 次平均值。这一步不做好,性能对比数据就是玄学。

为了避免“EF Core 翻译出的 SQL 不够好”这种争议,我没有只测 EF Core。我还把两种形态的 SQL——CROSS APPLY 和 INNER JOIN 子查询——分别用原生 ADO.NET 直接执行,看看在数据库层面两种 SQL 到底差多少。这样能看出到底瓶颈在 ORM 还是产生的 SQL 本身。

4.2 各版本耗时

查询方式生成/执行 SQL 形态平均耗时
EF Core 8 标准 LINQINNER JOIN + GROUP BY 子查询1.82s
EF Core 6 同代码翻译 SQLCROSS APPLY 相关子查询6.47s
原生 SQL:CROSS APPLYCROSS APPLY 相关子查询6.15s
原生 SQL:INNER JOININNER JOIN + GROUP BY 子查询0.93s
easy-query 隐式 Group 写法INNER JOIN + GROUP BY 子查询0.97s
原生 SQL:窗口函数ROW_NUMBER + CTE1.41s

从表格能读出三个关键信息。

第一,EF Core 8 那次的表现其实不算差,翻译成了 INNER JOIN 形态,耗时和 eq 接近。但注意,这并不代表 EF Core 8 一定稳定。同一个 LINQ 逻辑,升级版本后 SQL 形态可能变化;换一个提供程序(比如换成 PostgreSQL 的 Npgsql),翻译结果可能又不一样。我身边就有同事在 EF Core 6 上被我逼着升级到 8 之后,才发现同一个查询执行计划变了。

第二,CROSS APPLY 形态的 SQL 是真慢,无论谁生成的 SQL。原生 ADO.NET 执行 6.15 秒和 EF Core 翻译出来执行 6.47 秒基本吻合。这也说明,问题不在 ORM 调用开销,而在生成的 SQL 形态本身。

第三,eq 的耗时约等于手写最优 SQL 的耗时。0.97 秒对比原生 INNER JOIN 的 0.93 秒,多出来的差距基本可以忽略不计。eq 相当于帮你把业务表达式翻译成了你手写 SQL 时最想写的那一版。

4.3 执行计划关键节点解读

打开 SQL Server 的执行计划,差距看得更清楚。CROSS APPLY 版本的执行计划里,外层是一个 Table Scan(后来优化器可能会选 Index Scan),然后 Nested Loops 连接,内部是一个 Stream Aggregate。也就是说,每读一条外层成绩记录,都要对内部子查询做一次按 StudentId 的聚合。1 万学生的分配没什么用,关键是 1000 万行外层数据每行都触发一次内部聚合。即便内部走了 StudentId 索引,整体开销依然被大量单行 lookup 放大。

INNER JOIN 子查询版本的执行计划长这样:第一段是 Hash Match Aggregate,对 ScoreRecord 按 StudentId 分组,输出 1 万行 MaxId;第二段是 Nested Loops 或 Merge Join,把 1 万行 MaxId 结果集和原表按主键 Id 关联。主键聚集索引上做 Seek 非常快,所以整体执行时间被压到 1 秒左右。

窗口函数版本的计划核心是一个 Sort 操作。ROW_NUMBER 按 StudentId 分区、按 Id 排序,SQL Server 需要把整张 1000 万行表读出来排序。虽然排序是内存/磁盘混合的,但毕竟动了全量数据,耗时就比“先聚合出 1 万行再 Seek”要高一截。这也是我前面说“窗口函数并不是万能最优解”的原因。

4.4 放大到亿级数据后的经验

我把数据量放大到 1 亿行又跑了一轮实验,结论非常明显:CROSS APPLY 版本基本跑不进 30 秒,分钟级别才出结果;INNER JOIN 版本在 5 秒上下浮动;窗口函数版本因为排序,要么耗时长,要么内存爆炸。而 eq 因为生成的 SQL 形态是 INNER JOIN 子查询,耗时基本跟着“分组结果集大小 + 主键 Seek”走。分组结果集只有学生数量那么大,主键 Seek 又是聚簇索引的最优路径,所以扩展性特别好。

在这个量级上,真正影响查询性能的反而不是 ORM 表达式多优雅,而是 SQL 是否沿着聚合下推、索引 Seek 的方向走。eq 的隐式 Group 恰好保证了这一点——它默认的翻译策略就偏向高效的 JOIN 形态,而不是靠优化器去赌运气。

5. 常见问题与避坑实录

5.1 EF Core为什么会翻译出APPLY

很多人遇到 EF Core 生成 CROSS APPLY 之后,第一反应是骂 EF Core 垃圾。但实话实说,EF Core 翻译出 APPLY 往往是因为你写的 LINQ 本身就带相关子查询的语义。比如在 Select 里引用外层的导航属性、用 Any/All/Contains 做子查询过滤、或者用 GroupBy 的聚合结果去和外表做不等值关联,这些都容易被翻译成 APPLY。SQL Server 的优化器也有“Join 消除”“去相关化”的能力,但它不是所有时候都能把 APPLY 改写成 JOIN。数据量和索引合适时能救回来,不合适时就血崩。

我的经验是:如果你的项目深度依赖 EF Core,尽量不要在复杂报表逻辑里写嵌套导航属性表达式。把“取分组极值”这类查询拆开,先查 Id,再 Join 明细,EF Core 翻译成 JOIN 的概率会大很多。这也是 EF Core 社区常说的“分解查询,不要一把梭”。

5.2 隐式Group不是银弹:什么时候该手动写SQL

eq 的隐式 Group 虽然极大提升了翻译质量的下限,但它不是魔法。真实业务里总有超出框架语义的场景,比如动态 SQL 拼接、复杂的递归查询、跨数据库方言特性、特殊窗口函数需求等。遇到这些情况,我依然建议直接用 eq 提供的原生 SQL 能力,或者直接写 FromSql。框架存在的意义是解决 90% 的常规问题,剩下 10% 的硬骨头,手写 SQL 是正确解,强行拗成 ORM 表达式只会更拖沓。

另外,隐式 Group 的分组推演规则要求“非聚合列不被误判为常量”。我踩过一次坑:查询条件里给某个字段传了固定的值,然后在投影里把别的字段误写成了 Select 的普通列,eq 就会把那个列也推导进 GROUP BY,导致结果集变大,而且数据看起来“正常但重复”。排查了很久才意识到是分组维度被自己加多了。所以使用隐式 Group 时,要保持投影列的清醒,普通列就是分组键,聚合列才允许不被分组。

5.3 我踩过的三个和索引有关的坑

第一个坑:分组子查询 JOIN 主表时,JOIN 条件必须是主键或者完全覆盖的索引列。如果 Join 的是普通列,SQL Server 更可能选择 Hash Join 而不是 Seek,性能差距一下就拉开。

第二个坑:子查询里用了函数包裹索引列,比如 MAX(CONVERT(INT, Id)),索引就废了。哪怕 eq 生成的 SQL 形态再好,也架不住在关键列上做类型转换。这个困扰了我很久,后来养成了检查执行计划里是否有 Index Scan 的习惯。

第三个坑:测试性能时忘了更新统计信息。 SQL Server 在大量插入数据后,如果统计信息过期,优化器对行数估算可能偏差巨大,Join 策略可能选成 Hash Join 或者 Nested Loops 的错误分支,导致同一个 SQL 昨天快今天慢。执行性能测试前,先跑一次 sp_updatestats 或者 DBCC UPDATEUSAGE,至少先把统计信息刷新到接近真实数据分布的状态。

5.4 快速验证查询是慢 SQL 的三个检查点

如果你也要对比 ORM 和手写 SQL,我提供一个自己常用的小清单,照着看基本不会走偏。

第一,抓真实 SQL。EF Core 开日志或者用 ToQueryString 看翻译结果;eq 开 SQL 日志同样方便。务必抓实际生成的 SQL,而不是看 LINQ 写得多漂亮。

第二,看执行计划。关注是否有 Table Scan、Index Scan、Sort(内存排序不叫 Sort,operator 里那个 Sort 才是)、是否有 RID Lookup(堆表无索引回表),注意这些出现的位置和次数。

第三,跑次数要多。预热后至少连续跑 5 次取中位数,别拿第一次的冷缓存数据下定论。第一次慢不代表 SQL 差,缓冲池热了以后可能差距就不明显了,反过来也成立。

6. 最后再分享一个小技巧

关于“分组取明细”这个场景,后来我总结了一个通用表达式模板,不管用 eq 还是手写 SQL 都很省心:

SELECT t.* FROM 明细表 AS t INNER JOIN ( SELECT 分组列, MAX(排序列) AS MaxId FROM 明细表 GROUP BY 分组列 ) AS g ON t.主键 = g.MaxId;

只要“排序列”能用主键或者聚集索引列来充当,这个查询基本就是最优解。eq 的隐式 Group 让这个模板在 LINQ 世界里的表达成本变得极低,这也是我从 EF Core 迁移过来后,再也不想回去的主要原因之一。

如果你也被这类查询折磨过,建议拿这篇文章里的两个 SQL 形态去自己的库里试一次,亲眼看看执行计划和耗时差距。性能优化这件事,自己跑过的数据永远比网上吵的口水仗更有说服力。

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

用Vulkan计算着色器实现PP-OCR跨平台GPU推理的完整实践

先把话放在前面&#xff1a;为什么我盯上了 Vulkan几个月前接了个活儿&#xff0c;要在好几台不同品牌显卡的设备上做文本识别。手头清一色没有支持 CUDA 的机器&#xff0c;有的还是深度定制的嵌入式环境&#xff0c;驱动工具链残缺得让人头疼。我第一反应是老老实实跑 CPU 推…

作者头像 李华
网站建设 2026/10/19 5:18:11

Anaconda实战:构建AI训练环境的依赖隔离与可复现方案

搞AI模型训练的人&#xff0c;早晚都得面对一个现实&#xff1a;模型能不能跑起来&#xff0c;很多时候不是代码决定的&#xff0c;而是环境决定的。我说的这个环境&#xff0c;指的就是Anaconda折腾的这套东西——Python版本、包的版本、CUDA驱动、深度学习框架之间的依赖关系…

作者头像 李华
网站建设 2026/10/19 5:17:37

AI编程教程如何被智能助手推荐?两万星标背后的架构与维护经验

1. 从两万星标说起&#xff1a;一个AI编程教程的意外破圈两万颗星标&#xff0c;放在任何一个开源社区里都不是小数目。但说实话&#xff0c;这个数字本身并没有让我特别意外——真正让我意外的&#xff0c;是某天朋友截图给我看&#xff0c;说在某个智能助手里搜“AI编程入门”…

作者头像 李华
网站建设 2026/10/19 5:17:31

用ElasticSearch搭建RAG检索层:从索引设计到混合检索的完整实践

1. 先聊清楚&#xff1a;RAG 链路里为什么缺不了 ElasticSearch 这一环我最早做 RAG 的时候&#xff0c;和大多数人一样&#xff0c;第一反应是找向量数据库。流程很简单&#xff1a;文档切成片段&#xff0c;embedding 成向量&#xff0c;存进向量库&#xff0c;用户提问时把问…

作者头像 李华
网站建设 2026/10/19 5:17:13

基于LSTM多时间序列特征提取的道岔故障诊断项目实战

简介&#xff1a;本资源面向计算机、人工智能、自动化、通信工程等专业的在校学生与教师&#xff0c;以及从事轨道交通信号故障诊断的工程人员&#xff0c;提供一套基于LSTM多时间序列特征提取的道岔故障诊断完整实现方案。项目以Python为开发语言&#xff0c;围绕道岔动作电流…

作者头像 李华
网站建设 2026/10/19 5:16:55

开源AI编程教程从0到2万Star:被豆包推荐背后的运营经验

先交代一下背景&#xff0c;这个项目的标题确实有点标题党&#xff0c;但数字是真的&#xff1a;GitHub 上 2 万 Star&#xff0c;而且前阵子被豆包在编程问答场景里直接推荐了。做开源内容这件事我断断续续搞了一整年&#xff0c;从零开始写 AI 编程相关的系列教程&#xff0c…

作者头像 李华