在 .NET 應用中向 Oracle 大量寫入資料,傳統逐筆 ExecuteNonQuery 效能極差。ODP.NET (Oracle Data Provider for .NET) 提供 **Array Binding (陣列繫結)** 功能,可一次送出成千上萬筆參數化資料,達到秒級匯入百萬列。本文實測對比多種寫法,給出最佳實踐方案。
Oracle.ManagedDataAccess)。CREATE TABLE TEST_BULK (ID NUMBER, NAME VARCHAR2(50), VAL NUMBER)。| 方法 | 核心 API | 預估耗時 (100 萬筆) | 特點 |
|---|---|---|---|
| 逐筆 ExecuteNonQuery | cmd.ExecuteNonQuery() 迴圈 | ~ 30 分鐘+ | 網路來回、解析 SQL、事務提交極慢 |
| PL/SQL 批次 (FORALL) | 匿名區塊 FORALL i IN 1..N | ~ 15-20 秒 | 需撰寫 PL/SQL,維護性較差 |
| ODP.NET Array Binding | cmd.ArrayBindCount + 參數陣列 | ~ 3-5 秒 | 純 C#、參數化、效能最佳、支援事務 |
| Bulk Copy (SQL Server) | SqlBulkCopy | ~ 2-4 秒 | 僅限 SQL Server,列舉對比 |
using Oracle.ManagedDataAccess.Client;
using System;
using System.Data;
public static void BulkInsertArrayBinding(int rowCount = 1_000_000)
{
const string connStr = "User Id=scott;Password=tiger;Data Source=ORCL;";
const string sql = "INSERT INTO TEST_BULK (ID, NAME, VAL) VALUES (:id, :name, :val)";
using (var conn = new OracleConnection(connStr))
using (var cmd = new OracleCommand(sql, conn))
{
conn.Open();
using (var tran = conn.BeginTransaction())
{
cmd.Transaction = tran;
cmd.BindByName = true; // 依參數名稱繫結
cmd.ArrayBindCount = rowCount; // 關鍵:指定陣列長度
// 準備參數陣列 (陣列長度 = ArrayBindCount)
var ids = new int[rowCount];
var names = new string[rowCount];
var vals = new decimal[rowCount];
for (int i = 0; i < rowCount; i++)
{
ids[i] = i + 1;
names[i] = $"Item_{i+1}";
vals[i] = (decimal)(i * 1.5);
}
// 參數型別需與資料表對應
cmd.Parameters.Add(":id", OracleDbType.Int32, ids, ParameterDirection.Input);
cmd.Parameters.Add(":name", OracleDbType.Varchar2, names, ParameterDirection.Input);
cmd.Parameters.Add(":val", OracleDbType.Decimal, vals, ParameterDirection.Input);
// 單次往返完成百萬筆插入
int affected = cmd.ExecuteNonQuery();
tran.Commit();
Console.WriteLine($"Inserted {affected} rows in {sw.ElapsedMilliseconds} ms");
}
}
}
OracleDbType 需精確對應 (Int32, Varchar2, Decimal, Date 等)。OracleTransaction 中,失敗全滾回;超大資料量分批提交避免 Undo 表空間爆滿。OracleBulkCopy (ODP.NET 12.2+) 或外部表/ SQL*Loader。const int BatchSize = 50_000;
int total = 1_000_000;
for (int offset = 0; offset < total; offset += BatchSize)
{
int currentBatch = Math.Min(BatchSize, total - offset);
// 填充 ids[names/vals] 陣列...
cmd.ArrayBindCount = currentBatch;
cmd.ExecuteNonQuery();
tran.Commit(); // 或每 N 批提交一次
}
| 錯誤 | 原因 | 解決 |
|---|---|---|
| ORA-01008: not all variables bound | 參數陣列長度 != ArrayBindCount | 檢查陣列長度一致,且所有參數皆為陣列 |
| ORA-01461: can bind a LONG value only for insert into a LONG column | Varchar2 長度超過欄位定義 | 截斷字串或擴大欄位 |
| OutOfMemoryException | 單批陣列過大 | 降低 BatchSize、使用串流式寫入 |
ODP.NET **Array Binding** 是 .NET 向 Oracle 大量寫入的**標準高效方案**,純 managed 程式碼、參數化防注入、支援事務、效能逼近原生工具。掌握 ArrayBindCount、參數陣列準備、分批提交三要素,即可在企業級應用中穩定實現百萬級秒級匯入。