Oracle中无SqlBulkCopy,可用替代方案为ODP.NET的OracleBulkCopy(需19.10+托管驱动)或外部表+SQL*Loader;前者需显式列映射、禁用索引/启用NOLOGGING以保障性能。
Oracle 里没有 SqlBulkCopy,别硬套 SQL Server 那套逻辑
oracle 官方不提供类似 .net 中 sqlbulkcopy 那样直接从内存 datatable 批量写入的原生类。强行用 oledbcommand 循环插入或拼 insert all,在百万级以上数据时基本等于卡死——不是语法错,是设计定位不同。
真正可用的替代方案只有两个:Oracle 官方的 OracleBulkCopy(需 Oracle Data Provider for .NET,即 ODP.NET),以及基于外部表(External Table)+ SQL*Loader 的间接路径。前者更贴近 C# 开发者习惯,后者吞吐更高但要绕开 .NET 直接调系统命令。
-
OracleBulkCopy是 ODP.NET 12.1+ 引入的类,命名像SqlBulkCopy,但行为细节差异大:不支持自动列映射、不识别DataTable的ColumnMapping,必须显式指定目标列名 - 它底层走的是 Oracle 的 Direct Path API,绕过 SQL 引擎,所以不触发触发器、不校验 CHECK 约束(除非显式开启)、不支持 ON COMMIT REFRESH 的物化视图刷新
- 常见报错
ORA-26026: unique index <code>XXXinitially enabled with exceptions,往往是目标表有唯一约束但数据含重复值,而OracleBulkCopy默认不抛异常而是跳过——得手动设BatchErrorMode = OracleBulkCopyErrorMode.ThrowException
OracleBulkCopy 必须配 ODP.NET,且版本不能乱选
用 NuGet 装包时,别只搜 “Oracle.ManagedDataAccess” 就完事。OracleBulkCopy 类在 Oracle.ManagedDataAccess.Client 命名空间下,但仅存在于 19.10+(含)的托管驱动中;旧版如 12.2 或 18.3 虽有该类,但存在内存泄漏和列类型推断错误(比如把 NUMBER(10,0) 当成 decimal 却写入 int 报错)。
- 推荐用
Oracle.ManagedDataAccess≥ 21.1,它是目前最稳的长期支持版(LTS) - 连接字符串里必须加
Load Balance=true或明确指定单实例,否则在 RAC 环境下OracleBulkCopy可能随机连错节点导致 Direct Path 失败 - 不要混用
Oracle.DataAccess(ODAC)和Oracle.ManagedDataAccess,两者冲突,编译不报错但运行时TypeLoadException频发
写法上比 SqlBulkCopy 多三步:准备 DataTable、映射列、显式写列名
OracleBulkCopy 不会自动按列名或顺序匹配源/目标表,哪怕名字一样也得手动告诉它“第 0 列对应 EMP_ID”,否则默认按位置映射,极易错位。
var bulkCopy = new OracleBulkCopy(connectionString); bulkCopy.DestinationTableName = "EMPLOYEES"; bulkCopy.BatchSize = 50000; bulkCopy.WriteToServer(dataTable); // ❌ 这样会按位置硬塞,风险极高
正确写法:
bulkCopy.ColumnMappings.Add("ID", "EMP_ID"); // 源列名 → 目标列名
bulkCopy.ColumnMappings.Add("NAME", "FULL_NAME");
bulkCopy.ColumnMappings.Add("SALARY", "SALARY_AMT");
bulkCopy.WriteToServer(dataTable); // ✅ 显式映射后才安全- 源
DataTable的列名必须和ColumnMappings.Add()第一个参数严格一致(区分大小写) - 目标列名是数据库实际字段名,不是别名,也不接受表达式
- 如果源数据含
NULL,目标列必须允许 NULL,否则报ORA-01400: cannot insert NULL into ("SCHEMA"."TABLE"."COL")
大数据量下性能卡点不在 C#,而在 Oracle 端配置和表结构
实测 100 万行写入,OracleBulkCopy 本身耗时约 3–5 秒,但整个操作卡在 30 秒以上,八成概率是 Oracle 端没调优。
- 目标表必须禁用索引(
ALTER INDEX xxx UNUSABLE)或建为NOLOGGING,否则 Redo 日志写满磁盘就停住 - 确保表空间有足够空闲块,
OracleBulkCopy的 Direct Path 会一次性申请大量连续 extent,碎片多时分配失败并降级为常规 INSERT - 如果目标列有
DEFAULT值(如CREATED_DATE DATE DEFAULT SYSDATE),OracleBulkCopy不会自动填充,得在DataTable里提前算好填进去,否则报ORA-01400
最后提醒一句:OracleBulkCopy 的 WriteToServer 是同步阻塞调用,别把它丢进 Task.Run 就以为异步了——IO 瓶颈在 Oracle 服务端,线程调度改不了本质。

















