ToList() 在并发请求中引发的内存爆炸
高并发下频繁调用
ToList()会立即执行查询并生成新集合,如果源数据量大或调用频次高(比如每秒数百次 Web API 请求),会导致大量短生命周期对象堆积在 Gen 0,触发高频 GC,甚至引发
OutOfMemoryException。这不是 LINQ 本身慢,而是过早物化带来的内存压力。 只在真正需要遍历多次、或需修改集合时才调用
ToList()或
ToArray()对数据库查询(如 EF Core 的
IQueryable<t></t>),优先保持延迟执行,用
AsEnumerable()后再链式处理,避免重复执行 SQL 若必须缓存结果,考虑用
MemoryCache+ 懒加载,而不是每次请求都
ToList()
Where + FirstOrDefault 组合在 IQueryable 上的 N+1 风险
EF Core 中写
context.Orders.Where(o => o.UserId == userId).FirstOrDefault()看似合理,但若
userId来自未索引字段(如字符串 GUID 且无数据库索引),或该查询被嵌套在循环里(如遍历用户列表查各自最新订单),就会变成 N 次独立查询 —— 并发时直接压垮数据库连接池和 CPU。 确认数据库表上对应字段已建索引:
CREATE INDEX IX_Orders_UserId ON Orders(UserId)避免在循环内做 LINQ 查询;改用
IN批量查(
context.Orders.Where(o => userIds.Contains(o.UserId))),注意 EF Core 6+ 对
Contains的翻译更稳定 用
SQL Server Profiler或 EF Core 日志(
EnableSensitiveDataLogging)验证最终生成的 SQL 是否符合预期
Parallel LINQ(PLINQ)在 I/O 密集型场景反拖慢性能
AsParallel()适合 CPU 密集型计算(如图像处理、数值聚合),但在 Web 应用中处理 HTTP 请求、DB 查询、文件读取等 I/O 操作时启用 PLINQ,反而因线程争抢、上下文切换和同步开销导致吞吐下降,响应时间波动加剧。 除非明确是纯内存计算且单次耗时 > 50ms,否则不要对
IEnumerable<t></t>调用
AsParallel()异步 I/O 操作(如
ToListAsync()、
ReadAsStringAsync())天然支持并发,应优先用
Task.WhenAll()替代 PLINQ PLINQ 默认使用
ThreadPool线程,可能挤占 ASP.NET Core 的请求处理线程,造成请求排队
OrderBy + Skip + Take 分页在大数据集上的执行计划失效
写
query.OrderBy(x => x.CreatedAt).Skip(10000).Take(20)看似标准分页,但当跳过行数极大(如 > 50000)时,SQL Server/PostgreSQL 可能放弃使用索引,退化为全表扫描 + 排序,单次查询耗时从毫秒级飙升至秒级,并发叠加后数据库 CPU 持续 100%。
SELECT * FROM ( SELECT *, ROW_NUMBER() OVER (ORDER BY CreatedAt) AS RowNum FROM Orders ) AS T WHERE RowNum BETWEEN 10001 AND 10020避免深度分页;改用“键集分页”(Keyset Pagination):记录上一页最后一条的
CreatedAt和
Id,下一页查
WHERE CreatedAt > lastTime OR (CreatedAt = lastTime AND Id > lastId)确保
ORDER BY字段有复合索引,例如
CREATE INDEX IX_Orders_CreatedAt_Id ON Orders(CreatedAt, Id)EF Core 7+ 支持
EntityFrameworkCore.SqlServer的
UseRowNumberForPaging,但仅缓解不根治,仍需索引配合
实际压测中,最常被忽略的是分页方式与索引的严格匹配 —— 即使加了索引,只要
ORDER BY和
WHERE条件没对齐索引顺序,执行计划就可能失效。
