SqlBulkCopy 比循环 Insert 快 5–20 倍,因其绕过查询解析与单行事务,直推数据流至服务器缓冲区并启用最小日志记录;关键参数为 DestinationTableName(三段式)、BatchSize(10000–50000)和 SqlBulkCopyOptions(至少 TableLock 与 KeepIdentity)。

SqlBulkCopy 为什么比循环 Insert 快
因为 SqlBulkCopy 绕过了 SQL Server 的查询解析、计划编译、单行事务开销,直接将内存数据流以 TDS 协议批量推送到服务器缓冲区,底层走的是“最小日志记录”路径(尤其在 SqlBulkCopyOptions.TableLock + 简单恢复模式下)。实测插入 10 万行,通常比 INSERT INTO ... VALUES 循环快 5–20 倍。
必须设置的三个关键参数
漏掉任意一个都可能让性能打对折甚至报错:
-
DestinationTableName:必须是完整三段式名(如"dbo.Users"),不能带方括号或引号 -
BatchSize:建议设为 10000–50000;太小增加往返次数,太大易触发内存溢出或事务日志暴涨 -
SqlBulkCopyOptions:至少启用SqlBulkCopyOptions.TableLock(减少锁竞争)和SqlBulkCopyOptions.KeepIdentity(若源数据含 Identity 列)
DataTable 还是 IDataReader?选哪个更稳
优先用 DataTable——它可控、可调试、兼容性好;但注意两处坑:
- 列顺序必须与目标表字段顺序严格一致(不是按列名匹配!),否则值会错位。可用
DataTable.Columns["Name"].SetOrdinal(0)调整 - 空值必须用
DBNull.Value,不能传null或default(T),否则抛InvalidCastException - 如果数据来自数据库查询,直接用
SqlDataReader更省内存,但需确保 reader 未关闭且字段类型与目标列兼容(比如datetime2列不接受DateTimeKind.Unspecified的 DateTime)
常见报错和绕过方式
SqlException: Cannot insert the value NULL into column 'Id' —— 不是没设 KeepIdentity,而是源 DataTable 中该列为 AllowDBNull=false 但填了 DBNull.Value。解决方法:
• 目标表 Id 是 identity?删掉 DataTable 中该列,或设 Column.AllowDBNull = true 并确保值全为 DBNull.Value
• 想显式插入 Id?确认表已关掉 identity 属性,或执行 SET IDENTITY_INSERT [table] ON(需额外权限)
另一个高频问题:InvalidOperationException: The given value of type String from the data source cannot be converted to type nvarchar of the specified target column. —— 源字符串超长(如 DataTable 列是 string,但目标列定义为 nvarchar(50))。应对:提前截断或改用 SqlMetaData 显式声明列宽。










