【问题标题】:How to speed up a COUNT of values grouped across an INNER JOIN of three large tables?如何加快跨三个大表的 INNER JOIN 分组的 COUNT 个值?
【发布时间】:2014-11-12 02:20:04
【问题描述】:

有人可以指点我在下面加快查询速度的一般原则吗?

我有一个工作查询,它​​汇总了按五列分组的“属性”值的计数。但是运行需要二十多分钟。

计数汇总在三个相关的“案例”数据表中,每个表有大约 500,000 行,它们使用“用户 ID”加上“案例编号”复合键进行链接。 (CaseNumbers 仅对每个用户唯一。)我使用的是 SQL Server 2005。

我的关键问题似乎是:

  1. 我需要在加入三个表之后进行“分组”,因为每个表都唯一地包含至少一个我要分组的列(因此建议讨论 herehere似乎不适用)。

  2. 我想要的结果集中的可能排列范围(五列范围的乘积)很大(约 200,000 种可能性)。

如果我限制我的“范围”,我能够更快地获得一个数量级的结果。因此,例如,我可以将此查询重新设计为一次检索一个月的“foreach”循环。但我更愿意设计一种基于集合的方法。

我为这个查询创建了一个类似的版本,没有临时表,另一个版本为每个“范围”值创建了一个小的临时表,结果速度同样慢。

最终,我想计算数据库中每个“案例”中“类别”乘以“属性”的排列总数,按月份和用户分组。每个“UserID”+“CaseNumber”都唯一地与一个月和一年相关联,并且可能与两个或三个“类别”或“属性”相关联,在这种情况下,我想计算属性 * 类别的每个排列。

结果集如下所示:

主键:

  • “CaseMaster”有一个针对“UserID”的复合主键,并且 “案件编号”。

  • “CaseCategory”有一个复合主键 “UserID”、“CaseNumber”和“CategoryID”。

  • “CaseProperty”有一个 针对“UserID”和“CaseNumber”和“OtherID”的复合主键 (不是 PropertyID)。

  • “CaseNumber”是“varchar”。其余的都是“char”。

这是我的草稿查询:

USE MyDB

-- Drop Temp Table if it Exists
IF OBJECT_ID('tempdb..#DataRange') IS NOT NULL
DROP TABLE #DataRange

SELECT [UserID]
    ,[Year]
    ,[Month]
    ,[CategoryID]
INTO #DataRange
FROM [MyDB].[dbo].[IndexTable]

-- Aggregate a COUNT of "property" values joined across three large "Case" tables.
SELECT range.[UserID] AS [UserID]
    ,range.[Year] AS [Year]
    ,range.[Month] AS [Month]
    ,range.[CategoryID]
    ,cp.[PropertyID]
    ,COUNT(cp.[PropertyID]) AS [PropertyCount]

FROM
(
    -- (1) Get the range of possible permutations.
    (SELECT [UserID]
        ,[Year]
        ,[Month]
        ,[CategoryID]
    FROM #DataRange) range

    -- (2) Join against Dates AND Categories in the "Case Master" AND "Case Category" tables.
    INNER JOIN
    (
        SELECT cm.[CaseNumber] AS [CaseNumber]
               ,cm.[UserID] AS [UserID]
               ,cm.[Year] AS [Year]
               ,cm.[Month] AS [Month]
               ,cc.[CategoryID] AS [CategoryID]
        FROM
            ((SELECT [CaseNumber]
                     ,[UserID]
                     ,(CASE WHEN value1 = 'A' THEN datepart(year, date1)
                       ELSE datepart(year, date2) END) AS Year,
                     ,(CASE WHEN value2 = 'B' THEN datepart(month, date1)
                       ELSE datepart(month, date2) END) AS Month     
            FROM [MyDB].[dbo].[CaseMaster]) cm

            INNER JOIN

            (SELECT [CaseNumber]
                   ,[UserID]
                   ,[CategoryID]
            FROM [MyDB].[dbo].[CaseCategory]) cc

            ON cm.UserID = cc.UserID AND cm.CaseNumber = cc.CaseNumber)

    ) case

    ON range.[UserID] = case.[UserID] AND range.[Year] = case.[IncYear]
        AND range.[Month] = case.[IncMonth] AND range.[WebCategoryID] = case.[WebCategoryID]


    -- (3) Join against a "Property" fields in the "Case Property" table.
    INNER JOIN
    (
        SELECT [CaseNumber]
           ,[UserID]
           ,[property1] AS [PropertyID]
        FROM [MyDB].[dbo].[CaseProperty]
    ) cp

    ON range.UserID = cp.UserID AND case.CaseNumber = cp.CaseNumber
    AND cp.[PropertyID] IN (SELECT [PropertyID] FROM [MyDB].[dbo].[PropertyTypes])

)
GROUP BY range.[UserID], range.[Year], range.[Month], range.[CategoryID], p.[PropertyID]

DROP TABLE #DataRange

GO

【问题讨论】:

  • 你能编辑你的问题并描述你想要做什么吗?样本数据和期望的结果很有帮助。
  • 临时表的用途是什么?你只提到一次。它不是 IndexTable 的子集。行数没有过滤或其他限制。主表上的任何索引都没有用。
  • 中间似乎有一堆悬空的别名。 idata 是什么?
  • 我已经更正了别名中的拼写错误并添加了一个示例结果集。
  • 临时表不是绝对必要的,但在测试期间,我使用它来针对较小的“范围”集进行测试查询。 (例如:查询一个月的数据比查询一年的速度快十二倍。)

标签: sql sql-server performance join


【解决方案1】:

指数。检查所有必要索引的查询计划。

如果已经到位,它太慢了,您需要更快,找到瓶颈并购买适当的硬件来修复它,或者找到另一种获取数据的方法(缓存在内存中等),尽管我认为这不适用于此查询。

因此,在某一点上,大数据分析需要昂贵的硬件来实现是有原因的。 Smae 原因我只是将 5tb SSD 插入我的数据库服务器。

也就是说,您可能在 tempdb 上遇到吞吐量瓶颈 - 并且喜欢 SSD 的出色 Raid 0 ;)

【讨论】:

  • 自发布以来的索引看起来不错。如果是非集群的(可以覆盖默认设置),请考虑将它们设为集群。
【解决方案2】:

给出一些假设:

  • 查询命中(聚合)几乎每个表中的每一行,并且
  • 这些表确实很大(连接三个表,每个表有 500k 行),并且
  • 查询频繁运行,或者查询在被调用时必须快速运行

那么您可能正在查看数据仓库(数据集市、报表)的情况。这背后的一些基本概念是:

  • 设计表以支持报告 (olap) 查询,而不是写入/更新 (oltp) 查询
  • 在指定的时间点(每天?每小时?)刷新仓库,加载自上次刷新以来添加到系统的所有数据。 (或者,每次都从头开始重新加载所有内容,但这并不理想)
  • 设计得当,报告查询可以非常快速地运行

如果您只是在处理太多数据,这样的查询根本无法快速运行——想想“一天结束”的报告,或者一夜之间运行的东西。这里仓库的好处是这些长时间运行的查询不会在常规事务上运行,因此您不会遇到锁定、阻塞或死锁的情况(只要您没有同时运行查询您正在尝试加载表。)此外,在运行查询时基础数据不会更改。

【讨论】:

  • 您的前两个假设是正确的。就频率而言,我只是计划每晚运行一次或根据需要刷新数据。我正在使用此查询来填充网站的报表。但是我需要针对类似的报告进行十次这样的查询,而且我担心每个半小时(或更长时间)仍然太慢,即使对于每晚的过程也是如此。
  • 我会说如果你有一个半小时的过程,那么过夜是运行它们的理想时间。那是我们做自己的时候。
  • 我完全同意。但如果我的一套流程需要六到八个小时,那将产生负面影响。如果有任何优化甚至可以将我的速度提高五到十倍,那将是一个巨大的好处。而且我还担心我可能需要设计 INNER JOIN 四个大表的流程。
  • 更有理由考虑使用仓库/报告解决方案。将所有数据加载到表中可能需要几个小时,但是——如果设计得当(没有人会说这很容易)——将最近一天的活动价值添加到集合中需要几分钟。读取一个大 Fact 表将比跨四个大表的四路连接更快。
  • 我会考虑更多。从理论上讲,所有数据都可以更新,包括前几年,因此检测总计数的变化(包括删除)将涉及我们当前跟踪更新的方式发生重大变化。
【解决方案3】:

索引确实是罪魁祸首,特别是“案例属性”表中复合主键中元素的顺序。

由于某种原因,最初创建了“案例属性”表(不是我!),其关键元素的顺序是“用户 ID”->“其他 ID”->“案例编号”而不是“用户 ID”->“案例编号” " -> "其他ID"。

在没有其他更改的情况下切换顺序,对于相同的范围集,我的查询从 16 分钟加快到 1 秒。

另外,在“Case Property”INNER JOIN 之后添加以下琐碎的“ON”子句,也将查询从 16 分钟加快到 11 秒,而无需更正键,即使该子句对结果集没有影响并简单地搜索所有可能值的范围。

AND cp.[OtherID] IN (SELECT [OtherID] FROM [MyDB].[dbo].[OtherIDLookupTable])

感谢 TomTom 和 Philip 提供有用的建议和见解!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-10-09
    • 2015-03-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-29
    相关资源
    最近更新 更多