SqlBulkCopy是C#中万级以上数据批量插入的最优解,性能达普通INSERT循环的10–50倍;仅支持SQL Server,需目标表预先存在、字段顺序与类型严格匹配,推荐BatchSize 1000–10000并启用EnableStreaming,主键冲突宜用临时表+MERGE解决,事务需显式传入SqlTransaction。

用 SqlBulkCopy 实现高速批量插入
直接上结论:C# 中真正适合万级以上数据批量插入的,只有 SqlBulkCopy。它绕过 SQL 解析层,走的是 SQL Server 的原生批量加载通道,性能通常是 INSERT INTO ... VALUES 循环的 10–50 倍。
常见错误是拿 ExecuteNonQuery 配合参数化 INSERT 循环来“模拟批量”,结果单次插入 1 万条可能耗时 30 秒以上;而 SqlBulkCopy 同样数据通常在 300ms 内完成。
实操要点:
-
SqlBulkCopy只支持 SQL Server(含 Azure SQL),不适用于 MySQL、PostgreSQL - 目标表必须已存在,它不会自动建表或做字段映射校验(映射错会静默跳过或报错)
- 源数据建议用
DataTable或IDataReader,避免用IEnumerable<T>直接传——后者会触发完整枚举+反射,拖慢性能 - 关键配置项:
BatchSize(推荐 1000–10000)、DestinationTableName(必须全名,如[dbo].[Orders])、EnableStreaming = true(大数据量时减少内存占用)
DataTable 构造时字段顺序与类型必须严格匹配目标表
这是最常踩的坑:字段名对了但插不进数据,或者数值被截断、日期变 1900-01-01——根本原因往往是 DataTable 列顺序/类型和数据库表不一致。
例如目标表定义为:
CREATE TABLE Orders (
Id INT IDENTITY(1,1) PRIMARY KEY,
OrderNo NVARCHAR(20) NOT NULL,
Amount DECIMAL(18,2),
CreatedAt DATETIME2
)对应 DataTable 必须按此顺序添加列,且类型要兼容:
var dt = new DataTable();
dt.Columns.Add("OrderNo", typeof(string)); // 注意:不是 "Id" 开头
dt.Columns.Add("Amount", typeof(decimal));
dt.Columns.Add("CreatedAt", typeof(DateTime)); // 不能用 DateTime? 或 string如果数据库字段允许 NULL,DataTable 对应列的 AllowDBNull 属性也得设为 true,否则 BulkCopy 会抛 InvalidOperationException。
处理主键冲突:用临时表 + MERGE 替代直接插入
SqlBulkCopy 本身不支持 ON CONFLICT 或 IGNORE,遇到主键/唯一键重复会直接报错中断。真实业务中往往需要“存在则更新,不存在则插入”。
推荐做法是分两步:
- 先用
SqlBulkCopy把数据导入一个结构相同的临时表(如#staging_orders) - 再用 T-SQL 执行
MERGE,把临时表数据合并到正式表
示例合并语句:
MERGE Orders AS tgt
USING #staging_orders AS src
ON tgt.OrderNo = src.OrderNo
WHEN MATCHED THEN
UPDATE SET Amount = src.Amount, CreatedAt = src.CreatedAt
WHEN NOT MATCHED THEN
INSERT (OrderNo, Amount, CreatedAt) VALUES (src.OrderNo, src.Amount, src.CreatedAt);注意:临时表名前缀 # 表示会话级临时表,不用手动清理;若需跨连接复用,改用 ## 全局临时表,但要注意并发安全。
异步与事务控制:BulkCopy 本身不支持 async,但可包裹在 TransactionScope 中
SqlBulkCopy.WriteToServer 是同步阻塞调用,没有 WriteToServerAsync 方法(.NET 6+ 仍无)。想实现“不卡主线程”,只能靠线程池或 Task.Run 包裹,但要注意连接对象不能跨线程共享。
事务控制更关键:默认情况下 SqlBulkCopy 每个批次是独立事务,失败只回滚当前批次。如需整批原子性,必须显式传入已开启的 SqlTransaction:
using var conn = new SqlConnection(connStr);
conn.Open();
using var tx = conn.BeginTransaction();
using var bulk = new SqlBulkCopy(conn) {
DestinationTableName = "[dbo].[Orders]",
SqlTransaction = tx // ⚠️ 必须设置此项
};
bulk.WriteToServer(dt);
tx.Commit(); // 成功后提交,否则调用 tx.Rollback()容易忽略的一点:如果目标表有触发器,默认会被禁用;如需启用,得显式设置 FireTriggers = true,但会显著降低速度。


















