EF Core中Include、投影与跟踪策略的边界如何界定,是查询性能黑洞的根源吗?
摘要:很多团队把 EF Core 的性能问题归因于“ORM 天生慢”,但线上真实情况通常是: 查询写法对 SQL 形态不敏感 默认跟踪被滥用 图省事一次 Include 到底 结果是接口能跑,但高峰时段 P95 持续抬高,数据库 CPU 和网络带
很多团队把 EF Core 的性能问题归因于“ORM 天生慢”,但线上真实情况通常是:
查询写法对 SQL 形态不敏感
默认跟踪被滥用
图省事一次 Include 到底
结果是接口能跑,但高峰时段 P95 持续抬高,数据库 CPU 和网络带宽一起被拖上去。
这篇文章聚焦一个目标:把 EF Core 查询从“能查到数据”升级到“可预测、可解释、可优化”。
1. 问题背景:列表页为什么越改越慢
一个典型场景:订单列表页需要展示订单、客户、明细、商品。
最直觉的写法是连续 Include:
var orders = await db.Orders
.Include(o => o.Customer)
.Include(o => o.Items)
.ThenInclude(i => i.Product)
.Where(o => o.CreatedAt >= from && o.CreatedAt < to)
.OrderByDescending(o => o.CreatedAt)
.Take(50)
.ToListAsync();
这段代码看起来“很完整”,但常见后果是:
结果集行数被笛卡尔放大
应用端做了大量重复对象反序列化和跟踪
实际只显示 6 个字段,却把整个对象图都拉回来了
2. 原理解析:EF Core 查询成本主要花在哪里
2.1 翻译成本
LINQ 先被转换为表达式树,再翻译成 SQL。复杂投影、方法调用、局部函数容易导致翻译退化或直接失败。
2.2 执行与网络成本
Include 深度越深,JOIN 越复杂,网络回包越大。很多慢查询不是数据库“算得慢”,而是“传得多”。
2.3 跟踪成本
默认跟踪模式会创建实体快照,便于更新,但纯读场景这部分是额外开销。
2.4 物化成本
即使 SQL 很快,应用层物化大量实体和导航属性也会抬高 CPU 与内存分配。
