【问题标题】:How can I improve performance of average method in SQL?如何提高 SQL 中平均方法的性能?
【发布时间】:2010-12-15 12:03:25
【问题描述】:

我遇到了一些性能问题,其中计算列平均值的 SQL 查询随着记录数的增加而逐渐变慢。是否有可以添加到列中的索引类型以加快平均计算速度?

有问题的数据库是 PostgreSQL,我知道特定的索引类型可能不可用,但我也对理论上的答案感兴趣,如果没有某种缓存解决方案,这甚至是可能的。

更具体地说,有问题的数据本质上是具有这种定义的日志:

table log {
  int duration
  date time
  string event
}

我正在做类似的查询

SELECT average(duration) FROM log WHERE event = 'finished'; # gets average time to completion
SELECT average(duration) FROM log WHERE event = 'finished' and date > $yesterday; # average today

第二个总是相当快,因为​​它有一个更严格的 WHERE 子句,但总平均持续时间是导致问题的查询类型。我知道我可以缓存这些值,使用 OLAP 或其他东西,我的问题是天气有一种方法可以完全通过数据库端优化(例如索引)来做到这一点。

【问题讨论】:

    标签: sql performance postgresql


    【解决方案1】:

    记录越多,计算平均值的性能就会越慢,因为它总是需要使用结果中每条记录的值。

    如果索引包含的数据少于表本身,索引仍然可以提供帮助。为您想要平均值的字段创建索引通常没有帮助,因为您不想进行查找,您只想尽可能高效地获取所有数据。通常,您会将该字段作为输出字段添加到查询已使用的索引中。

    【讨论】:

    • 为什么投反对票?如果你不解释你认为错的地方是什么,就无法改进答案。
    • 我评论太慢了。我投了反对票,因为我觉得您的回答只是在解释典型的 b-tree 索引如何帮助过滤。所有查找列和我正在计算平均值的列都有索引,并且查找速度很快。由于平均值的数量,平均计算速度很慢。我正在寻找的是一个数据结构的名称,它创建一列的 b-tree 并存储与 b-tree 中给定节点匹配的行的另一列的总数,或者没有这样的东西可以合理的语句存在。
    • @Sindri Traustason:那么您希望的是一个可以在某些字段上分组的索引,并为每个组的另一个字段保留一个运行总计?我认为任何常规关系数据库都不支持类似的东西。对于您添加到问题中的示例,您将为字段eventdata 创建索引,并将duration 作为输出字段。这样查询就可以使用索引的输出进行平均计算。
    • 接受的答案在上面 Guffas 的第二条评论中。
    【解决方案2】:

    取决于你在做什么?如果您不过滤数据,那么除了按顺序排列聚集索引之外,数据库还如何计算列的平均值?

    有些系统会执行在线分析处理 (OLAP),这些系统会执行诸如保持运行总和和平均下来您希望检查的信息之类的事情。这完全取决于您在做什么以及您对“慢”的定义。

    例如,如果您有一个基于 Web 的程序,也许您可​​以每分钟生成一次平均值,然后将其缓存,一遍又一遍地将缓存的值提供给用户。

    【讨论】:

    • 你可以用索引视图做一些偷偷摸摸的事情,但我们需要一个更具体的例子来帮助你。
    • 在这种情况下,我对慢的定义是:已经明显变慢,并且会随着时间的推移而变慢。
    【解决方案3】:

    加速聚合通常是通过保留额外的表来完成的。

    假设有相当大的表 detail(id, dimA, dimB, dimC, value),如果您想让 AVG(或其他聚合函数)的性能几乎是恒定的时间,无论您可以引入新表的记录数如何

    dimAavg(dimA, avgValue)

    • 此表的大小将仅取决于 dimA 的不同值的数量(此外,此表在您的设计中可能有意义,因为它可以详细保存适用于 dimA 的值的域(以及与域值;您可能/应该已经有这样的表)
    • 此表仅在您仅通过 dimA 分析时才有用,一旦您需要根据 dimA 和 dimB 的 AVG(值),它就变得无用了。因此,您需要知道要对哪些属性进行快速分析。在多个属性上保持聚合所需的行数是 n(dimA) x n(dimB) x n(dimC) x ...,它可能增长很快,也可能不会增长。
    • 维护此表会增加更新(包括插入和删除)的成本,但您可以采用进一步的优化...

    例如,让我们假设系统主要执行插入操作,只是偶尔更新和删除。

    让我们进一步假设您只想通过 dimA 进行分析,并且 ids 正在增加。然后有结构如

    dimA_agg(dimA, Total, Count, LastID) 
    

    可以提供帮助,而不会对系统产生重大影响。

    这是因为您可能拥有不会在每次插入时触发的触发器,但可以说是在 100 次插入时触发。

    这样你仍然可以从这个表中得到准确的聚合带有

    的详细信息表
    SELECT a.dimA, (SUM(d.value)+MAX(a.Total))/(COUNT(d.id)+MAX(a.Count)) as avgDimA
    FROM details d INNER JOIN
         dimA_agg a ON a.dimA = d.dimA AND d.id > a.LastID 
    GROUP BY a.dimA
    

    上述具有适当索引的查询将从dimA_agg 中获取一行,而从detail 中仅获取不到100 行——这将在几乎恒定的时间内执行(~logfanoutn)并且会不需要每次插入都更新到dimA_agg(减少更新惩罚)。

    100 的值只是作为示例给出的,您应该自己找到最佳值(或者甚至保持可变,尽管在这种情况下仅触发器是不够的)。

    维护删除和更新必须在每个操作上触发,但您仍然可以检查要删除或更新的记录的 id 是否已经在统计信息中,以避免不必要的更新(将节省一些 I/O)。

    注意:分析是针对具有谨慎属性的域进行的;在处理时间序列时,情况会变得更加复杂——您必须确定要保留摘要的域的粒度。

    编辑

    还有materialized views23

    【讨论】:

    • 我很好奇:如何制作一个每触发一次仅触发一次的触发器(如您的示例)?
    • @DrColossos,严格来说它必须每次都触发,但你可以让触发器只在id % 100 = 0上做一些真正的工作
    • 序列保证没有孔,因此有孔是可能的,并且可能错过一轮(或多轮)(取决于你多久打电话给@987654334 @ 没有实际插入行),实际上这不会破坏系统,只会改变性能(平均而言,您应该能够找到适合您的目标插入数量)
    【解决方案4】:

    只是一个猜测,但索引不会有太大帮助,因为平均值必须读取所有记录(以任何顺序),索引对于查找行的子集很有用,如果您必须迭代所有没有特殊排序索引的行没有帮助...

    【讨论】:

      【解决方案5】:

      这可能不是您想要的,但如果您的表有某种方式对数据进行排序(例如按日期),那么您可以只进行增量计算并存储结果。

      例如,如果您的数据有一个日期列,您可以计算记录 1 - Date1 的平均值,然后将该批次的平均值与 Date1 和您平均的 #records 一起存储。下次计算时,将查询限制为结果 Date1..Date2,并添加记录数,并更新最后查询的日期。您拥有计算新平均值所需的所有信息。

      这样做时,在日期或您用于排序的任何列上有一个索引显然会很有帮助。

      【讨论】:

        猜你喜欢
        • 2022-11-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-04-28
        • 1970-01-01
        • 1970-01-01
        • 2018-09-03
        • 2014-01-04
        相关资源
        最近更新 更多