高并发下频繁调用ToList()引发内存爆炸,应延迟执行;Where+FirstOrDefault易致N+1查询,需建索引并批量查;PLINQ不适用于I/O场景;深度分页需键集分页与复合索引配合。

c# LINQ 在高并发场景下的性能陷阱  第1张

ToList() 在并发请求中引发的内存爆炸

高并发下频繁调用 ToList() 会立即执行查询并生成新集合,如果源数据量大或调用频次高(比如每秒数百次 Web API 请求),会导致大量短生命周期对象堆积在 Gen 0,触发高频 GC,甚至引发 OutOfMemoryException。这不是 LINQ 本身慢,而是过早物化带来的内存压力。

  • 只在真正需要遍历多次、或需修改集合时才调用 ToList()ToArray()
  • 对数据库查询(如 EF Core 的 IQueryable),优先保持延迟执行,用 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 调用 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):记录上一页最后一条的 CreatedAtId,下一页查 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.SqlServerUseRowNumberForPaging,但仅缓解不根治,仍需索引配合

实际压测中,最常被忽略的是分页方式与索引的严格匹配 —— 即使加了索引,只要 ORDER BYWHERE 条件没对齐索引顺序,执行计划就可能失效。