【问题标题】:how to structure an index for group by in Sql Server如何在 Sql Server 中为 group by 构建索引
【发布时间】:2010-12-12 23:51:46
【问题描述】:

以下简单查询需要很长时间(几分钟)才能执行。

我有一个索引:

create index IX on [fctWMAUA] (SourceSystemKey, AsAtDateKey)
SELECT MAX([t0].[AsAtDateKey]) AS [Date], [t0].[SourceSystemKey] AS [SourceSystem]
FROM [fctWMAUA] (NOLOCK) AS [t0]
WHERE SourceSystemKey in (1,2,3,4,5,6,7,8,9)
GROUP BY [t0].[SourceSystemKey]

统计如下:

  • 逻辑读取 1827978
  • 物理读取 1113
  • 提前阅读 1806459

采用完全相同的查询并将其重新格式化,如下所示:

  • 逻辑读取 36
  • 物理读取 0
  • 预读 0

执行需要 31 毫秒。

SELECT MAX([t0].[AsAtDateKey]) AS [Date], [t0].[SourceSystemKey] AS [SourceSystem]
 FROM [fctWMAUA] (NOLOCK) AS [t0]
 WHERE SourceSystemKey = 1
 GROUP BY [t0].[SourceSystemKey]
UNION
 SELECT MAX([t0].[AsAtDateKey]) AS [Date], [t0].[SourceSystemKey] AS [SourceSystem]
 FROM [fctWMAUA] (NOLOCK) AS [t0]
 WHERE SourceSystemKey = 2
 GROUP BY [t0].[SourceSystemKey]
UNION
 SELECT MAX([t0].[AsAtDateKey]) AS [Date], [t0].[SourceSystemKey] AS [SourceSystem]
 FROM [fctWMAUA] (NOLOCK) AS [t0]
 WHERE SourceSystemKey = 3
 GROUP BY [t0].[SourceSystemKey]
/* AND SO ON TO 9 */

如何创建一个快速分组的索引?

【问题讨论】:

  • 你有 SourceSystemKey 的索引吗?如果没有,我认为您可能会引发全表扫描。
  • showplan 显示什么? SourceSystemKey 可以取什么值?

标签: sql sql-server indexing group-by


【解决方案1】:

不看执行计划就很难说,但是您可能想尝试以下方法:

SELECT * FROM
(
    SELECT MAX(t0.AsAtDateKey) AS [Date], t0.SourceSystemKey AS SourceSystem
    FROM fctWMAUA (NOLOCK) AS t0
    GROUP BY t0.SourceSystemKey
)
WHERE SourceSystem in (1,2,3,4,5,6,7,8,9)

不看执行计划很难判断,但我认为发生的事情是 SQL 服务器不够聪明,无法意识到指定的 WHERE 子句过滤掉了组,并且对记录没有任何影响包含在每个组中。一旦 SQL Server 意识到这一点,它就可以免费使用一些更智能的索引查找来计算最大值(这是您的第二个查询中发生的事情)

只是一个理论,但可能值得一试。

【讨论】:

    【解决方案2】:

    我发现最好的解决方案如下。 它模仿联合版本的查询,运行速度非常快。

    40 次逻辑读取,执行时间为 3ms。

    SELECT [t3].[value]
    FROM [dimSourceSystem] AS [t0]
    OUTER APPLY (
        SELECT MAX([t2].[value]) AS [value]
        FROM (
            SELECT [t1].[AsAtDateKey] AS [value], [t1].[SourceSystemKey]
            FROM [fctWMAUA] AS [t1]
            ) AS [t2]
        WHERE [t2].[SourceSystemKey] = ([t0].[SourceSystemKey])
        ) AS [t3]
    

    【讨论】:

      【解决方案3】:

      使用 HAVING 而不是 WHERE,以便在发生分组后进行过滤:

      SELECT MAX(AsAtDateKey) AS [Date], SourceSystemKey AS SourceSystem
      FROM fctWMAUA (NOLOCK)
      GROUP BY SourceSystemKey
      HAVING SourceSystemKey in (1,2,3,4,5,6,7,8,9)
      

      我也不特别关心 IN 子句,尤其是当它可以替换为“

      【讨论】:

        【解决方案4】:

        尝试告诉 SQL Server 使用索引:

        ...
        FROM [fctWMAUA] (NOLOCK, INDEX(IX)) AS [t0]
        ...
        

        确保表格的统计信息是最新的:

        UPDATE STATISTICS [fctWMAUA]
        

        要获得更好的答案,请为两个查询打开显示计划:

        SET SHOWPLAN_TEXT ON
        

        并将结果添加到您的问题中。

        您也可以编写不带 GROUP BY 的查询。例如,您可以使用排他性 LEFT JOIN 排除日期较早的行:

        select cur.SourceSystemKey, cur.date
        from fctWMAUA cur
        left join fctWMAUA next
            on next.SourceSystemKey = next.SourceSystemKey
            and next.date > cur.date
        where next.SourceSystemKey is null
        and cur.SourceSystemKey in (1,2,3,4,5,6,7,8,9)
        

        这可能会快得惊人,但我认为它无法击败 UNION。

        【讨论】:

        • 尝试了你所有的建议。还是很慢。联盟还是很快的。
           |--Stream Aggregate(GROUP BY:([t0].[SourceSystemKey]) DEFINE:([Expr1003]=MAX([partialagg1004]))) |--Parallelism(Gather Streams, ORDER BY:([t0 ].[SourceSystemKey] ASC)) |--Stream Aggregate(GROUP BY:([t0].[SourceSystemKey]) DEFINE:([partialagg1004]=MAX([KITE].[dbo].[fctWMAUA].[AsAtDateKey] as [t0].[AsAtDateKey]))) |--Index Seek(OBJECT:([KITE].[dbo].[fctWMAUA].[IX_AsAtDateSourceSystem] AS [t0]), SEEK:([t0].[SourceSystemKey ] >= (1) AND [t0].[SourceSystemKey] 
        • 我还对索引中的字段进行了重新排序,它没有改变。
        • 看看这个计划是有道理的。最初的搜索将找到所有记录。只有九个源系统,它正在寻找很多。
        • 在查询末尾添加“OPTION (HASH GROUP)”或“OPTION (ORDER GROUP)”有什么不同吗?
        • 嗨,Andomar,好建议。哈希组已将其缩短到大约 15 秒,如果我缓存结果,这是可以接受的。仍然很奇怪,我可以从联合版本中获得 32 毫秒,而按版本分组却没有。 union 版本对每个查询执行一个 seek 和一个 top(1),速度非常快。索引似乎无法复制。
        【解决方案5】:
         WHERE SourceSystemKey = 3
         GROUP BY [t0].[SourceSystemKey]
        

        您不需要按固定字段进行分组。

        无论如何我更喜欢第一句话。可能我会替换

         WHERE SourceSystemKey in (1,2,3,4,5,6,7,8,9)
        

        类似的东西

         WHERE SourceSystemKey BETWEEN 1 AND 9
        

         WHERE SourceSystemKey >= 1 AND SourceSystemKey <= 9
        

        如果 SourceSystemKey 是整数。但我不认为它会引起大的变化。

        我将首先测试的是重建统计信息并重建表的所有索引并等待一段时间。重建不是即时的,它取决于服务器的繁忙程度,但这句话结构良好,适合优化器使用的索引。

        问候。

        【讨论】:

        • “您不需要按固定字段分组”是什么意思?他正在寻找最长的约会日期。
        • 我尝试了两者之间并没有改变任何东西。它正在使用索引,初始索引查找返回 6.65 亿行。使用联合,它会为每个正确排序的最大值寻找一行(前 1 行),并且它的速度非常快。如果没有联合,它会查找 6.65 亿行并迭代该批次。这很疯狂。两个查询在计划中肯定使用相同的索引。
        • Andomar:我谈到了 GROUP BY,因为如果你输入“WHERE SourceSystemKey = 3”,我看不出“GROUP BY SourceSystemKey”没有任何意义,因为只有一个 SourceSystemKey。没有要分组的内容,您正在寻找通过 WHERE 过滤器的绝对 MAX 值。但是优化器知道它的任何方式都不应该成为问题。 (编辑:谈论第二个命令。显然,在第一种情况下 GROUP BY 是可以的)
        • @j.a.estevan:SQL Server 需要 GROUP BY 才能让您使用 MAX() 等聚合
        • 在这种情况下不需要它。通常,如果您不需要对数据进行分组,则不需要。只需尝试(例如): select max(object_id) from sys.tables where name like '%A%' 这在 SQL Server 2005 中运行良好。
        【解决方案6】:

        您是否尝试过仅在 SourceSystemKey 列上创建另一个索引?当您在 where 子句中使用该列时,大量的逻辑读取让我认为它正在执行索引/表扫描。你能在这上面运行执行计划,看看是不是这样吗?执行计划也可能会提出索引建议。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2010-11-29
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2022-06-15
          • 2017-01-03
          相关资源
          最近更新 更多