首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 數據庫知識 >  >> Oracle 資料庫 >> Oracle 資料庫基礎

Oracle BLOB 大物件儲存 4GB 影片實戰:分塊串流寫入與讀取完整方案

     編輯:Oracle 資料庫基礎

問題背景:大檔案寫入 BLOB 的記憶體瓶頸

Oracle BLOB 理論支援 4GB 資料,但傳統「一次性讀入 byte[] 再寫入」的方式存在兩大致命缺陷:

  1. 記憶體溢位:new byte[file.length()] 嘗試一次性配置巨大陣列,1GB 以上檔案極易導致 OutOfMemoryError
  2. 整數溢位:Integer.MAX_VALUE (2,147,483,647) 限制,3GB 以上檔案長度超出 int 範圍,Integer.valueOf() 拋出異常

解決方案核心:OracleLob 分批串流處理

使用 oracle.sql.BLOB / oracle.jdbc.OracleLob 的 BeginBatch/Write/EndBatch 機制,配合 FileStream 分塊讀取,實現常數記憶體佔用的大檔案寫入。

完整寫入實作 (C# / Oracle.ManagedDataAccess)

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;
                }
            }
        }
    }
}

關鍵技術點解析

大檔案讀取實作:串流式輸出

public bool DownloadBlobToFile(string selectSql, string outputPath, string connStr)
{
    const int READ_BUFFER_SIZE = 8192;  // 8KB 讀取緩衝區
    
    using (var conn = new OracleConnection(connStr))
    using (var cmd = new OracleCommand(selectSql, conn))
    {
        conn.Open();
        
        // 使用 SequentialAccess 避免一次性載入整個 BLOB 到記憶體
        using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess))
        using (var fs = new FileStream(outputPath, FileMode.Create, FileAccess.Write))
        {
            if (!reader.Read()) return false;
            
            long offset = 0;
            byte[] buffer = new byte[READ_BUFFER_SIZE];
            long bytesRead;
            
            // 迴圈讀取直到結束
            while ((bytesRead = reader.GetBytes(0, offset, buffer, 0, READ_BUFFER_SIZE)) > 0)
            {
                fs.Write(buffer, 0, (int)bytesRead);
                offset += bytesRead;
            }
            
            return true;
        }
    }
}

PL/SQL 端的等效處理

若在資料庫端處理(如從外部表載入),可使用 DBMS_LOB 套件:

DECLARE
  l_blob  BLOB;
  l_bfile BFILE := BFILENAME('VIDEO_DIR', 'large_video.mp4');
  l_dest_offset INTEGER := 1;
  l_src_offset  INTEGER := 1;
  l_chunk_size  INTEGER := 32768;  -- 32KB 區塊
  l_warning     INTEGER;
BEGIN
  -- 鎖定目標行
  SELECT video_data INTO l_blob FROM video_table WHERE id = 1 FOR UPDATE;
  
  DBMS_LOB.OPEN(l_bfile, DBMS_LOB.LOB_READONLY);
  DBMS_LOB.OPEN(l_blob, DBMS_LOB.LOB_READWRITE);
  
  -- 分塊載入
  DBMS_LOB.LOADBLOBFROMFILE(
    dest_lob    => l_blob,
    src_bfile   => l_bfile,
    amount      => DBMS_LOB.LOBMAXSIZE,  -- 全部載入
    dest_offset => l_dest_offset,
    src_offset  => l_src_offset
  );
  
  DBMS_LOB.CLOSE(l_blob);
  DBMS_LOB.CLOSE(l_bfile);
  COMMIT;
END;
/

工具限制與替代方案

技術點說明
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✅ 區塊級備份資料庫級備份恢復

效能調優建議

  • 網路傳輸:啟用 SDU (Session Data Unit) 優化,sqlnet.ora 設定 DEFAULT_SDU_SIZE=2097152 (2MB)
  • 資料庫參數:DB_BLOCK_SIZE 設 8K/16K,LOB_CHUNK 設 8K/16K/32K 對齊
  • SecureFiles:Oracle 11g+ 推薦使用 SECUREFILE LOB 儲存,支援壓縮、去重、加密
  • 並行度:大檔案上傳可拆分多線程上傳不同區段(需應用層協調)

總結

Oracle BLOB 儲存 4GB 以內大檔案的關鍵在於串流分塊處理。客戶端使用 OracleLob.BeginBatch/Write/EndBatch 搭配 FileStream 迴圈讀寫,資料庫端使用 DBMS_LOB.LOADBLOBFROMFILE,皆能以常數記憶體完成超大檔案操作。避開「一次性載入 byte[]」反模式,是解決 OutOfMemory 與整數溢位的根本之道。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved