私は理解できない最も奇妙なことに遭遇しています。ntextフィールドに多数のレポートが格納されたSQLテーブルがあります。そのうちの1つの値をコピーしてメモ帳に貼り付けて保存すると(Visual Studioを使用して別の行の小さなレポートから値を取得)、生のtxtファイルは約5Mbでした。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行しか返しません。SQLManagementStudioで実行すると、可能な限り満足のいく結果が得られます。
スローされる例外は次のとおりです。
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です。
編集:SQLデータ型の問題を除外するために、テーブルを削除し、ReportDataフィールドをntextではなくnvarchar(MAX)として再作成します。それはロングショットであり、私が見つけたもので再び更新します。
Edit2:テーブルのフィールドをnvarchar(max)に変更しても効果はありませんでした。また、output = cmd.ExecuteScalar()。ToString()も使用してみましたが、影響はありませんでした。SqlDataReaderの最大サイズがあるかどうかを確認しようとしています。SQL Mgmt Studioからテキストの値をコピーしたとき、メモ帳に保存したときの値はわずか43Kbでした。これを確認するために、既知の作業ID(小さいレポート)を使用してレポートを取得しました。値をVisual Studioから直接コピーしてメモ帳にダンプすると、約5MBになりました。つまり、これらの大きなレポートは、おそらくnvarchar(max)フィールドにある約20MBの範囲にあります。
Edit3:開発IISサーバー、SQLサーバー、開発ラップトップを含めるために、すべてを再起動しました。今は動いているようです。しかし、これはなぜこれが起こったのかについての答えではありません。何が起こったのかを説明するためにこの質問を開いたままにしておき、そのうちの1つを回答としてマークします。
Edit4:そうは言っても、何も変更せずに別のテストを実行したところ、同じ例外が返されました。これはSQLの問題だと本当に思い始めています。この質問のタグを更新しています。まったく同じクエリを実行する別のアプリを作成しましたが、正常に実行されます。
Edit5:以下の回答の1つに従って、シーケンシャルアクセスを実装しました。すべてが適切にストリームに読み込まれますが、文字列に書き出そうとすると、メモリ不足の例外が発生します。これは、メモリの連続ブロックを取得する問題を示していますか?バッファリングを実装した方法は次のとおりです。
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ワーカープロセス(実行中のメソッドを保持している)が約700MBに達することがわかります。これはIISExpressで実行されており、運用サーバーの完全なIISでは実行されていません。これはそれと何か関係がありますか?また、Byte [] data = stream.ToArray()を呼び出すと、断続的にOOMも取得します。私が本当に必要としているのは、このプロセスにより多くのメモリを与える方法だと思いますが、これをどこで構成するかはわかりません。
Edit7:開発サーバーをローカルマシンでのIISExpressの使用から組み込みのVisualStudioWebサーバーに変更しました。OOM例外はなくなりました。これは、メモリの問題の連続したブロックを割り当てることであり、何らかの理由でIISExpressがそれをフォークしなかったと本当に思います。正常に動作しているので、通常のIIS7を実行している2008R2の本格的なサーバーに公開して、どのように動作するかを確認します。