很长时间没亲自写写东西了,只是收集转载了一些好资料,其实,真正静下心总结一下,可以写的知识点很多。与困难做斗争,挑战技术难关,总会有些感受心得的。
今天想和网友分享一下“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
本博客声明:本人的技术探索过程中,得到了国信司南公司方面支持。今后,本人博客里的所有技术探索成果将归“无痕客”、“国信司南”和“博客园”三方共同所有,原创作品如需转载,请注明本博客声明。