很长时间没亲自写写东西了,只是收集转载了一些好资料,其实,真正静下心总结一下,可以写的知识点很多。与困难做斗争,挑战技术难关,总会有些感受心得的。
今天想和网友分享一下“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 ; }
参考资料:
http://msdn.microsoft.com/zh-cn/library/cydxhzhz(v=VS.90).aspx
http://www.cnblogs.com/zhengmaoch/archive/2005/08/10/212014.html
http://msdn.microsoft.com/en-us/library/dyh7k75y(vs.71).aspx
http://www.pczpg.com/a/2010/0719/14962.html
http://msdn.microsoft.com/en-us/library/system.io.filestream.read.aspx
http://www.csharp-examples.net/filestream-read-file/
http://blog.csdn.net/lonet/archive/2010/03/03/5342386.aspx
本博客声明:本人的技术探索过程中,得到了国信司南公司方面支持。今后,本人博客里的所有技术探索成果将归“无痕客”、“国信司南”和“博客园”三方共同所有,原创作品如需转载,请注明本博客声明。
转载于:https://www.cnblogs.com/wuhenke/archive/2010/10/25/1860809.html
相关资源:数据结构—成绩单生成器