2013-02-27 19 views
13

私はわかりません。私はntextフィールドに格納されたレポートの束とSQLテーブルを持っています。そのうちの1つの値をコピーしてメモ帳に貼り付けて保存したとき(Visual Studioを使用して別の行の小さなレポートから値を取得)、raw txtファイルは約5Mbでした。 SqlDataReaderを使ってこの同じデータを取得しようとすると、それを文字列に変換すると、メモリ不足例外が発生します。ここで私はそれをやろうとしています方法です: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(); 

私は、オブジェクトとデータをつかむためのStringBuilderを作成しようとしたが、私はまだメモリ例外の同じアウトを取得します。私も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 

私は仕事に登場し、他の行番号でテストしていたが、それは、これらのテスト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方法の開始時にFRBの文字列パラメータに基づいて正しいCompiledReportTimeTypeIDを取得した辞書です。 ReportTimeは、以前に入力されたDateTimeです。

編集: SQLデータ型の問題を排除するために、テーブルを削除し、ntextの代わりにReportDataフィールドをnvarchar(MAX)として再作成します。それはロングショットなので、私が見つけたものをもう一度更新します。

Edit2: テーブルのフィールドをnvarchar(max)に変更しても効果はありません。私はまた、output = cmd.ExecuteScalar()。ToString()も同様に影響を与えずに使ってみました。私はSqlDataReaderの最大サイズがあるかどうかを調べようとしています。 SQL Mgmt Studioからテキストの値をコピーしたとき、メモ帳に保存されたのはわずか43Kbでした。これを確認するには、既知の作業ID(小さいレポート)のレポートを取り出し、Visual Studioから値をそのままコピーしてメモ帳にダンプしたところ、約5MBでした!つまり、これらの大きなレポートはおそらくnvarchar(最大)フィールドに〜20MBの範囲内にあることを意味します。

Edit3: 私は、devの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ワーカープロセスは、ほぼ700メガバイトを打つ見る起こると、これに追加するには。これはIIS Expressでは実行されており、本番サーバー上の完全なIISでは実行されません。これはそれと関係がありますか?また、私はByte [] data = stream.ToArray()を呼び出すときに断続的にOOMを取得します。私は本当に必要なのは、このプロセスにもっと多くのメモリを与える方法だと思いますが、どこで設定するのか分かりません。

Edit7: 私のローカルマシン上のIIS Expressを組み込みのVisual Studio Webサーバーに使用することから、私の開発サーバーを変更しました。今OOM例外はなくなりました。私は実際にそれがメモリ問題の連続的なブロックを割り当てていたと思うし、何か理由でIIS Expressはそれをフォークしないだろうと思う。今は正常に動作していますので、2008R2の全面的なサーバーに公開して、通常のIIS7を実行して、その動作を確認します。

+1

完全なエラーメッセージも含めてください。 –

+1

返される文字列の大きさはどれくらいですか?言い換えれば、ReportDataの大きさはどれくらいですか? –

+0

例外の完全なスタックトレースを表示します。 –

答えて

9

リーダーを実行するときにcommand behaviorを指定して、データを順番に読み取ろうとする必要があります。ドキュメントごとにSequentialAccessを使用して、大きな値とバイナリデータを取得します。そうしないと、OutOfMemoryExceptionが発生し、接続が終了します

通常、大規模なバイナリデータではシーケンシャルアクセスが使用されますが、MSDNのドキュメントに基づいて大量の文字データの読み取りにも使用できます。 BLOBフィールド内のデータにアクセスする場合、GetBytesメソッドまたは GetCharsはを使用 データを持つ配列を埋めるのDataReaderのアクセサを、型付き

。文字データにはGetStringを使用することもできます。しかしながら。 にシステムリソースを節約すると、BLOB全体の 値を1つの文字列変数にロードしたくない場合があります。代わりに、返されるデータの具体的なバッファサイズは 、返されるデータから読み取られる最初のバイトまたは文字は開始位置 と指定できます。 GetBytesとGetCharsは、返されたバイト数または文字数の を表すlong値を返します。 NULL配列を GetBytesまたはGetCharsに渡すと、返されるlong値はBLOB内の合計文字数 バイトまたは文字になります。任意で、読み取るデータの開始位置として配列内の インデックスを指定することができます。

このMSDN exampleは、順次アクセスの実行方法を示しています。私はGetCharsメソッドを使ってテキストデータを読むことができると信じています。

+0

これは有望です。私はその日のためにジェット機に乗る必要がありますが、私はこの最初のことを朝にしようとします。 –

+0

バッファリングは素晴らしいですが、ストリームに書き込むときにOOM例外が発生します。 .GetBytes()の代わりに.GetChars()を使用すると、結果を含むchar配列をインスタンス化するフィールドの長さを取得しようとしているので、すぐにOOMを取得します。 –

+0

DATALENGTHを使用して、結果セットの一部として全長を返し、その値を使用して結果をチャンクで読み取る前に配列を構築することができます。 – Oppositional

0

ここに野生のものがあります。

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

あなたは@記号が欠けていました。 CompiledReportTimeIDの両方のインスタンスをidに置き換えて、等価性のためにすべての結果を得ますか?

+0

SQLパラメータの最初の引数に@を追加すると、同じ結果が得られます。 Stack Overflowは、面白いが、コンパイルされたReportTimeを書式化しましたが、単なるintです。私はパラメータをintと同じものに指定しました。これはまた、テーブル内のフィールドと同じ名前になります。おそらくそれをそのように名づけるベストプラクティスではないでしょう(私はこの愚かなことをやった後に修正します) –

4

基本的に、メモリが不足しているときだけでなく、オブジェクトに1つの連続したメモリブロックを割り当てることができないときには、System.OutOfMemoryExceptionが発生しません。大きなXmlDocumentsを作成するときには、多くの場合、つまりはできない、

ArrayStringは、一般的に連続して割り当てる必要が...時には非常に大きな配列を作成しようとすると、エラーを参照してください、または大規模なビットマップオブジェクトを読み込み、もしくはます断片に分割され、メモリ内の空き領域に割り当てられます。

これはSQLの問題ではない可能性があり、SqlReaderが行内のデータを格納するのに十分な大きさの文字列を割り当てようとすると、より問題になります。

再起動後に正常に動作すると述べましたので、コードが根本的に正しいと仮定しましょう(レコードセットをバッファリングする代わりにデータをむしろストリームとして公開することができます)。新しく再起動されたマシンは断片化されたメモリを持っていない可能性がありますが、それを多く使用するとメモリが断片化し、エラーが返されます...

閉じるメモリ理論としてできるだけ多くの他のプログラムを追加し、エラーのコードの前にGC.Collect(GC.MaxGeneration)reference)を強制的に追加するコードを追加します。これは、プロセスに割り当てられたメモリが断片化している可能性があるため、保証ではありません。

私は値をストリーミングするとエラーが発生するのを防ぐことができ、すべてをストリングにバッファリングしようとするのを避ける方がよいと思います。欠点は、データベース接続を開いたままにして、残りのプログラムによって結果がストリーム/消費され、それが独自のオーバーヘッドをもたらすことです。あなたのコードが結果と関係があるかどうかは分かりませんが、Stringインスタンスで動作する必要がある場合は、プロセスに利用可能なメモリを拡張する必要があります(それを助けるいくつかの方法がありますが、 - コメントを残して、必要に応じてこの回答に追加できます)

+0

私はGCを無駄にしようとしましたが(良いアイデア!)。私はOppositionalの答えに従ってバッファリングを実装し、ストリングにストリームをダンプしようとするとOOMを取得しています。それで私はメモリ割り当ての問題に同意する。利用可能なプロセスメモリを拡張するためのガイドへのリンクがありますか?私はもちろんこれがなければ私が必要とすることをする方法を見つけなければなりませんが、そのようなものは今のところうまくいくでしょう。 –

+0

メモリを稼働させるために環境設定を微調整しようとするのではなく、本当にお勧めします。あなたは目的地にデータをストリーミングするオプションがありますか?例えばあなたが2つのダムの間のポンピングステーションであると想像してください。あなたは1つのダムのすべての水を他のダムに吸い上げる前に吸うことはできません。一度に1つのバッファをフラッシュする必要があります。私は問題がすべてのデータを文字列にダンプしようとしていると思います。 –

関連する問題