简体   繁体   English

从SqlDataReader读取字符串时内存不足

[英]Out of Memory when reading a string from SqlDataReader

I'm running into the strangest thing that I can't figure out. 我遇到了一些我无法弄清楚的最奇怪的事情。 I have a SQL table with a bunch of reports stored in an ntext field. 我有一个SQL表,其中包含一堆存储在ntext字段中的报告。 When I copied and pasted the value of one of them into notepad and saved it (used Visual Studio to grab the value from a smaller report in a differente row), the raw txt file was about 5Mb. 当我将其中一个的值复制并粘贴到记事本中并保存它时(使用Visual Studio从不同行中的较小报表中获取值),原始txt文件大约为5Mb。 When I try to get this same data using SqlDataReader and convert it to a string, I get an out of memory exception. 当我尝试使用SqlDataReader获取相同的数据并将其转换为字符串时,我得到一个内存不足的异常。 Here is how I am trying to do it: 以下是我尝试这样做的方法:

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();

I tried creating an object and a stringbuilder to grab the data, but I still get the same out of memory exception. 我尝试创建一个对象和一个stringbuilder来获取数据,但我仍然得到相同的内存不足异常。 I've also tried using reader.GetValue(0).ToString() as well to no avail. 我也尝试过使用reader.GetValue(0).ToString()也无济于事。 The query only returns 1 row, and when I run it in SQL Management Studio its as happy as can be. 查询只返回1行,当我在SQL Management Studio中运行时,它尽可能快乐。

The exception thrown is: 抛出的异常是:

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

I had tested with other row numbers that appeared to work, but that was a false positive as those test ID's had no data. 我已经测试了其他似乎有用的行号,但这是误报,因为那些测试ID没有数据。 I pulled some other test ID's after looking at the table that contain reports that are near identical, and I get the same exception. 在查看包含几乎相同的报告的表之后,我提取了一些其他测试ID,我得到了相同的异常。 Maybe its how the string is encoded? 也许它的字符串是如何编码的? The data stored in the table is a JSON encoded string that was generated out of a really gnarly class I made somewhere else, in case that helps. 存储在表中的数据是一个JSON编码的字符串,它是由我在其他地方创建的一个非常粗糙的类生成的,如果有帮助的话。

Here is the preceding code block: 这是前面的代码块:

// 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 is a dictionary that gets the correct CompiledReportTimeTypeID based on a string parameter that's fed in at the beginning of the method. CompiledReportTypeIDs是一个字典,它根据在方法开头输入的字符串参数获取正确的CompiledReportTimeTypeID。 ReportTime is a DateTime that is fed in earlier. ReportTime是早先提供的DateTime。

Edit: I am going to drop the table and recreate it with the ReportData field as nvarchar(MAX) instead of ntext, just to rule out a SQL data type issue. 编辑:我将删除表并使用ReportData字段将其重新创建为nvarchar(MAX)而不是ntext,只是为了排除SQL数据类型问题。 It's a long shot and I'll update again with what I find. 这是一个很长的镜头,我会用我发现的东西再次更新。

Edit2: Changing the field in the table to nvarchar(max) had no effect. Edit2:将表中的字段更改为nvarchar(max)无效。 I also tried using output = cmd.ExecuteScalar().ToString() as well, with no impact. 我也尝试过使用output = cmd.ExecuteScalar()。ToString(),没有任何影响。 I'm trying to see if there is a max size for SqlDataReader. 我正在尝试查看SqlDataReader是否有最大大小。 When I copied the value of the text from SQL Mgmt Studio, it was only 43Kb when saved in notepad. 当我从SQL Mgmt Studio复制文本的值时,在记事本中保存时只有43Kb。 To verify this, I pulled a report with a known working ID (a smaller report), and when I copied the value straight out of Visual Studio and dumped it in notepad it was around 5MB! 为了验证这一点,我提取了一份具有已知工作ID(较小的报告)的报告,当我将值直接从Visual Studio中复制并将其转储到记事本中时,它大约为5MB! That means these big reports are probably in the ~20MB range sitting in a nvarchar(max) field. 这意味着这些大型报告可能位于nvarchar(max)字段的~20MB范围内。

Edit3: I rebooted everything, to include my dev IIS server, the SQL server, and my dev laptop. Edit3:我重启了一切,包括我的开发IIS服务器,SQL服务器和我的开发笔记本电脑。 Now it seems to be working. 现在它似乎正在起作用。 This isn't the answer as to why this happened though. 这不是为什么会发生这种情况的答案。 I'm leaving this question open for explanations as to what happened, and I'll mark one of those as an answer. 我将这个问题留待解释所发生的事情,我将其中一个标记为答案。

Edit4: Having said that, I ran another test without changing a thing and the same exception has returned. 编辑4:话虽如此,我在没有改变事情的情况下运行了另一个测试并返回了相同的异常。 I'm really starting to think that this is a SQL issue. 我真的开始认为这是一个SQL问题。 I'm updating the tags on this question. 我正在更新这个问题的标签。 I made a separate app that runs the exact same query and it runs fine. 我做了一个单独的应用程序运行完全相同的查询,它运行正常。

Edit5: I have implemented sequential access as per one of the answers below. 编辑5:我按照下面的一个答案实现了顺序访问。 Everything gets read into a stream properly, but when I try to write it out to a string I'm still getting the out of memory exception. 所有东西都被正确地读入流中,但当我尝试将其写入字符串时,我仍然会遇到内存不足异常。 Would this indicate the issue of getting a contiguous block of memory? 这是否表明获得连续的内存块的问题? Here is how I implemented the buffering: 以下是我实现缓冲的方法:

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

Edit6: To add to this, when OOM exception happens I see the IIS worker process (which holds the method that is running) hit almost 700MB. Edit6:添加到此,当OOM异常发生时,我看到IIS工作进程(它保存正在运行的方法)命中几乎700MB。 This is running on IIS Express and not the full IIS on the production server. 这是在IIS Express上运行的,而不是生产服务器上的完整IIS。 Would this have anything to do with it? 这与它有什么关系吗? Also when I call Byte[] data = stream.ToArray() I intermittently get the OOM as well. 此外,当我调用Byte [] data = stream.ToArray()时,我也断断续续地获取OOM。 I think what I really need is a way to give more memory to this process, but I don't know where to configure this. 我认为我真正需要的是一种为这个过程提供更多内存的方法,但我不知道在哪里配置它。

Edit7: I just changed my dev server from using IIS Express on my local machine to the built-in Visual Studio web server. 编辑7:我刚刚将我的开发服务器从本地计算机上的IIS Express更改为内置的Visual Studio Web服务器。 The OOM exception is now gone. OOM例外现在已经消失。 I really think it was the allocating a contiguous block of memory issue, and for whatever reason IIS Express wouldn't fork it over. 我真的认为这是分配一个连续的内存块问题,无论出于何种原因,IIS Express都不会将其分叉。 Now that it is running fine, I will publish to my full blown server on 2008R2 running the regular IIS7 to see how it goes. 现在它运行正常,我将在2008R2上发布运行常规IIS7的完整服务器以查看它是如何运行的。

You should try to read the data sequentially by specifying the command behavior when you execute the reader. 您应该通过在执行阅读器时指定命令行为来尝试按顺序读取数据。 Per the documentation, Use SequentialAccess to retrieve large values and binary data. 根据文档, 使用SequentialAccess检索大值和二进制数据。 Otherwise, an OutOfMemoryException might occur and the connection will be closed . 否则,可能会发生OutOfMemoryException,并且将关闭连接

While sequential access is typically used on large binary data, based on the MSDN documentation you can use it to read large amounts of character data as well. 虽然顺序访问通常用于大型二进制数据,但基于MSDN文档,您也可以使用它来读取大量字符数据。

When accessing the data in the BLOB field, use the GetBytes or GetChars typed accessors of the DataReader, which fill an array with data. 访问BLOB字段中的数据时,请使用DataReader的GetBytes或GetChars类型访问器,这些访问器使用数据填充数组。 You can also use GetString for character data; 您还可以将GetString用于字符数据; however. 然而。 to conserve system resources you might not want to load an entire BLOB value into a single string variable. 为了节省系统资源,您可能不希望将整个BLOB值加载到单个字符串变量中。 You can instead specify a specific buffer size of data to be returned, and a starting location for the first byte or character to be read from the returned data. 您可以改为指定要返回的数据的特定缓冲区大小,以及从返回的数据中读取的第一个字节或字符的起始位置。 GetBytes and GetChars will return a long value, which represents the number of bytes or characters returned. GetBytes和GetChars将返回一个long值,表示返回的字节数或字符数。 If you pass a null array to GetBytes or GetChars, the long value returned will be the total number of bytes or characters in the BLOB. 如果将空数组传递给GetBytes或GetChars,则返回的long值将是BLOB中的总字节数或字符数。 You can optionally specify an index in the array as a starting position for the data being read. 您可以选择将数组中的索引指定为正在读取的数据的起始位置。

This MSDN example shows how to perform sequential access. MSDN示例显示了如何执行顺序访问。 I believe you can use the GetChars method to read the textual data. 我相信你可以使用GetChars方法来读取文本数据。

Fundamentally, a System.OutOfMemoryException doesn't just occur when you are out of memory, but when you cannot allocate a single contiguous block of memory for an object. 从根本上说, System.OutOfMemoryException不仅在内存不足时发生,而且当您无法为对象分配单个连续的内存块时。 You'll often see that error when trying to create a very large array, or load a large bitmap object, or sometimes when creating large XmlDocuments... 尝试创建一个非常大的数组,或者加载一个大的位图对象时,或者有时在创建大型XmlDocuments时,您经常会看到错误...

Array and String typically need to be allocated contiguously, ie can't be broken up into pieces and allocated into empty spaces in memory. ArrayString通常需要连续分配,即不能分解成碎片并分配到内存中的空白区域。

This likely isn't a SQL issue and is more an issue with the SqlReader trying to allocate a string large enough to contain the data in a row. 这可能不是SQL问题,而且SqlReader尝试分配足够大的字符串以包含连续数据时更是一个问题。

You mentioned that it worked properly after a reboot, so let's assume your code is fundamentally correct (possibly can still be optimised to rather expose the data as a stream instead of buffering the recordset) and that the current symptom is environmental. 你提到它在重新启动后工作正常,所以让我们假设你的代码基本上是正确的(可能仍然可以优化以将数据显示为流而不是缓冲记录集)并且当前症状是环境的。 A freshly rebooted machine possibly doesn't have as much fragmented memory, but as you used it more, the memory fragmented and the error returned... 一个刚刚重新启动的机器可能没有那么多碎片的内存,但是当你使用它时,内存碎片化并返回错误...

You may be able to prove the contiguous memory theory by closing as many other programs as possible, and adding code to force a GC.Collect(GC.MaxGeneration) ( reference ) before the code with the error. 可以通过关闭尽可能多的其他程序来证明连续的内存理论,并添加代码以强制执行带有错误的代码之前的GC.Collect(GC.MaxGeneration)引用 )。 This isn't a guarantee, as the memory allocated to your process may still be fragmented. 这不是保证,因为分配给您的进程的内存可能仍然是碎片。

I think streaming the value might be the way to stop the error occurring, and better to avoid trying to buffer everything into a string. 我认为流式传输值可能是阻止错误发生的方法,最好避免尝试将所有内容缓冲到字符串中。 The downside to this is that you will keep the database connection open while the result is streamed / consumed by the rest of the program and that will bring its own overheads. 这样做的缺点是,在结果被程序的其余部分流式传输/消耗时,您将保持数据库连接处于打开状态,这将带来自己的开销。 I'm not sure what your code needs to do with the result, but if it needs to work with a String instance, you may need to expand the memory available to the process (several ways to help that, but may be off-topic - leave a comment and I can add to this answer if needed) 我不确定你的代码需要对结果做什么,但如果它需要使用String实例,你可能需要扩展进程可用的内存(有几种方法可以帮助它,但可能是偏离主题的 - 发表评论,如果需要,我可以添加到这个答案)

wild guess here. 这里疯狂猜测。

cmd.Parameters.Add(new SqlParameter("CompiledReportTimeID", CompiledReportTimeID));

you missed the @ sign. 你错过了@符号。 so it replaces both instances of CompiledReportTimeID with the id and you get all the results instead because of the equality? 所以它用ID替换了CompiledReportTimeID的两个实例,并且由于相等而得到所有结果?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM