【问题标题】:Effectively COUNTing and SUMing on a large inner join有效地对大型内部联接进行计数和求和
【发布时间】:2012-10-27 22:53:13
【问题描述】:

请考虑以下情况(为了问题而简化):

我在 SQL Server 2012 数据库中有以下表:

Parent_Table

Id  | Parent table fields
----+--------------------
1   | ...
2   | ...
3   | ...
...

Child_Table

Id  | ParentId | Child table fields
----+----------+-------------------
1   | 2        | ...
2   | 1        | ...
3   | 1        | ...
4   | 3        | ...
5   | 2        | ...
...

Big_Table

Id      | ChildId  | Value  | Status  | Other fields
--------+----------+--------|---------------------
1       | 12       | 672    | Closed  |
2       | 23       | 133    | Closed  |
3       | 7        | 2611   | Open    |
4       | 14       | 84     | Closed  |
...
1295769 | 23       | 458    | Closed  |
1295770 | 18       | 1046   | Open    |
1295771 | 7        | 8      | Open    |

子表和父表相对较小(每个父表大约有 100 个父条目和 5 个子条目),并且它们的条目每天只插入或删除几次。

另一方面,“大表”正在快速增长(为了讨论,每秒 100 个条目),并且行的状态在一段时间后变为 Closed(想想客户端会话,这实际上是这里的情况)。

我需要定期(每隔几秒)提供 Big_Table 行数和 Big_Table.Value 列的总和指定的 Parent.Id - 每次都不同。

我怀疑直接实现(使用内部连接等)可能效率极低,更好的解决方案可能包括附加表、某种计数器表,或者我应该在我的服务代码中实现它(?!)并以某种方式处理持久性。

实现上述内容的“正确”(效率方面)方式是什么?处理额外级别的父母/孩子的解决方案将是最好的解决方案。

【问题讨论】:

  • 确保表上有正确的索引,也许对它进行分区,然后使用连接。如果您的数据库设计正确,SQL 可以轻松处理大量行。
  • @cadrell0 - 显然索引是必须的(尽管插入时会产生额外费用),但我不确定在这种情况下是否足够。
  • 你考虑过索引视图吗?
  • @Max - 是的,我有,但我仍然认为它需要“太多”的 SQL 资源,而引擎将非常忙于处理其他请求。
  • 只要你在视图定义中使用 NOLOCK,我不会太在意阻塞。 SQL 优化器很有可能会意识到您在做什么并帮助您(缓存结果等)。我建议尝试一下并查看执行计划和资源利用率。如果查询确实太昂贵,请向业务层添加一些缓存逻辑。但请记住,您必须处理缓存失效、保持缓存与数据库同步等问题。

标签: sql-server tsql inner-join


【解决方案1】:

如果我们假设 ChildId 始终保留相同的 ParentId,那么要考虑的一种选择是将 ParentId 添加到 Big_Table。如果您确定某些行只会从您控制的应用程序代码中添加到这些表中(例如,与具有数据库连接的人自己运行 INSERT 语句相反),您可以在内存中保留父表和子表的缓存,并且插入 Big_Table 时提供 ChildId 到 ParentId 的快速映射。然后您的总和/计数查询将完全在 Big_Table 上完成。这种非规范化当然会增加 Big_Table 的大小,但如果这种情况和应用程序更改是可以容忍的,这是一个可行的选择。

此外,如果 ParentId 是访问 Big_Table 中的行的主要方式(或具有最大性能问题的方式),那么您可以考虑在 ParentId 上对 Big_Table 进行分区。

一种完全不同的方法是将您想要的统计信息直接存储在 Parent_Table 中,并使用 Big_Table 上的触发器来保持更新,或者,如果您在应用程序代码中控制所有数据库交互,则使用应用程序逻辑将统计信息更新为Big_Table 插入的一部分。您可以在事务中执行此操作以确保一致性,或者如果您可以容忍某些错误,则可以将其作为异步统计信息更新来完成,以便 Big_Table 上的插入可以快速发生,而统计信息在后台更新。

至于处理额外级别的父母和孩子,您可能需要在灵活性和效率之间做出选择。例如,您可能需要限制您愿意支持的树的深度,以便硬编码深度逻辑以提高性能。

【讨论】:

  • 第 1 段的两个假设都适用。所以你的建议:)
【解决方案2】:

我会在下面的 2 上做一个时间测试,如果它是合理的,你可以保持简单。我将由您来确定 with(nolock) 提示是否适合您的情况(数据库设置、数据的性质)。由于您知道父 ID,因此不需要涉及 Parent_Table。

显然Big_Table.ChildId和Child_Table.ParentId上应该有索引

select sum(Value)
from Big_Table with(nolock)
where ChildId in
(select Id
from Child_Table with(nolock)
where ParentId = @ParentId)

select sum(Value)
from Big_Table with(nolock)
inner join Child_Table with(nolock)
on  Big_Table.ChildId = Child_Table.Id
where Child_Table.ParentId = @ParentId

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-27
    • 2011-08-17
    • 1970-01-01
    • 1970-01-01
    • 2010-11-24
    • 2017-06-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多