我遇到了一个奇怪的问题,无法解决。我有一个 SQL 表格,其中有一大堆报告存储在 ntext 字段中。当我将其中一个报告的值复制粘贴到记事本中并保存时(使用 Visual Studio 从不同行中的较小报告中获取该值),原始文本文件大小约为 5MB。但是,当我尝试使用 SqlDataReader 获取相同的数据并将其转换为字符串时,会出现内存不足异常。以下是我的尝试方法:
我尝试创建一个对象和一个stringbuilder来获取数据,但我仍然得到相同的内存溢出异常。我也尝试使用reader.GetValue(0).ToString(),但也无济于事。查询只返回1行,当我在SQL管理工具中运行它时,它是很顺利的。
抛出的异常是:
我曾经测试了其他行号,看起来它们是有效的,但那是一个误报,因为这些测试ID没有数据。在查看包含几乎相同报告的表后,我提取了一些其他测试ID,并得到了相同的异常。也许这是字符串编码的问题?表中存储的数据是一个JSON编码的字符串,该字符串是从我在其他地方创建的一个非常复杂的类中生成的,如果这有帮助的话。
以下是前面的代码块:
CompiledReportTypeIDs是一个字典,根据在方法开始时提供的字符串参数来获取正确的CompiledReportTimeTypeID。ReportTime是之前提供的DateTime。
编辑:我将删除该表并重新创建它,将ReportData字段更改为nvarchar(MAX),而不是ntext,只是为了排除SQL数据类型问题。这是一个很小的可能性,我会再次更新我的发现。
编辑2:将表中的字段更改为nvarchar(max)没有影响。我还尝试使用output = cmd.ExecuteScalar()。ToString()作为替代方式,但没有影响。我正在尝试查看SqlDataReader的最大大小。当我从SQL Mgmt Studio复制文本值时,保存在记事本中的文件大小仅为43Kb。为了验证这一点,我拉出了一个已知工作ID的报告(较小的报告),当我直接从Visual Studio中复制该值并将其转储到记事本中时,它约为5MB!这意味着这些大报告可能在一个nvarchar(max)字段中达到约20MB的范围。
编辑6: 此外,当发生OOM异常时,我看到持有正在运行的方法的IIS工作进程几乎达到了700MB。这是在IIS Express上运行而不是生产服务器上的完整IIS。这是否与之有关?另外,当我调用Byte [] data = stream.ToArray()时,有时也会出现OOM。我想我真正需要的是一种给该进程更多内存的方法,但我不知道在哪里配置。
编辑7: 我刚刚将我的开发服务器从在本地机器上使用的IIS Express更改为内置的Visual Studio Web服务器。OOM异常现在已经消失了。我真的认为这是分配连续块内存的问题,由于某种原因,IIS Express无法分配它。现在它运行得很好,我将发布到运行常规IIS7的2008R2上的完整服务器上,以查看情况如何。
string output = "";
string cmdtext = "SELECT ReportData FROM Reporting_Compiled WHERE CompiledReportTimeID = @CompiledReportTimeID";
SqlCommand cmd = new SqlCommand(cmdtext, conn);
cmd.Parameters.Add(new SqlParameter("CompiledReportTimeID", CompiledReportTimeID));
SqlDataReader reader = cmd.ExecuteReader();
while (reader.Read())
{
output = reader.GetString(0); // <--- exception happens here
}
reader.Close();
我尝试创建一个对象和一个stringbuilder来获取数据,但我仍然得到相同的内存溢出异常。我也尝试使用reader.GetValue(0).ToString(),但也无济于事。查询只返回1行,当我在SQL管理工具中运行它时,它是很顺利的。
抛出的异常是:
System.OutOfMemoryException was unhandled by user code
Message=Exception of type 'System.OutOfMemoryException' was thrown.
Source=mscorlib
StackTrace:
at System.String.CreateStringFromEncoding(Byte* bytes, Int32 byteLength, Encoding encoding)
at System.Text.UnicodeEncoding.GetString(Byte[] bytes, Int32 index, Int32 count)
at System.Data.SqlClient.TdsParserStateObject.ReadString(Int32 length)
at System.Data.SqlClient.TdsParser.ReadSqlStringValue(SqlBuffer value, Byte type, Int32 length, Encoding encoding, Boolean isPlp, TdsParserStateObject stateObj)
at System.Data.SqlClient.TdsParser.ReadSqlValue(SqlBuffer value, SqlMetaDataPriv md, Int32 length, TdsParserStateObject stateObj)
at System.Data.SqlClient.SqlDataReader.ReadColumnData()
at System.Data.SqlClient.SqlDataReader.ReadColumn(Int32 i, Boolean setTimeout)
at System.Data.SqlClient.SqlDataReader.GetString(Int32 i)
at Reporting.Web.Services.InventoryService.GetPrecompiledReportingData(DateTime ReportTime, String ReportType) in C:\Projects\Reporting\Reporting.Web\Services\InventoryService.svc.cs:line 3244
at SyncInvokeGetPrecompiledReportingData(Object , Object[] , Object[] )
at System.ServiceModel.Dispatcher.SyncMethodInvoker.Invoke(Object instance, Object[] inputs, Object[]& outputs)
at System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc& rpc)
InnerException:
null
我曾经测试了其他行号,看起来它们是有效的,但那是一个误报,因为这些测试ID没有数据。在查看包含几乎相同报告的表后,我提取了一些其他测试ID,并得到了相同的异常。也许这是字符串编码的问题?表中存储的数据是一个JSON编码的字符串,该字符串是从我在其他地方创建的一个非常复杂的类中生成的,如果这有帮助的话。
以下是前面的代码块:
// get the report time ID
int CompiledReportTimeTypeID = CompiledReportTypeIDs[ReportType];
int CompiledReportTimeID = -1;
cmdtext = "SELECT CompiledReportTimeID FROM Reporting_CompiledReportTime WHERE CompiledReportTimeTypeID = @CompiledReportTimeTypeID AND CompiledReportTime = @ReportTime";
cmd = new SqlCommand(cmdtext, conn);
cmd.Parameters.Add(new SqlParameter("CompiledReportTimeTypeID", CompiledReportTimeTypeID));
cmd.Parameters.Add(new SqlParameter("ReportTime", ReportTime));
reader = cmd.ExecuteReader();
while (reader.Read())
{
CompiledReportTimeID = Convert.ToInt32(reader.GetValue(0));
}
reader.Close();
CompiledReportTypeIDs是一个字典,根据在方法开始时提供的字符串参数来获取正确的CompiledReportTimeTypeID。ReportTime是之前提供的DateTime。
编辑:我将删除该表并重新创建它,将ReportData字段更改为nvarchar(MAX),而不是ntext,只是为了排除SQL数据类型问题。这是一个很小的可能性,我会再次更新我的发现。
编辑2:将表中的字段更改为nvarchar(max)没有影响。我还尝试使用output = cmd.ExecuteScalar()。ToString()作为替代方式,但没有影响。我正在尝试查看SqlDataReader的最大大小。当我从SQL Mgmt Studio复制文本值时,保存在记事本中的文件大小仅为43Kb。为了验证这一点,我拉出了一个已知工作ID的报告(较小的报告),当我直接从Visual Studio中复制该值并将其转储到记事本中时,它约为5MB!这意味着这些大报告可能在一个nvarchar(max)字段中达到约20MB的范围。
编辑3: 我重新启动了所有设备,包括我的开发IIS服务器、SQL服务器和我的笔记本电脑。现在似乎可以工作了。但这并不是为什么会发生这种情况的答案。我将保持这个问题的开放状态,等待解释,并将其中一个标记为答案。
编辑4: 话虽如此,我进行了另一个测试,未更改任何内容,但相同的异常返回了。我真的开始认为这是一个SQL问题。我正在更新此问题的标签。我创建了一个单独的应用程序,运行完全相同的查询,它可以正常运行。
编辑5: 我按照下面一个答案所述实现了顺序访问。一切都被正确地读入流中,但当我尝试将其写入字符串时,仍然出现内存不足异常。这是否表明获取连续内存块的问题?以下是我实现缓冲的方式:
reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess);
long startIndex = 0;
long retval = 0;
int bufferSize = 100;
byte[] buffer = new byte[bufferSize];
MemoryStream stream = new MemoryStream();
BinaryWriter writer = new BinaryWriter(stream);
while (reader.Read())
{
// Reset the starting byte for the new CLOB.
startIndex = 0;
// Read bytes into buffer[] and retain the number of bytes returned.
retval = reader.GetBytes(0, startIndex, buffer, 0, bufferSize);
// Continue while there are bytes beyond the size of the buffer.
while (retval == bufferSize)
{
writer.Write(buffer);
writer.Flush();
// Reposition start index to end of last buffer and fill buffer.
startIndex += bufferSize;
retval = reader.GetBytes(0, startIndex, buffer, 0, bufferSize);
}
//output = reader.GetString(0);
}
reader.Close();
stream.Position = 0L;
StreamReader sr = new StreamReader(stream);
output = sr.ReadToEnd(); <---- Exception happens here
//output = new string(buffer);
编辑6: 此外,当发生OOM异常时,我看到持有正在运行的方法的IIS工作进程几乎达到了700MB。这是在IIS Express上运行而不是生产服务器上的完整IIS。这是否与之有关?另外,当我调用Byte [] data = stream.ToArray()时,有时也会出现OOM。我想我真正需要的是一种给该进程更多内存的方法,但我不知道在哪里配置。
编辑7: 我刚刚将我的开发服务器从在本地机器上使用的IIS Express更改为内置的Visual Studio Web服务器。OOM异常现在已经消失了。我真的认为这是分配连续块内存的问题,由于某种原因,IIS Express无法分配它。现在它运行得很好,我将发布到运行常规IIS7的2008R2上的完整服务器上,以查看情况如何。
SqlCommand
、SqlDataReader
和SqlConnection
对象周围使用using
块。 - John SaundersLen(col)
。 - RBarryYoung