Подтвердить что ты не робот

Из памяти при чтении строки из SqlDataReader

Я сталкиваюсь с самой странной вещью, которую я не могу понять. У меня есть таблица SQL с кучей отчетов, хранящихся в поле ntext. Когда я скопировал и вставил значение одного из них в блокнот и сохранил его (использовал Visual Studio для захвата значения из меньшего отчета в другой строке), необработанный txt файл был около 5 Мб. Когда я пытаюсь получить эти же данные с помощью SqlDataReader и преобразовать его в строку, я получаю исключение из памяти. Вот как я пытаюсь это сделать:

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

Я попытался создать объект и построитель строк для захвата данных, но я все равно получаю исключение из памяти. Я также пробовал использовать reader.GetValue(0).ToString(), но безрезультатно. Запрос возвращает только 1 строку, и когда я запускаю ее в SQL Management Studio, она будет такой же счастливой, как может быть.

Исключение составляет:

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

Я тестировал с другими номерами строк, которые, казалось, работали, но это было ложным положительным, так как у идентификатора теста не было данных. Я вытащил еще один идентификатор теста, посмотрев на таблицу, содержащую отчеты, которые почти идентичны, и я получаю то же исключение. Может быть, как кодируется строка? Данные, хранящиеся в таблице, представляют собой закодированную в JSON строку, которая была создана из действительно gnarly класса, который я сделал где-то еще, в случае, если это помогает.

Вот предыдущий кодовый блок:

// 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. Это длинный выстрел, и я снова обновлю, что я нахожу.

Edit2: Изменение поля в таблице на nvarchar (max) не повлияло. Я также попытался использовать output = cmd.ExecuteScalar(). ToString() также без какого-либо воздействия. Я пытаюсь посмотреть, есть ли максимальный размер для SqlDataReader. Когда я скопировал значение текста из SQL Mgmt Studio, это было всего 43 КБ, если сохранить его в блокноте. Чтобы проверить это, я вытащил отчет с известным рабочим идентификатором (меньший отчет), и когда я скопировал значение прямо из Visual Studio и сбросил его в блокнот, это было около 5 МБ! Это означает, что эти большие отчеты, вероятно, находятся в диапазоне ~ 20 МБ, сидя в поле nvarchar (max).

Edit3: Я перезагрузил все, чтобы включить мой сервер IIS, сервер SQL и мой ноутбук. Теперь это работает. Это не ответ, почему это произошло. Я оставляю этот вопрос открытым для объяснений относительно того, что произошло, и я отмечу один из них в качестве ответа.

Edit4: Сказав это, я провел еще один тест, не меняя вещи, и то же исключение вернулось. Я действительно начинаю думать, что это проблема SQL. Я обновляю теги по этому вопросу. Я сделал отдельное приложение, которое запускает тот же самый запрос и работает нормально.

Edit5: Я выполнил последовательный доступ в соответствии с одним из ответов ниже. Все правильно считывается в поток, но когда я пытаюсь записать его в строку, я все равно получаю исключение из памяти. Означает ли это проблему получения непрерывного блока памяти? Вот как я реализовал буферизацию:

                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: Чтобы добавить к этому, когда произойдет исключение OOM, я вижу, что рабочий процесс IIS (который поддерживает запущенный метод) поражает почти 700 МБ. Это работает на IIS Express, а не на полном IIS на производственном сервере. Будет ли это иметь к этому какое-либо отношение? Также, когда я вызываю Byte [] data = stream.ToArray(), я периодически получаю OOM. Я думаю, что мне действительно нужен способ предоставить больше памяти этому процессу, но я не знаю, где его настроить.

Edit7: Я только что изменил мой сервер-dev с помощью IIS Express на моем локальном компьютере на встроенный веб-сервер Visual Studio. Исключение OOM теперь исчезло. Я действительно думаю, что это было выделение непрерывного блока памяти, и по какой-либо причине IIS Express не разветвлял его. Теперь, когда он работает нормально, я опубликую на моем полномасштабном сервере в 2008R2, запускающем обычный IIS7, чтобы посмотреть, как это происходит.

4b9b3361

Ответ 1

Вы должны попытаться прочитать данные последовательно, указав поведение команды при выполнении чтения. В документации Используйте SequentialAccess для извлечения больших значений и двоичных данных. В противном случае может возникнуть исключение OutOfMemoryException и соединение будет закрыто.

В то время как последовательный доступ обычно используется для больших двоичных данных, на основе документации MSDN вы можете использовать его для чтения больших объемов символьных данных.

При обращении к данным в поле BLOB используйте GetBytes или GetChars набирали аксессоры DataReader, которые заполняют массив данные. Вы также можете использовать GetString для символьных данных; Однако. в сохраняйте системные ресурсы, которые вы, возможно, не захотите загрузить весь BLOB значение в одну строковую переменную. Вместо этого вы можете указать конкретный размер буфера возвращаемых данных и начальное местоположение для первого байта или символа, который будет считан из возвращаемых данных. GetBytes и GetChars вернут длинное значение, которое представляет собой количество возвращенных байтов или символов. Если вы передадите нулевой массив в GetBytes или GetChars, возвращаемое длинное значение будет общим числом байтов или символов в BLOB. Вы можете дополнительно указать индекс в массиве в качестве начальной позиции для считываемых данных.

Этот пример MSDN показывает, как выполнять последовательный доступ. Я считаю, что вы можете использовать метод GetChars для чтения текстовых данных.

Ответ 2

В принципе, System.OutOfMemoryException возникает не только в случае нехватки памяти, но когда вы не можете выделить единый непрерывный блок памяти для объекта. Вы часто будете видеть эту ошибку при попытке создать очень большой массив или загрузить большой объект растрового изображения или иногда при создании больших XmlDocuments...

Array и String обычно должны выделяться смежно, т.е. не могут быть разбиты на куски и выделены в пустые места в памяти.

Вероятно, это не проблема SQL, и это больше проблема с SqlReader, пытающимся выделить строку, достаточно большую, чтобы содержать данные в строке.

Вы упомянули, что после перезагрузки он корректно работал, поэтому предположим, что ваш код в корне прав (возможно, его можно оптимизировать, чтобы скорее подвергать данные потоку, а не буферизации набора записей) и что текущий симптом является экологическим. У недавно перезагруженной машины, возможно, не так много фрагментированной памяти, но по мере ее использования фрагментация памяти и ошибка возвращаются...

Возможно, вам удастся доказать непрерывную теорию памяти, закрыв как можно больше других программ, и добавив код, чтобы заставить ссылку GC.Collect(GC.MaxGeneration) () перед кодом с ошибкой. Это не гарантия, так как память, выделенная для вашего процесса, может быть фрагментирована.

Я думаю, что потоковое значение может быть способом остановить возникшую ошибку, и лучше избегать попыток буферизации всего в строку. Недостатком этого является то, что вы оставите соединение с базой данных открытым, в то время как результат будет передаваться/потребляться остальной частью программы, и это принесет свои собственные накладные расходы. Я не уверен, что ваш код должен делать с результатом, но если ему нужно работать с экземпляром String, вам может потребоваться расширить доступную для процесса память (несколько способов помочь, но могут быть отключены -topic - оставить комментарий, и я могу добавить к этому ответу, если это необходимо)

Ответ 3

дикая предпосылка здесь.

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

вы пропустили знак @. поэтому он заменяет оба экземпляра CompiledReportTimeID идентификатором, и вы получаете все результаты вместо этого из-за равенства?