news 2026/9/28 13:06:45

EF Core LINQ查询导致SQL Server CPU 100%:列套函数如何引发全表扫描

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
EF Core LINQ查询导致SQL Server CPU 100%:列套函数如何引发全表扫描

下午 3 点 47 分,客户 IT 负责人打来电话,语气很急:"数据库 CPU 100% 了,报表导不出来。"

会议室瞬间安静了。我当时的第一个念头不是"代码写得烂",而是先确认一个问题:这 100% 到底是数据库进程烧出来的,还是应用服务进程烧出来的。这个区别会直接决定排查方向。后来证明,这判断帮我们省掉了至少两小时的无用功。

折腾到晚上,真凶锁定在一行 LINQ 代码上。严格说,是这段查询里的一个.Where条件。那一行代码让 SQL Server 对一张 286 万行的订单表做了全表扫描,每一行都要先执行一次CONVERT(date, ...)再比较。对账报表每次跑,数据库 CPU 就拉满,页面假死,财务那边干瞪眼等数据。

这篇文章我会从报障现场说起,把完整的排查链路、根因原理、修复方式和后续防坑措施全部讲清楚。如果你是 .NET 后端开发,或者正在用 EF Core 写查询,这篇文章里的坑大概率你也会踩。踩过也没关系,关键是下次看到类似的代码,你能第一时间反应过来。

1. 下午三点四十七,客户说数据库 CPU 又红了

1.1 报障现场:一个不能再普通的月度对账日

客户的环境不复杂:.NET Core 3.1 应用,EF Core 3.1 做数据访问,数据库是 SQL Server 2016,核心业务是 B2B 订单系统。整个部署就是一台应用服务器加一台数据库服务器,下单量不算小,订单表积累了 286 万行。

当天上午系统一切正常,下午两点多财务开始跑前一天的订单流水对账。三点半过后,客户内部反馈"对账报表一直转圈",紧接着 IT 负责人看了一眼服务器监控,发现数据库 CPU 已经在 95% 以上徘徊。再过几分钟,整条业务链路都开始变慢——不光是报表,订单查询也开始卡。

这种情况下,很多人第一反应是"是不是有人跑了一个大 SQL"或者"是不是索引失效了"。但我不建议上来就拆代码。CPU 100% 是一个结果,不是一个原因,你得先搞清楚是哪个进程在烧 CPU,再顺着线索走下去。

1.2 第一轮排查:先确认是数据库进程在烧 CPU,而不是应用进程

我让客户打开任务管理器,看 CPU 占用最高的进程。几秒后对方报给我:sqlservr.exe几乎拉满,应用进程反而很安静。

这一步信息量很大。如果是w3wp.exe或者 Kestrel 应用进程烧 CPU,重点应该在应用代码,比如 LINQ 的客户端评估、内存大对象、死循环。但数据库进程烧 CPU,方向就清晰了:问题出在数据库侧的执行计划,要么是慢 SQL 大量占用 CPU,要么是编译风暴,要么是大量并发扫描任务。

我没有立刻去分析慢 SQL,而是做了两个动作:

第一,让客户打开 Windows 性能监视器,确认Processor(_Total)\% Processor Time和Process(sqlservr.exe)\% Processor Time的曲线确实长时间居高不下;

第二,让客户在 SSMS 里打开活动监视器,看当前有哪些会话正在跑、等待类型是什么。

活动监视器一打开,真相就已经露出一半了:有 4 个 SPID 在反复执行同一个 SQL,每个会话的Wait Type都带SOS_SCHEDULER_YIELD。这个等待类型不是一个坏信号,它说明这些会话不是被锁卡住,而是 CPU 密集型任务在主动把线程让出去——换句话说,SQL Server 的调度器正在全力轮转,干的全是脏活累活。它们执行的就是同一段报表查询。

到这一步,我已经基本确定:这段 SQL 在反复全表扫描,扫描的数据量太大,把 CPU 吃满了。接下来要做的,就是把这段 SQL 从执行计划里完整挖出来。

2. 抓真凶:执行计划比代码诚实得多

2.1 从 DMV 里翻出正在烧 CPU 的 SQL

活动监视器能看到请求,但看不到完整 SQL 文本和执行计划。我让客户在 SSMS 里跑了一段 DMV 查询,专门从缓存里捞 CPU 消耗最高的语句。

SELECT TOP 20 qs.total_worker_time / 1000 AS total_cpu_ms, qs.execution_count, qs.total_logical_reads, SUBSTRING(st.text, 1, 2000) AS sql_text FROM sys.dm_exec_query_stats AS qs CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS st ORDER BY qs.total_worker_time DESC;

这份查询结果里,排名第一的语句total_cpu_ms高出第二名一个数量级。文本格式化以后,核心部分长这样:

SELECT [o].[Id], [o].[CustomerId], [o].[OrderNo], [o].[TotalAmount], [o].[Status] FROM [Orders] AS [o] WHERE CONVERT(date, [o].[CreatedDate]) BETWEEN @p0 AND @p1 ORDER BY [o].[CreatedDate] DESC;

BETWEEN @p0 AND @p1是 EF Core 参数化生成的,@p0和@p1分别是前一天零点、当天零点。真正刺眼的是这句:

CONVERT(date, [o].[CreatedDate])

熟悉 SQL Server 的人看到这行基本就明白了:CreatedDate列被CONVERT函数包住了一层,这叫"列上套函数",是破坏索引使用最典型的写法之一。

2.2 SQL 特征反推:问题指向 LINQ 里的一行.Where

拿到 SQL 文本之后,下一步是从应用代码里把它找出来。我不建议直接在几十个文件里肉眼搜索,先看 SQL 的特征:CONVERT(date, ...)是 EF Core 翻译DateTime.Date属性的典型产物。凡是你在 LINQ 查询里写了o.CreatedDate.Date,EF Core 3.1 在 SQL Server 上翻译出来基本就是CONVERT(date, [列名])。

于是我用这个特征在代码库里一搜,问题查询很快就浮出水面。这是一段导出前一日订单流水的方法,其中有一行.Where条件:

var startDate = DateTime.Today.AddDays(-1); var endDate = DateTime.Today; var list = await db.Orders .Where(o => o.CreatedDate.Date >= startDate.Date && o.CreatedDate.Date <= endDate.Date) .OrderByDescending(o => o.CreatedDate) .ToListAsync();

整段查询只有这一个条件,页面上导出按钮一点,它就从订单表里捞数据。但就是这一行.Where,把CreatedDate.Date翻译成了CONVERT(date, [o].[CreatedDate]),导致索引没法走。

2.3 实锤:执行计划里的 Clustered Index Scan 和 CONVERT 谓词

光靠 SQL 文本猜测还不够,得看执行计划。我让客户用 SSMS 把这条 SQL 的实际执行计划抓出来,结果非常明确:

  • 操作类型:Clustered Index Scan,不是 Index Seek;
  • Estimated Number of Rows Read:286 万,也就是说整张表被完整扫了一遍;
  • 在扫描操作符的 Predicate 里,清清楚楚写着CONVERT(date, [o].[CreatedDate]) >= @p0 AND CONVERT(date, [o].[CreatedDate]) <= @p1。

索引不是没有。订单表在CreatedDate上建了一个非聚集索引,可它在这次查询里完全没有被使用。因为CONVERT包住了列,SQL Server 无法把CONVERT(date, CreatedDate)这个表达式和索引对应的那一列直接建立 seek 关系。

这里有个很重要的现实问题:为什么开发本地从来测不出来?原因很简单。本地订单表可能只有几万行,SQL Server 优化器在估算成本的时候发现"我全表扫描也就扫个几万行,耗时几十毫秒",那它当然会选择直接扫,不需要走索引。而生产环境是 286 万行,扫描一次的成本高得吓人。数据量一上去,同样的执行计划就变成灾难。

3. 根因拆解:为什么"列上加函数"等于宣判索引死刑

3.1 SARG:SQL Server 判断能不能用索引的分水岭

要彻底理解这个问题,必须知道一个词:SARG(Search ARGument)。SARG 指的是"搜索参数",是一个可以被索引定位的谓词表达式。判断标准很朴素:

条件能不能被优化器拆解成"某列和某个确定值的直接比较"?如果能,就有机会走 Seek;如果不能,优化器只能退而求其次去扫描。

举个例子,CreatedDate >= @start AND CreatedDate < @end是标准的 SARG 写法,因为列在左边保持原样,没有套任何函数,右边是参数,SQL Server 能直接基于索引 B-Tree 定位到起始位置,然后只读需要的范围。

而CONVERT(date, CreatedDate) BETWEEN @p0 AND @p1就不一样了。条件等于说"先把这一列每一行的值都做一次加工,用加工后的结果再去比较"。索引里存储的是原始值,不是加工后的值,优化器没法用 B-Tree 定位,只能把所有行的原始值一个个取出来,逐行做CONVERT,再逐行比较。

如果你用生活场景类比:索引相当于一本字典的拼音目录。你查"zhang"可以直接翻目录找到那一页;但如果你要求先"把每个字的首字母取出来再判断",那就等于要求把整本字典每一页都翻一遍,边翻边用规则筛选。数据量小的时候感觉不出来,数据量一大,CPU 也就被这种"逐行加工"吃满了。

3.2 EF Core 的.Date到底翻译成了什么

很多开发者在写o.CreatedDate.Date的时候,心里想的是"我先取到这个日期的零点,再拿这个零点去和另一个零点比较"。这个理解放在普通 C# 代码里没错,但在 LINQ to Entities 里,它不是这么工作的。

LINQ 查询本质是一棵表达式树。EF Core 拿到这棵树之后,会把它翻译成数据库能执行的 SQL,翻译规则由各个数据库的 Provider 决定。在 SQL Server Provider 里,DateTime.Date属性的翻译结果就是:

CONVERT(date, [o].[CreatedDate])

你看,取日期部分的操作,最终是落在了数据库的列上,而不是落在参数那一侧。列一旦被包裹,索引就废了,这就是问题本质。

至于为什么 EF Core 不直接翻译成更好的写法?因为在 EF Core 看来,.Date表达的就是"对列值做日期转换",它只能把这个语义如实翻译出来。它没法自动猜测"其实你想表达的是时间范围"。这种语义鸿沟,恰恰是 LINQ 查询优化里最危险的地方——代码看起来合理,翻译结果却暗藏杀机。

3.3 正确写法:半开区间是怎么设计出来的

修复思路很直接:不要对列做加工,把条件改写成可直接定位的区间比较。你要查的是"前一天零点到今天零点",那就把条件写成一个左闭右开区间:

var startDate = DateTime.Today.AddDays(-1); var endDate = DateTime.Today; var list = await db.Orders .Where(o => o.CreatedDate >= startDate && o.CreatedDate < endDate) .OrderByDescending(o => o.CreatedDate) .ToListAsync();

翻译成 SQL 就是:

SELECT [o].[Id], [o].[CustomerId], [o].[OrderNo], [o].[TotalAmount], [o].[Status] FROM [Orders] AS [o] WHERE [o].[CreatedDate] >= @p0 AND [o].[CreatedDate] < @p1 ORDER BY [o].[CreatedDate] DESC;

列保持了原样,右边是确定的参数。执行计划从Clustered Index Scan直接变成Index Seek,SQL Server 只读索引范围内对应的那部分数据,剩下的就不碰了。

为什么用< endDate而不是<= endDate,或者干脆写BETWEEN?这是老经验了。BETWEEN在语义上是闭区间,等价于>= start AND <= end。可日期时间列经常带毫秒甚至更小的精度,一旦数据里有2026-01-02 00:00:00.000这个值,<= endDate会把"当天零点"这一瞬间的数据也算进去,与业务语义"昨天全天"出现边界偏差。用左闭右开>= start AND < end,既避免了精度问题,也让 SQL Server 的索引范围定位更干净。这个习惯在写时间查询时一定要养成。

4. 同类坑盘点:EF Core 里常见的非 SARG 写法

4.1 函数包裹列:ToString、DateDiff、Year 全都中招

这次事故里最核心的问题是.Date,但它只是非 SARG 写法里的一种。EF Core 场景下,凡是把列包在某种函数内部的条件,都需要高度警惕。我根据自己的踩坑经历,整理了一个高频清单:

写法翻译后效果问题推荐替换
o.CreatedDate.Date >= startCONVERT(date, [o].[CreatedDate]) >= @p列被转换,索引不稳o.CreatedDate >= start && o.CreatedDate < end
o.CreatedDate.Year == 2026DATEPART(year, [o].[CreatedDate]) = 2026列被函数包裹o.CreatedDate >= start && o.CreatedDate < end
o.OrderNo.ToString() == orderNoCONVERT(varchar, [o].[OrderNo]) = @p字符串化列,索引失效直接用o.OrderNo == orderNo
o.Customer.Name.Contains(keyword)LIKE '%' + @p + '%'前置通配符,无法用普通索引换后缀匹配或引入文本索引方案
EF.Functions.DateDiffDay(o.CreatedDate, DateTime.Today) == 1DATEDIFF(day, [o].[CreatedDate], GETDATE()) = 1列在函数内部改写为时间范围判断

注意一点:Contains在 EF Core 里翻译成LIKE '%' + @keyword + '%',这个写法因为通配符在开头,普通 B-Tree 索引是没法定位的。如果列上建了全文索引是另一回事,但大多数业务系统不会为了一个模糊搜索专门上全文索引,所以这种条件在数据量大时也容易变成扫描。

4.2 隐式转换和集合匹配:另两类全表扫描触发器

除了函数包裹列,还会遇到两类容易全表扫描的场景。

第一类是隐式类型转换。比如一个varchar类型的订单号列,你写o.OrderNo == orderNo,但orderNo在 C# 里是long类型。EF Core 可能会生成把字符串列转成数字的 SQL,比如CONVERT(bigint, [o].[OrderNo]) = @p。列在转换一侧,照样废索引。这种坑通常在实体属性类型和前端传入参数类型不一致的时候出现,上&&条件前先确认两侧类型一致。

第二类是集合匹配。Where(o => orderIds.Contains(o.Id))如果orderIds是一个几千上万的集合,EF Core 会把这个集合展开成一条很长的IN条件。量小的时候问题不大,一旦集合超过几百上千,SQL 文本会急剧膨胀,执行计划编译成本也会显著上升,极端情况下还会触达 SQL Server 的 2100 个参数上限。更合理的方式是分块或者借助临时表。这个坑不在题目里,但我在排查其他 CPU 问题时见过不止一次。

4.3 客户端评估:比全表扫描更隐蔽的灾难

还有一类问题比显式的非 SARG 更隐蔽,就是客户端评估。

EF Core 3.x 之后,对于无法翻译的表达式,默认是会抛异常而不是静默处理。但如果代码里不小心用了AsEnumerable()或者ToList()把数据拉到内存,然后再写第二个 LINQ 条件,那后续过滤就变成了 LINQ to Objects 的内存过滤。举个例子:

var list = await db.Orders .Where(o => o.Status == status) .ToListAsync(); var filtered = list.Where(o => IsSpecialOrder(o)).ToList();

这段代码里IsSpecialOrder是一个普通 C# 方法,不可能翻译成 SQL,所以它被放到了内存里过滤。如果Where(o => o.Status == status)筛选后仍有几十万行,这些数据会全部从数据库传输到应用服务器,然后再在内存里逐行判断。

这种"隐藏扫描"对 CPU 的杀伤力和数据库全表扫描一样大,而且它烧的是应用服务器的 CPU。排查的时候如果只看数据库侧,根本找不到原因。我见过一次线上事故,数据库表现平稳,应用服务器 CPU 拉满,最后定位到就是这个AsEnumerable之后的条件。

所以排查 CPU 100% 的时候,眼睛不能只盯着数据库,要把两端的数据都看了再下结论。

5. 修复前后对比:逻辑读从 13.8 万到 625

5.1 只改一行代码,为什么不影响业务口径

修好这次事故,代码层面只动了一行.Where条件,就是把列上的.Date去掉,改成半开区间。如果你的第一反应是"这样改会不会漏数据",我理解,因为我自己刚改的时候也反复验证过口径。

原查询条件是"订单创建时间的日期部分在[startDate, endDate]之间"。比如startDate = 2026-01-01,endDate = 2026-01-02,它要的是 1 月 1 日零点到 1 月 2 日零点之间创建的全部订单。

o.CreatedDate >= startDate && o.CreatedDate < endDate

这个条件精确覆盖了"1 月 1 日零点(含)到 1 月 2 日零点(不含)"。对于任何CreatedDate落在区间内的数据,两个条件的结果一致;对于边界上的数据,新的写法在语义上比原写法更精确,因为它不会把00:00:00.000之后的任何一毫秒漏掉或算重。

在我这边实际验证下来,导出文件的行数与旧逻辑完全一致。如果你们业务对时间边界有特殊定义,只需要注意AddDays(1)还是直接传endDate的选择,别把半开区间写反了。

5.2 执行计划、IO、耗时三个维度对比

修复之后,我重新抓了一次执行计划,顺带记下了SET STATISTICS IO, TIME ON的数据。这里放一张修复前后的对比表,直观感受一下差距:

指标修复前修复后
查询条件CONVERT(date, CreatedDate) BETWEEN @p0 AND @p1CreatedDate >= @p0 AND CreatedDate < @p1
执行计划操作Clustered Index ScanIndex Seek(idx_Orders_CreatedDate)
扫描/读取行数2,860,0002,631
逻辑读138,440 页625 页
CPU 时间约 6,000 ms22 ms
单次总耗时约 18~25 秒约 38 ms

这个数据是同一个数据库、同一条数据量下测出来的。逻辑读从 13.8 万页掉到 625 页,差了 221 倍。CPU 时间从 6 秒掉到 22 毫秒,差了 270 倍。之前并发跑 4 个这样的报表任务,数据库 CPU 就直接 100%;修复后就算 10 个并发同时跑,数据库 CPU 也几乎看不到波动。

说实话,我修过的很多性能问题改善幅度没有这么大,这次之所以这么夸张,纯粹是因为罪魁祸首是全表扫描和索引可用之间这种"天壤之别"的差距。

5.3 上线那天的 CPU 曲线

修复发布之后,我让客户盯着数据库服务器的 CPU 曲线,重点看几个固定时间段。第二天下午,对账报表按时跑起来了。

从前一天同一时段接近 100% 的 CPU 占用,到当天稳定在 5% 以下,几乎没有再出现尖峰。财务那边反馈报表在十几秒内就能打开,应用页面也不卡了。这个问题对业务的影响是直接降为零的。

我还建议客户顺手做了一件事:给Orders表的Status列和CreatedDate列建了一个复合索引,因为对账查询偶尔也会用Status做过滤,避免以后数据量继续增长时,出现另一个维度的扫描问题。当然,这不是本次事故的必要步骤,但属于低成本高收益的预防性操作。

6. 把这套排查流程沉淀成团队的防坑机制

6.1 一个通用的 "CPU 100% 定位路径"

这次事故解决完,我把排查过程总结成一条可以复用的路径。以后再遇到任何"CPU 100%"的报警,直接按这个顺序走:

  1. 先看进程:确认烧 CPU 的是数据库进程还是应用进程,决定接下来的排查主方向;
  2. 实时抓 SQL:数据库侧用活动监视器或 DMV 查正在跑的请求,应用侧用日志或性能分析工具定位热点代码;
  3. 拿执行计划:找到高消耗 SQL 之后,第一件事是看 Scan / Seek,看 Predicate 里有没有列套函数的痕迹;
  4. 反推代码:根据 SQL 里的函数和参数特征,回原代码里找对应的 LINQ 表达式;
  5. 改写并对比:把非 SARG 条件改成 SARG 写法,记录修改前后的逻辑读、CPU 时间和耗时,确认数据口径一致再上线。

这套路径的核心方法论是:不要凭感觉猜代码,而是让执行计划说出问题出在哪一行。执行计划不会骗人,哪个操作符最贵、哪个谓词破坏了索引,它都会如实展示。

6.2 我常用的 DMV 查询和工具组合

生产环境不是每次都允许你随便连上去排查,提前准备几个常用查询很关键。除了上面用过的sys.dm_exec_query_stats,实时查看正在运行的请求用这个:

SELECT r.session_id, r.cpu_time, r.total_elapsed_time, r.status, SUBSTRING(t.text, 1, 2000) AS sql_text FROM sys.dm_exec_requests AS r CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) AS t WHERE r.session_id > 50 AND r.status = 'running' ORDER BY r.cpu_time DESC;

如果打算看执行计划 XML,可以再CROSS APPLY sys.dm_exec_query_plan(r.plan_handle)。不过要注意,这个视图可能需要更高权限。生产环境建议提前找 DBA 确认权限范围。

SSMS 自带的"活动监视器"适合快速看等待类型和阻塞关系;想看历史趋势,Windows 性能监视器和 SQL Server 自带的性能计数器配合使用。这些工具足够应对绝大多数"SQL 打满 CPU"的场景。

6.3 防新增慢查询:拦截器、Code Review 红线、性能冒烟

修完本次问题,我更建议团队从机制上防止类似事故再次发生。光靠个人经验是守不住的,尤其当团队里新同学增多,代码 review 很容易漏掉隐藏的 LINQ 翻译问题。

第一件事,加一个 EF Core 慢查询拦截器。EF Core 允许继承DbCommandInterceptor,在命令执行结束之后拿到耗时数据。超过预设阈值就记录 SQL 和参数,写进日志或者告警系统。

public sealed class SlowQueryInterceptor : DbCommandInterceptor { private static readonly TimeSpan Threshold = TimeSpan.FromSeconds(2); public override DbDataReader ReaderExecuted( DbCommand command, CommandExecutedEventData eventData, DbDataReader result) { if (eventData.Duration > Threshold) { var message = $"Slow query ({eventData.Duration.TotalMilliseconds:F0} ms): {command.CommandText}"; // 建议在这里把 command.Parameters 也格式化进日志 Console.WriteLine(message); } return base.ReaderExecuted(command, eventData, result); } }

注册方式在DbContextOptionsBuilder里调用AddInterceptors(new SlowQueryInterceptor())。它有异步版本可以重写,生产环境记得用异步避免不必要的线程阻塞。

第二件事,把"列上套函数"和"集合参数超量"写进 Code Review 红线清单。我在团队里给这几条打了最高优先级:

  • LINQ 查询里禁止对列使用.Date、.Year、.ToString()、EF.Functions.DateDiff*等函数包裹;
  • Contains集合元素数上限控制,建议超过 200 就改临时表方案;
  • 任何AsEnumerable()/ToList()之后的过滤条件必须写注释说明原因;
  • 新查询在 CR 阶段必须附带执行计划或SET STATISTICS IO, TIME ON的实测数据。

第三件事,建立一个性能冒烟集合。把系统中几条核心查询固定下来,每次发版之前用一个小型完整数据量跑一遍,超过阈值直接失败。这个做法不需要引入复杂工具,最笨的办法也能用。关键是让性能问题在合入主干之前就被拦住,而不是上了生产再等客户来报警。

说到底,真正让这次事故变得有价值的,不是我们修复了一行代码,而是这套"看到列套函数就想到全表扫描"的条件反射,现在已经成了整个团队的肌肉记忆。以后 review 代码的时候,看到DateTime.Date类写法,大家都会下意识问一句:这个条件能不能直接用区间表达?能问出这一句,就已经避开绝大多数同类雷区了。

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

ANSYS Workbench齿轮动力学仿真:从接触设置到齿根应力读取全攻略

同一个齿轮模型&#xff0c;你算出来的齿面接触应力是800MPa&#xff0c;隔壁工位算出是400MPa&#xff0c;两个人用的都是ANSYS Workbench&#xff0c;参数看着也都差不多——这种场景我在做齿轮动力学仿真那几年里见过太多次了。问题往往不出在求解器&#xff0c;而是出在模型…

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

集团ERP多角交易怎么做?SAP三种落地方案与配置要点

1. 先拆业务&#xff1a;多角交易里到底有几条流1.1 从一条真实的业务链说起客户找我的时候&#xff0c;描述的是很典型的集团业务场景&#xff1a;集团下有贸易公司、生产公司和销售公司&#xff0c;海外的原材料先进贸易公司&#xff0c;贸易公司卖给生产公司&#xff0c;生产…

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

IPC主控芯片选型指南:GK7205V300与HI3516EV300深度对比

IPC摄像头这个行业里&#xff0c;选主控芯片从来都是个绕不开的坎。我做安防硬件方案设计快八年了&#xff0c;经手的IPC项目少说也有几十个&#xff0c;从早期的3518E到后来的3516系列&#xff0c;再到近两年国产替代浪潮里冒出来的各种新方案&#xff0c;踩过的坑比吃过的盐还…

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

YOLOV5建筑工地安全检测:数据集格式整理与训练避坑指南

简介&#xff1a;面向目标检测与工地安全监控场景&#xff0c;这份YOLOV5目录格式的建筑工地安全隐患数据集&#xff0c;整合了10类常见目标&#xff08;含安全帽、口罩、车辆等&#xff09;&#xff0c;图片为640640 RGB&#xff0c;已做mosaic增强&#xff0c;可直接用于训练…

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

基于DEAP的遗传算法与遗传编程实现算法交易策略

简介&#xff1a;这套算法交易程序聚焦苹果股价预测&#xff0c;面向对量化交易、机器学习预测感兴趣的Python开发者&#xff0c;提供遗传编程与遗传算法两种可对照的实现思路。遗传编程模块通过进化基于树的种群来最小化预测价与真实价误差&#xff0c;引入纳斯达克、苹果、标…

作者头像 李华