构建大型 SQL 行集并在 .NET 中使用

Posted

技术标签:

【中文标题】构建大型 SQL 行集并在 .NET 中使用【英文标题】:Structuring large SQL rowset(s) and consuming in .NET 【发布时间】:2012-01-03 20:20:11 【问题描述】:

看看这个伪架构(请注意这是一个简化,所以请尽量不要对架构本身的“可取性”发表过多评论)。假设索引在 FK 上就位。

 TABLE Lookup (
     Lookup_ID int not null PK
     Name nvarchar(255) not null
 )

 TABLE Document (
     Document_ID int not null PK
     Previous_ID null FK REFERENCES Document(Document_ID)
 )

 TABLE Document_Lookup (
     Document_ID int not null FK REFERENCES Document(Document_ID)
     Lookup_ID int not null FK REFERENCES Lookup(Lookup_ID)
 )

卷:文档,400 万行,其中 90% 的 Previous_ID 字段值为空;查找,6000 行,附加到每个文档的平均查找 20 给 Document_Lookup 80 百万行。

现在在 .NET 服务中具有表示这样的查找行的结构:-

 struct Lookup
 
      public int ID;
      public string Name;
      public List<int> DocumentIDs;
 

并且查找行存储在Dictionary&lt;int, Lookup&gt; 中,其中键是查找ID。这里重要的一点是,该字典应包含至少一个文档引用 Lookup 的条目,即列表 DocumentIDs 应具有 Count > 0。

我的任务是有效地填充这本字典。所以简单的方法是:-

  SELECT dl.Lookup_ID, l.Name, dl.Document_ID
  FROM Document_Lookup dl
  INNER JOIN Lookup l ON l.Lookup_ID = dl.Lookup_ID
  INNER JOIN Document d ON d.Document_ID = dl.Lookup_ID
  WHERE d.Previous_ID IS NULL
  ORDER BY dl.Lookup_ID, dl.Document_ID

这可以用来相当有效地填充字典。

问题底层行集交付(TDS?)是否执行一些优化?在我看来,对数据进行反规范化的查询非常常见,因此字段值不会从一行更改为下一行的可能性很高,因此通过不发送没有的字段值来优化流是有意义的t 改变了。有谁知道这样的优化是否到位?(优化似乎不存在)。

我可以使用什么更复杂的查询来消除重复(我特别想重复名称值)?我听说过“嵌套行集”这样的东西,可以生成那种东西吗?性能会更好吗?如何在 .NET 中访问它?

我会执行两个查询;一个填充查找字典,然后填充字典列表。然后,我将添加代码以剔除未使用的 Lookup 整体。然而,想象一下我的预测错了,Lookup 最终有 100 万行,而任何文档实际引用了四分之一?

【问题讨论】:

重新查询 TDS 部分;不是 AFAIK,但您可以通过拉出 2M 的 same 字符串来测试它,而不是拉出 2M 的 不同 字符串(相同长度)。只是一个想法。 @Marc:哦,那将是我应该做的一个明智的测试。 我知道这无助于回答问题,但为什么需要将整个数据集检索到结构中?只获取现在需要的一小部分记录比快速获取全部记录要好得多。 @webturner:“会更理想”我知道这不是真的。我有一个专门的服务器做一些非常密集的事情。我知道一个事实,一旦建立了这个字典,我正在做的事情的性能大大优于使用 SQL 的等效项。没问题,我只是想确保在极少数情况下需要重新启动服务的及时启动时间。精心构建的问题是为了提供对特定问题的看法,而不是对我正在做的所有事情的概述。 @Marc:已经完成了我应该首先完成的测试,看来您是正确的。似乎没有这样的优化。 【参考方案1】:

只要名称在实践中比较短,可能就不需要优化。

最简单的优化是将其拆分为两个查询,一个获取名称,另一个获取 Document_ID 列表。 (如果可以更轻松地填充数据结构,则可以采用其他顺序)。

例子:

/*First get the name of the Lookup*/
select distinct dl.Lookup_ID, l.Name
FROM Document_Lookup dl 
INNER JOIN Lookup l ON l.Lookup_ID = dl.Lookup_ID 
INNER JOIN Document d ON d.Document_ID = dl.Lookup_ID 
WHERE d.Previous_ID IS NULL 
ORDER BY dl.Lookup_ID, dl.Document_ID 

/*Now get the list of Document_IDs for each*/
SELECT dl.Lookup_ID, dl.Document_ID 
FROM Document_Lookup dl 
INNER JOIN Lookup l ON l.Lookup_ID = dl.Lookup_ID 
INNER JOIN Document d ON d.Document_ID = dl.Lookup_ID 
WHERE d.Previous_ID IS NULL 
ORDER BY dl.Lookup_ID, dl.Document_ID 

您还可以使用各种技巧将它们按摩到一张桌子上,但我认为这些都不值得。

您正在考虑的分层行集是 MSDASHAPE OLEDB 提供程序。他们可以按照您的建议进行操作,但会限制您使用 OLEDB 提供程序的 SQL,这可能不是您想要的。

最后慎重考虑XML

例如:

select
  l.lookup_ID as "@l", 
  l.name as "@n",
  (
    select dl.Document_ID as "node()", ' ' as "node()" 
    from Document_Lookup dl where dl.lookup_ID = l.lookup_ID for xml path(''), type
  ) as "*"
  from Lookup l
  where l.lookup_ID in (select dl.lookup_ID from Document_Lookup dl)
  for xml path('dl')

返回:

<dl l="1" n="One">1 2 </dl>
<dl l="2" n="Two">2 </dl>

【讨论】:

【参考方案2】:

当您询问“嵌套行集”时,您指的是使用 DbDataReader.NextResult() 方法吗?

如果您的查询有两个“输出”(两个返回单独结果集的选择语句),您可以使用 DbDataReader.Next() 循环第一个,当返回“false”时,您可以调用 DbDataReader.NextResult()然后再次使用 DbDataReader.Next() 继续。

var reader = cmd.ExecuteReader();
  while(reader.Read())
    // load data
  

  if(reader.NextResult())
    while(reader.Read())
      // lookup record from first result

      // load data from second result
    
  

我经常这样做以减少类似情况下的重复数据,并且效果非常好:

SELECT * FROM tableA WHERE [condition]
SELECT * FROM tableB WHERE EXISTS (SELECT * FROM tableA WHERE [condition] AND tableB.FK = tableA.PK)

免责声明:我没有用你描述的那么大的结果集尝试过这个。

这样做的缺点是您需要使用哈希表或订单列表将第二个结果集映射到第一个结果集的方法。

【讨论】:

以上是关于构建大型 SQL 行集并在 .NET 中使用的主要内容,如果未能解决你的问题,请参考以下文章

PHP PDO 多选查询始终删除最后一个行集

SQL Server:分组重复行集

SQL 表变量 - 添加检查匹配行集是不是存在的约束

选择 PL/SQL 中两个非空列值之间的行集

SQL - 如何返回某些行与给定列表匹配的整个行集

JDBC高级特性事务并发控制和行集