2

我之前曾两次尝试为这个问题找到解决方案,但不幸的是,这些答案并没有提供永久修复,所以我在这里,再试一次。

我有一个 SQL Server 存储过程,它返回 150 万个整数 ID 的列表。我从 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)

然后我将该阅读器传递给类构造函数以构建通用列表(整数)。代码非常基本:

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+1) 项分配额外的内存来扩展列表并将现有的 2^n 项复制到其中。大多数时候你的 n=19(所以 524289 项),但有时它可能会更高。

对此,您可以做三件基本的事情:

  1. 预分配:正如您所发现的,通过预分配,您应该能够在任何地方获得 1.5 到 3 倍的项目。如果您提前知道您将拥有多少项目,这将最有效,因此我建议您SELECT COUNT(*)..提前执行 a,或者添加一COUNT(*) OVER(PARTITION BY 1)列并从返回的第一行中选择它以预先分配列表。这种方法的问题是你仍然非常接近你的极限,并且在不久的将来很容易耗尽内存......

  2. 重新配置:现在您最多只能获得 2^22 字节的内存,而理论上您应该能够获得大约 2^29-2^30。这意味着您机器上的某些东西正在阻止您将可写虚拟内存限制扩展到那么高。可能的原因包括页面文件的大小和来自其他进程的竞争(但还有其他可能性)。解决这个问题,你应该有足够的空间来解决这个问题。

  3. 流式传输:你真的需要同时在内存中的所有 150 万个项目吗?如果没有,并且您可以即时确定您不需要哪些(或提取您确实需要的信息),那么您可以SqlDataReader使用流式传输相同的方式解决此问题。只需阅读一行,使用它,然后丢失它并继续下一行。

希望这会有所帮助。

(* -- 很明显,感谢@granadaCoder 和@MartinSmith)


如果您真的认为问题仅在于 List 数据结构(而不​​是内存不足),那么还有其他一些方法可以解决 List 结构的分配行为。一种方法是实现一个替代 List 类(如IList(of Integer))。

通过接口,它看起来与 List 相同,但在内部它将具有不同的分配方案,通过将数据存储在嵌套的List(of List(of Integer)). 每 1000 个项目,它将创建一个新的List(of Integer),将其添加到父嵌套列表中,然后使用它来添加接下来的 1000 个项目。

我之前不建议这样做的原因是,就像预分配一样,这可能会让你更接近你的内存限制,但是,如果这是问题所在,你最终还是会用完(就像 pre -allocating),因为这个限制太接近您需要的实际项目数(150 万)。

于 2013-08-27T19:03:25.367 回答
0

基本上,您使用选择查询读取 SqlDataReader 中的I suggest you to add order by in your query所有记录,它按 Acceding 顺序对所有记录进行排序,并且它们还在 SqlDataReader 中按 acceding 顺序读取。

我在上一个项目中也遇到了这个问题,我more than 2 million records从具有唯一 ID serialNo 的数据库中读取,但是这些记录不是按照after 1000 records它跳转到的顺序出现的,21, 00, 263th record并且所有记录的顺序都是错误的。

然后我使用(order by serialNo)这个查询,我的问题就解决了,你不需要做任何额外的事情,只需在你的选择查询中下订单,它就会为你工作

我希望这对你有帮助。

于 2014-03-21T06:35:14.863 回答