Parallel.ForEach 在 I/O 密集型导入中变慢,因线程争抢连接池或锁;应限流(如 MaxDegreeOfParallelism=4–8)、改用 SqlBulkCopy(设 BatchSize、EnableStreaming、TABLOCK)或分批 SaveChanges(禁用自动追踪、每 500–2000 条提交),并用 SemaphoreSlim 控制并发防连接池耗尽。

用 Parallel.ForEach 处理大批量数据导入时为什么反而变慢?
不是所有“并发”都加速,Parallel.ForEach 在 I/O 密集型场景(如数据库插入、文件读写)中常因线程争抢连接或锁而拖慢整体吞吐。它默认按 CPU 核心数分配线程,但数据库连接池、磁盘 IO 或网络带宽才是真实瓶颈。
- 避免直接包裹
DbContext.SaveChanges()或SqlCommand.ExecuteNonQuery()—— 每次调用都可能触发独立事务和连接获取 - 改用批量操作:如 EF Core 的
ExecuteSqlRaw+ 参数化 SQL 批量插入,或 Dapper 的connection.Execute(sql, list) - 手动控制并行度:
Parallel.ForEach(list, new ParallelOptions { MaxDegreeOfParallelism = 4 }, item => { ... }),值设为数据库连接池大小(如 SQL Server 默认 100,实际建议 4–8)
如何让 SqlBulkCopy 真正跑满带宽?
SqlBulkCopy 是 .NET 原生最快的数据导入方式,但默认配置下常只用单线程、小缓冲、无索引优化,导致吞吐远低于理论值。
- 必须设置
BatchSize(如 10000),避免单次提交过大内存溢出或过小频繁往返 - 开启
EnableStreaming = true,配合DataTable或IDataReader流式供数,减少内存峰值 - 导入前执行
ALTER TABLE ... DISABLE TRIGGER和DROP INDEX(完事后重建),尤其对有唯一约束或外键的表影响巨大 - 确保目标表有
WITH (TABLOCK)提示(通过SqlBulkCopy.SqlRowsCopied事件无法控制,需在 SQL 层显式加)
EF Core 批量插入时 SaveChanges 卡住的三个常见原因
EF Core 的变更跟踪机制在海量数据下会吃光内存、拖慢性能,SaveChanges 不是“越快越好”,而是“越少调用越好”。
NetShop软件特点介绍: 1、使用ASP.Net(c#)2.0、多层结构开发 2、前台设计不采用任何.NET内置控件读取数据,完全标签化模板处理,加快读取速度3、安全的数据添加删除读取操作,利用存储过程模式彻底防制SQL注入式攻击4、前台架构DIV+CSS兼容IE6,IE7,FF等,有利于搜索引挚收录5、后台内置强大的功能,整合多家网店系统的功能,加以优化。6、支持三种类型的数据库:Acces
- 禁用自动检测变更:
context.ChangeTracker.AutoDetectChangesEnabled = false,手动context.Entry(entity).State = EntityState.Added - 分批提交:每 500–2000 条调用一次
SaveChanges(),而非全量后一次提交(否则事务日志暴涨、锁表时间过长) - 不用
AddRange直接传大集合——它仍会逐个标记状态;改用context.AddRange(entities.Take(batchSize))+ 循环
异步 + 并发组合使用时容易忽略的连接池耗尽问题
用 await context.SaveChangesAsync() 配合 Task.WhenAll 看似高效,实则极易触发 Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool 错误。
-
Task.WhenAll同时发起 100 个 SaveChanges —— 就等于向连接池申请 100 个连接,远超默认上限(通常 100,但受服务器资源限制) - 正确做法:用
SemaphoreSlim限流,例如var semaphore = new SemaphoreSlim(8);
await semaphore.WaitAsync();
try { await context.SaveChangesAsync(); }
finally { semaphore.Release(); } - 连接字符串中显式加大
Max Pool Size=200不解决问题,只是掩盖争抢——应优先降低并发请求数,再调高池大小作为补充
SqlBulkCopy、分批 SaveChanges 还是纯原生 ADO.NET,比盲目加并发更有效。









