2

私は以前にこの問題の解決策を 2 回見つけようとしましたが、残念ながらそれらの回答は永続的な解決策を提供していませんでした。

150 万の整数 ID のリストを返す SQL Server ストアド プロシージャがあります。ASP.NET/VB.NET コードからこの SP を呼び出し、SqlDataReader を実行しています。

m_dbSel.CommandType = CommandType.StoredProcedure
m_dbSel.CommandText = CstSearch.SQL.SP_RS_SEARCH_EX
oResult = m_dbSel.ExecuteReader(CommandBehavior.CloseConnection)

次に、そのリーダーをクラス コンストラクターに渡して、Generic List(Of Integer) を構築します。コードは非常に基本的です。

Public Sub New(i_oDataReader As Data.SqlClient.SqlDataReader)

    m_aFullIDList = New Generic.List(Of Integer)

    While i_oDataReader.Read
        m_aFullIDList.Add(i_oDataReader.GetInt32(0))
    End While

    m_iTotalNumberOfRecords = m_aFullIDList.Count

End Sub

問題は、これが 150 万件のレコードをすべて読み取らないこと、数に一貫性がないこと、最終的なカウントが 500K または 100 万になる可能性があることです (ほとんどの場合、524289レコードの「魔法の」数が返されます)。コマンドの実行時に設定を使用してみましCommandBehavior.SequentialAccessたが、結果も一貫していませんでした。

SSMS で SP を実行している場合、特定の数のレコードがすぐに返されて表示されますが、150 万件のレコードがすべて完了するまでさらに数秒間実行されます。これとは関係がありますか?

アップデート


しばらくすると、非常にまれに、上記のループ コードが例外をスローすることがわかりました。

System.NullReferenceException: オブジェクト参照がオブジェクトのインスタンスに設定されていません。System.Data.SqlClient.SqlDataReader.ReadColumnHeader (Int32 i) で

したがって、いくつかの内部グリッチが発生します。また、交換すると次のようになります

While i_oDataReader.Read
  m_aFullIDList.Add(i_oDataReader.GetInt32(0))
End While

整数を扱う

While i_oDataReader.Read
   m_aFullIDList.Add(i_oDataReader(0))
End While

オブジェクトを扱う - コードは問題なく実行され、すべてのレコードを返します。

図に行きます。

4

2 に答える 2

4

基本的に、コメント (*) でつぶやいたように、問題はSqlDataRead、ストアド プロシージャ、または SQL にあるわけではありません。むしろ、List.Addリストを拡張して既存の 2^n 項目をコピーするために 2^(n+1) 項目に追加のメモリを割り当てることができないため、失敗しています。ほとんどの場合、n=19 (つまり 524289 アイテム) ですが、それ以上になることもあります。

これについて実行できる基本的なことが 3 つあります。

  1. 事前割り当て: お気づきのように、事前割り当てを行うことで、1.5 倍から 3 倍のアイテムを獲得できるはずです。これは、アイテムの数が事前にわかっている場合に最適に機能するため、SELECT COUNT(*)..事前に実行するか、列を追加してCOUNT(*) OVER(PARTITION BY 1)返された最初の行から選択して、リストを事前に割り当てることをお勧めします。このアプローチの問題は、まだ限界にかなり近づいており、近い将来に簡単にメモリ不足になる可能性があることです...

  2. 再構成: 理論的には 2^29 ~ 2^30 程度のメモリを取得できるはずですが、現時点では、このために最大で 2^22 バイトのメモリしか取得していません。これは、マシン上の何かが、書き込み可能な仮想メモリの制限をそれほど高くすることを妨げていることを意味します。ページファイルのサイズや他のプロセスとの競合などが原因として考えられます (ただし、他の可能性もあります)。これを修正すれば、これに十分な余裕があるはずです。

  3. ストリーミング: メモリ内の 150 万個のアイテムすべてを同時に必要とすることは本当にありますか? そうでない場合、不要なものをその場で判断 (または必要な情報を抽出) できる場合SqlDataReaderは、ストリーミングを使用して、同じ方法でこの問題を解決できます。行を読んで使用し、それを失って次の行に進むだけです。

うまくいけば、これが役に立ちます。

(* -- 明らかに、@granadaCoder と @MartinSmith に感謝します)


問題が List データ構造のみにあると本当に考えている場合 (メモリ不足ではなく)、List 構造の割り当て動作を回避する方法がいくつかあります。1 つの方法は、別の List クラスを (としてIList(of Integer)) 実装することです。

インターフェースを通しては List と同じように見えますが、ネストされた にデータを格納することにより、内部的には異なる割り当てスキームになりますList(of List(of Integer))。1000 項目ごとに新しい を作成し、List(of Integer)それを親のネストされたリストに追加してから、それを使用して次の 1000 項目を追加します。

以前にこれを提案しなかった理由は、事前割り当てと同様に、これによりメモリ制限に近づくことができる可能性があるためですが、それが問題である場合でも、最終的には不足する可能性があるためです (事前割り当てと同様)。この制限は、必要なアイテムの実際の数 (150 万) に近すぎるためです。

于 2013-08-27T19:03:25.367 に答える
0

基本的に、selectクエリI suggest you to add order by in your queryを使用してSqlDataReaderのすべてのレコードを読み取り、すべてのレコードを昇順でソートし、SqlDataReaderでも昇順で読み取ります。

more than 2 million records一意のID serialNo を持つデータベースから読み取った最後のプロジェクトでもこの問題に直面していますが、このレコードはafter 1000 recordsジャンプ先の順序で取得されず21, 00, 263th record、すべてのレコードが間違った順序で取得されます。

次に、(order by serialNo)このクエリを使用すると、問題が解決されました。選択クエリで注文するだけで、余分なことをする必要はありません。

これがお役に立てば幸いです。

于 2014-03-21T06:35:14.863 に答える