Oracle BLOB 理論支援 4GB 資料,但傳統「一次性讀入 byte[] 再寫入」的方式存在兩大致命缺陷:
new byte[file.length()] 嘗試一次性配置巨大陣列,1GB 以上檔案極易導致 OutOfMemoryErrorInteger.MAX_VALUE (2,147,483,647) 限制,3GB 以上檔案長度超出 int 範圍,Integer.valueOf() 拋出異常使用 oracle.sql.BLOB / oracle.jdbc.OracleLob 的 BeginBatch/Write/EndBatch 機制,配合 FileStream 分塊讀取,實現常數記憶體佔用的大檔案寫入。
public bool UploadLargeVideoToBlob(string filePath, string updateSql, string connStr)
{
const int CHUNK_COUNT = 10; // 分 10 塊寫入,單塊最大 ~400MB,避開 int 上限
using (var conn = new OracleConnection(connStr))
using (var cmd = new OracleCommand(updateSql, conn))
{
conn.Open();
using (var txn = conn.BeginTransaction())
{
cmd.Transaction = txn;
// 1. 先查詢鎖定目標行,取得可寫入的 OracleLob
using (var reader = cmd.ExecuteReader())
{
reader.Read();
var blob = reader.GetOracleLob(0); // 假設 BLOB 為第 1 欄位
reader.Close();
// 2. 開啟批次寫入模式
blob.BeginBatch(OracleLobOpenMode.ReadWrite);
try
{
using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
long fileLen = fs.Length;
int chunkSize = (int)(fileLen / CHUNK_COUNT); // 安全轉 int
int lastChunkSize = (int)(fileLen / CHUNK_COUNT + fileLen % CHUNK_COUNT);
byte[] buffer;
int bytesRead;
// 3. 寫入前 9 塊
for (int i = 0; i < CHUNK_COUNT - 1; i++)
{
buffer = new byte[chunkSize];
bytesRead = 0;
while (bytesRead < chunkSize)
{
int n = fs.Read(buffer, bytesRead, chunkSize - bytesRead);
if (n == 0) break;
bytesRead += n;
}
blob.Write(buffer, 0, chunkSize);
buffer = null; // 釋放記憶體
}
// 4. 寫入最後一塊(含餘數)
buffer = new byte[lastChunkSize];
bytesRead = 0;
while (bytesRead < lastChunkSize)
{
int n = fs.Read(buffer, bytesRead, lastChunkSize - bytesRead);
if (n == 0) break;
bytesRead += n;
}
blob.Write(buffer, 0, lastChunkSize);
}
blob.EndBatch();
txn.Commit();
return true;
}
catch
{
txn.Rollback();
throw;
}
}
}
}
}
| 技術點 | 說明 | |
|---|---|---|
SELECT ... FOR UPDATE | 查詢時必須加 FOR UPDATE 鎖定行,否則 GetOracleLob 返回唯讀 Lob | |
BeginBatch/EndBatch | 批次模式減少網路往返,將多次 Write 合併為單次傳輸,大幅提升效能 | |
| 分塊大小策略 | 檔案長度 / 10 確保單塊 < 400MB,int 安全;最後一塊包含餘數 | |
FileStream.Read 迴圈 | 單次 Read 不保證讀滿緩衝區,必須迴圈讀取至目標長度 | |
| 緩衝區重用 | 每塊寫入後置 null 協助 GC 回收,維持低記憶體佔用 |
| 工具 | 大檔案 BLOB 支援 | 替代方案 |
|---|---|---|
| PL/SQL Developer | ❌ 受限於 32 位進程記憶體,~1GB 失敗 | 自製 .NET/Java 工具或 SQL*Loader |
| SQL*Loader | ✅ 支援 LOBFILE 分塊載入 | 控制檔指定 LOBFILE 參數 |
| Data Pump (expdp/impdp) | ✅ 原生支援大 LOB | 首選遷移方案 |
| RMAN | ✅ 區塊級備份 | 資料庫級備份恢復 |
sqlnet.ora 設定 DEFAULT_SDU_SIZE=2097152 (2MB)DB_BLOCK_SIZE 設 8K/16K,LOB_CHUNK 設 8K/16K/32K 對齊SECUREFILE LOB 儲存,支援壓縮、去重、加密Oracle BLOB 儲存 4GB 以內大檔案的關鍵在於串流分塊處理。客戶端使用 OracleLob.BeginBatch/Write/EndBatch 搭配 FileStream 迴圈讀寫,資料庫端使用 DBMS_LOB.LOADBLOBFROMFILE,皆能以常數記憶體完成超大檔案操作。避開「一次性載入 byte[]」反模式,是解決 OutOfMemory 與整數溢位的根本之道。