很長時間沒親自寫寫東西了,只是收集轉載了一些好資料,其實,真正靜下心總結一下,可以寫的知識點很多。與困難做斗爭,挑戰技術難關,總會有些感受心得的。
今天想和網友分享一下“Oracle中BLOB大字段如何讀寫視頻數據”,這個話題起因是我在使用Oracle備份數據時,誤刪了數據庫實例的控制文件,導致項目數據需要重新入庫。也就是我在彌補這個錯誤時,發現之前的數據入庫功能,都沒有把200M以上的視頻數據導入Oracle的BLOB字段裡,也就是之前的寫入BLOB字段數據的方法失效了。這是個驚人的發現,我發現因為這個程序BUG我遺漏掉近300G的視頻數據,某些單個視頻文件數據量達到3.6G。
我研究基於ORACLE Text的全文檢索功能,開始接觸ORACLE的BLOB字段,3年多了,自認為已經熟知BLOB字段的操作。但這次的難題迫使我更深入的認識Oracle的BLOB字段。
BLOB字段能以二進制形式存放4G數據,200M的視頻數據當然應該沒問題,可以出錯了?!原來的方法會報“Out of memory”錯誤,PLSQL Developer工具導入大視頻數據,同樣會報“Out of memory”錯誤。3.6G的視頻數據又該如何導入?原來寫入大字段的方法,導入一般的圖片和文檔一點問題沒有。
- /// <summary>
- /// 寫大字段內容
- /// (新方法,2010.2.4)
- /// </summary>
- /// <param name="pDbConn"></param>
- /// <param name="strTable"></param>
- /// <param name="strBlobFIEld"></param>
- /// <param name="strFile"></param>
- /// <param name="strWhereClause"></param>
- /// <returns></returns>
- public bool WriteBlobFIEld(System.Data.OleDb.OleDbConnection pDbConn,
- string strTable,
- string strBlobFIEld,
- string strFile,
- string strWhereClause)
- {
- if (strWhereClause == "")
- {
- return false;
- }
- try
- {
- string strSQL = "UPDATE " + strTable + " SET " + strBlobFIEld + " =:blob WHERE " + strWhereClause;
- OleDbCommand cmd = new OleDbCommand(strSQL, pDbConn);
- //無需說明類型
- //cmd.Parameters.Add(new OleDbParameter("blob", SqlDbType.VarBinary));
- // cmd.Parameters.AddWithValue("blob", SqlDbType.Binary);
- FileInfo fileInfo = new FileInfo(strFile);
- FileStream fsBlob = fileInfo.OpenRead();// new FileStream(strFile, FileMode.Open,FileAccess.Read);
- byte[] dataBlob = new byte[fsBlob.Length];//問題1所在
- fsBlob.Read(dataBlob, 0, System.Convert.ToInt32(fsBlob.Length));//問題2所在
- fsBlob.Close();
- //采用新的方法,AddWithValue();
- cmd.Parameters.AddWithValue("blob", dataBlob);
- //cmd.Parameters["blob"].Value = dataBlob;
- int result = cmd.ExecuteNonQuery();
- if (result < 1)
- {
- return false;
- }
- }
- catch (Exception ex)
- {
- // MessageBox.Show(ex.Message, "寫數據", MessageBoxButtons.OK);
- return false;
- }
- return true;
- }
- /// <summary>
- /// 將字符串寫成大字段內容
- /// (2010.2.4 修改)
- /// </summary>
- /// <param name="pDbConn"></param>
- /// <param name="strTable"></param>
- /// <param name="strBlobFIEld"></param>
- /// <param name="strBlobContent"></param>
- /// <param name="strWhereClause"></param>
- /// <returns></returns>
- public bool WriteBlobFIEld2(System.Data.OleDb.OleDbConnection pDbConn,
- string strTable,
- string strBlobFIEld,
- string strBlobContent,
- string strWhereClause)
- {
- if (strWhereClause == "")
- {
- return false;
- }
- try
- {
- string strSQL = "UPDATE " + strTable + " SET " + strBlobFIEld + " =:blob " +
- "WHERE " + strWhereClause;
- OleDbCommand cmd = new OleDbCommand(strSQL, pDbConn);
- cmd.Parameters.Add(strBlobFIEld, SqlDbType.Binary);
- // byte[] dataBlob = new byte[strBlobContent.Length];
- byte[] dataBlob = System.Text.Encoding.Default.GetBytes(strBlobContent);
- cmd.Parameters["blob"].Value = dataBlob;
- int result = cmd.ExecuteNonQuery();
- if (result < 1)
- {
- return false;
- }
- }
- catch (Exception ex)
- {
- MessageBox.Show(ex.Message, "寫數據", MessageBoxButtons.OK);
- return false;
- }
- return true;
- }
問題1:無法一次性開辟足夠大空間(如1G),寫入大視頻時,會導致報內存不足。
問題2:System.Convert.ToInt32()會使3G的視頻時,會報類型轉換失敗,數值值過大。
上面兩個問題在網絡中所有的方法中都普遍存在的,都會導致無法導入700M以上的視頻數據。
OLEDB方法對ORCLE 8以後的大字段操作不在支持,我在解決問題的過程中轉向了OracleClIEnt命名空間下的方法來操作BLOB大字段,主要參考微軟官方http://msdn.microsoft.com/zh-cn/library/cydxhzhz(v=VS.90).ASPx和博客園中的http://www.cnblogs.com/zhengmaoch/archive/2005/08/10/212014.Html。這兩份資料對我解決500M以下數據量的視頻很有幫助,但是1G甚至是3G以上視頻數據是無法解決的。上面兩處使用了事務處理在導500M以上數據時,會報“ORA-22297: warning: Open LOBs exist at transaction commit time ”錯誤,主要因為提交事務時數據文件沒有讀完。
經過試驗和參考http://msdn.microsoft.com/en-us/library/system.io.filestream.read.ASPx方法,終於完全解決上面兩個問題,實現大視頻量數據導入BLOB字段。
- /// <summary>
- /// 2010.10.22
- /// 讀取視頻數據進入Oracle大字段中
- /// </summary>
- /// <param name="fileToUpload"></param>
- /// <param name="uploadSQL"></param>
- /// <returns></returns>
- public bool OracleUpload(string fileToUpload, string uploadSQL)
- {
- /*
- * Get Connected
- */
- string connection = strConn;
- OracleConnection conn;
- conn = new OracleConnection(connection);
- conn.Open();
- OracleCommand cmd = new OracleCommand(uploadSQL, conn);
- OracleTransaction transaction = conn.BeginTransaction();
- cmd.Transaction = transaction;
- OracleDataReader reader = cmd.ExecuteReader();
- using (reader)
- {
- try
- {
- reader.Read();
- OracleLob tmpBlob = reader.GetOracleLob(4);
- reader.Close();
- FileStream fsBlob = new FileStream(fileToUpload, FileMode.OpenOrCreate, FileAccess.Read);
- //BinaryReader br = new BinaryReader(fs);
- tmpBlob.BeginBatch(OracleLobOpenMode.ReadWrite);
- long length = fsBlob.Length;
- int numBytesToRead = System.Convert.ToInt32(length / 10);//解決問題2
- int numBytesRead = 0;
- int n;
- byte[] Buffer = new byte[numBytesToRead];
- //2010.10.25 修改加 將文件分為10塊 防止文件為3.3G以上
- //解決問題1
- for (int i = 0; i < 9; i++)
- {
- n = 0;
- // numBytesToRead = length / 5;
- Buffer = new byte[numBytesToRead];
- numBytesRead = 0;
- while ((n = fsBlob.Read(Buffer, numBytesRead, numBytesToRead)) > 0)
- {
- numBytesRead += n;
- numBytesToRead -= n;
- }
- numBytesToRead = System.Convert.ToInt32(length / 10);
- tmpBlob.Write(Buffer, 0, numBytesToRead);
- }
- numBytesToRead = System.Convert.ToInt32(length / 10+ length % 10);
- numBytesRead = 0;
- n = 0;
- int tmpLength = numBytesToRead;
- byte[] Buffer2 = new byte[tmpLength];
- while ((n = fsBlob.Read(Buffer2, numBytesRead, numBytesToRead)) > 0)
- {
- numBytesRead += n;
- numBytesToRead -= n;
- }
- //numBytesToRead = tmpLength;
- tmpBlob.Write(Buffer2, 0, tmpLength);
- fsBlob.Close();
- tmpBlob.EndBatch();
- cmd.Parameters.Clear();
- Buffer = null;
- }
- catch(Exception ex)
- {
- MessageBox.Show("出錯:"+ex.Message);
- //關閉
- reader.Close();
- transaction.Commit();
- conn.Close();
- return false;
- }
- }
- reader.Close();
- transaction.Commit();
- conn.Close();
- return true;
- }
上面的方法完全能處理4G以下的視頻數據的導入問題,已經經過驗證的。PLSQL Developer工具同樣無法讀取BLOB字段中的大數據量的視頻,如需讀取請詳細參照http://www.cnblogs.com/wuhenke/archive/2010/10/25/1860752.Html
- /// <summary>
- /// 從數據庫中讀出大字段到文件中
- /// </summary>
- /// <param name="uploadSQL"></param>
- /// <returns></returns>
- public bool OracleRead(string uploadSQL)
- {
- string connection = strConn;
- OracleConnection conn;
- conn = new OracleConnection(connection);
- conn.Open();
- OracleCommand cmd = new OracleCommand(uploadSQL, conn);
- long readStartByte = 0;//從BLOB數據體的何處開始讀取數據
- int hopeReadSize = 1024; //希望每次從BLOB數據體中讀取數據的大小
- long realReadSize = 0;//每次實際從BLOB數據體中讀取數據的大小
- //CommandBehavior.SequentialAccess將使OracleDataReader以流的方式加載BLOB數據
- string filename = "F:\\Test"+DateTime.Now.Day+DateTime.Now.Minute+DateTime.Now.Second+".avi";
- OracleDataReader dr = cmd.ExecuteReader(CommandBehavior.SequentialAccess);
- while (dr.Read())
- {
- FileStream fs = new FileStream(filename, FileMode.Create);
- byte[] buffer = new byte[hopeReadSize];
- realReadSize = dr.GetBytes(0, readStartByte, buffer, 0, hopeReadSize);
- //循環,每次讀取1024byte大小,並將這些字節寫入流中
- while ((int)realReadSize == hopeReadSize)
- {
- fs.Write(buffer, 0, hopeReadSize);
- readStartByte += realReadSize;
- realReadSize = dr.GetBytes(0, readStartByte, buffer, 0, hopeReadSize);
- }
- //讀取BLOB數據體最後剩余的小於1024byte大小的數據,並將這些字節寫入流中
- realReadSize = dr.GetBytes(0, readStartByte, buffer, 0, hopeReadSize);
- fs.Write(buffer, 0, (int)realReadSize);
- }
- //transaction.Commit();
- conn.Close();
- return true;
- }