約590,000レコードを返す動的クエリがあります。初めて正常に実行されますが、もう一度実行すると、System.OutOfMemoryException
。これが起こる可能性のある理由は何ですか?
ここでエラーが発生しています:
public static DataSet GetDataSet(string databaseName,string
storedProcedureName,params object[] parameters)
{
//Creates blank dataset
DataSet ds = null;
try
{
//Creates database
Database db = DatabaseFactory.CreateDatabase(databaseName);
//Creates command to execute
DbCommand dbCommand = db.GetStoredProcCommand(storedProcedureName);
dbCommand.CommandTimeout = COMMAND_TIMEOUT;
//Returns the list of SQL parameters associated with that stored proecdure
db.DiscoverParameters(dbCommand);
int i = 1;
//Loop through the list of parameters and set the values
foreach (object parameter in parameters)
{
dbCommand.Parameters[i++].Value = parameter;
}
//Retrieve dataset and set to ds
ds = db.ExecuteDataSet(dbCommand);
}
//Check for exceptions
catch (SqlException sqle)
{
throw sqle;
}
catch (Exception e)
{
throw e; // Error is thrown here.
}
//Returns dataset
return ds;
}
ボタンクリックで実行されるコードは次のとおりです。
protected void btnSearchSBIDatabase_Click(object sender, EventArgs e)
{
LicenseSearch ls = new LicenseSearch();
DataTable dtSearchResults = new DataTable();
dtSearchResults = ls.Search();
Session["dtSearchResults"] = dtSearchResults;
Response.Redirect("~/FCCSearch/SearchResults.aspx");
}
else
lblResults.Visible = true;
}
初回は正常に実行されますが、再度実行すると、System.OutOfMemoryExceptionが発生し続けます。これが起こる可能性のある理由は何ですか?
他の人が言ったことに関係なく、エラーはDBCommandまたはDBConnectionの破棄を忘れることとは関係がなく、どちらかを破棄してもエラーは修正されません。
このエラーは、ほぼ600,000行のデータを含むデータセットに関係しています。どうやらデータセットは、マシンで利用可能なメモリの50%以上を消費しているようです。明らかに、最初のデータセットがガベージコレクションされる前に同じサイズの別のデータセットを返すと、メモリが不足します。そのような単純な。
この問題はいくつかの方法で解決できます。
より少ないレコードを返すことを検討してください。個人的には、60万件のレコードを返すことがユーザーにとって有用であった時代は想像できません。返されるレコードを最小化するには、次を試してください。
クエリを最初の1000レコードに制限します。クエリから返される結果が1000を超える場合、検索結果を絞り込むようユーザーに通知します。
ユーザーが大量のデータを一度に表示することを本当に求めている場合は、データをページングしてみてください。覚えておいてください:Googleは一度に22百万件の検索結果を一度に表示することは決してありません。 Googleはおそらく一度に22兆個の結果すべてを一度にメモリに保持するわけではありません。おそらく、データベースを再クエリして新しいページを生成する方がメモリ効率が高いことに気付くでしょう。
データを反復処理するだけで、ランダムアクセスが不要な場合は、代わりにデータリーダーを返します。データリーダーは、一度に1つのレコードのみをメモリにロードします。
これらのいずれもオプションではない場合、これらのメソッドのいずれかを使用してメソッドを呼び出す前に、データセットで使用されているメモリを.NETに強制的に解放する必要があります。
古いデータセットへのすべての参照を削除します。データセットの保護を保持しているものはすべて、データセットがメモリによって回収されるのを防ぎます。
データセットへのすべての参照をnullにできない場合は、代わりにデータセットからすべての行とそれらの行にバインドされているオブジェクトをすべてクリアします。これにより、データ行への参照が削除され、ガベージコレクターがそれらを使用できるようになります。
GC.Collect()
を呼び出して生成サイクルを強制する必要があるとは思わない。一般に、GC.Collect()
を呼び出すのは悪い考えであるだけでなく、十分なメモリプレッシャーによって.NETがガベージコレクターを単独で呼び出すためです。
注:データセットでDisposeを呼び出すと、メモリが解放されず、ガベージコレクターが呼び出されず、データセットへの参照も削除されません。Disposeは、アンマネージリソースをクリーンアップするために使用されますが、DataSet管理されていないリソース。IDispoableを実装するのはMarshalByValueComponentに固有であるため、データセットのDisposeメソッドはほとんど役に立ちません。
おそらく、前回の実行からの以前の接続/結果クラスを破棄していないので、それらはまだメモリ内に残っています。
あなたは明らかに物を処分していません。
IDisposableを実装するオブジェクトを一時的に使用する場合は、「using」コマンドを検討してください。
私はすでにこの種の問題に何度も直面しているため、大きなデータを可能な限り破壊するようにしてください。ここでは、15列の10 Lakh以上のレコードがあります。
どこで失敗しますか?
あなたの問題はおそらく、600,000行のデータセットがおそらく大きすぎるということです。それをセッションに追加していることがわかります。 Sqlセッション状態を使用している場合は、そのデータもシリアル化する必要があります。
オブジェクトを適切に破棄しても、セッションで1回、手続きコードで1回、2回実行すると、常にこのデータセットの少なくとも2つのコピーがメモリに保持されます。これは、Webアプリケーションでは決してスケーリングしません。
1行あたり1〜128ビットのGUIDで計算を行うと、600,000行となり、データセットのオーバーヘッドは言うまでもなく、9.6メガバイト(600k * 128/8)のデータのみになります。
結果をトリミングします。