【问题标题】:How do SQL aggregate functions workSQL 聚合函数如何工作
【发布时间】:2013-02-26 05:44:35
【问题描述】:

谁能告诉我聚合函数是如何在 SQL 数据库中实现的,例如:Oracle 或 SQL Server。

我的意思是,当 select 子句中存在聚合函数时,这些数据库是否使用一些内部数据结构或算法。

我问这个的原因是因为我在 java ArrayList 中有 100,000 条记录,当我尝试对所有值求和时大约需要 1 分钟,但是当相同的 100,000 条记录存储在 DB 中并且我使用 sum( column_nm) 它的执行时间几乎是 1/4。

我想以类似的方式提高我的 java 代码性能,为此我想了解 SQL 聚合函数的内部结构。

谢谢。

【问题讨论】:

  • 在一个 ArrayList 中总结一百万条记录的一分钟听起来可怕慢。您是否包括首先从数据库中获取所有记录所需的时间?
  • 这是十万(十万)条记录,而不是一百万。
  • 对不起,我的意思是 10,00,000 条记录,没有任何数据库操作,但我确实有一些其他的内存操作。
  • 你的零还在。没关系,但我们明白了:P。所以.. 一百万条记录?
  • 数据库可能是在本机代码中实现的,而您的ArrayList 代码可能在“客户端”VM 中运行,尤其是在 IDE 中。如果将其全部加载到内存中并在“服务器”虚拟机中运行,它甚至可能会胜过数据库。

标签: java sql sql-server oracle aggregate-functions


【解决方案1】:

虽然这与内部定义的聚合的工作方式不完全匹配,但在 SQL Server 中,您可以创建 user-defined aggregates。看看这样的聚合必须定义哪些方法可能会很有启发性:

  • Init:

查询处理器使用此方法来初始化聚合的计算。对于查询处理器正在聚合的每个组,都会调用一次此方法。查询处理器可以选择重用聚合类的相同实例来计算多个组的聚合。 Init 方法应根据此实例的先前使用执行任何必要的清理,并使其能够重新启动新的聚合计算。

  • Accumulate:

...查询处理器使用此方法来累积聚合值。对于正在聚合的组中的每个值,都会调用一次。只有在聚合类的给定实例上调用 Init 方法后,查询处理器才会调用它。该方法的实现应该更新实例的状态,以反映传入的参数值的累积。

  • Merge:

此方法可用于将此聚合类的另一个实例与当前实例合并。查询处理器使用这种方法来合并聚合的多个部分计算。

  • Terminate:

该方法完成聚合计算并返回聚合结果。 ...

MergeTerminate 的描述中,我们可以推断服务器可能在单个组内并行执行多个部分聚合。一旦发生了这些并行累积中的每一个,所有结果将在最后调用 Terminate 之前在类的一个实例上生成最终聚合结果之前一起 Merged。

因此,实现加速(如果可能)的一种明显方法是并行化累积阶段。

【讨论】:

    【解决方案2】:

    有一个非常简单的解释,为什么java代码慢很多:

    您正在使用 ArrayList,因此我假设您将 Integer-Objects 放在那里。在某些堆栈中,它们确实比 C 中的 int 有很大的开销。 其次,当您将它们相加并为每个部分和创建另一个 Integer 时,您的 GarbageCollector 会吃掉所有性能。

    如其他答案所述,

    1. DB 将使用直接数学处理器访问来仅在寄存器中添加整数 - 再快不过了。
    2. 良好的数据库不会只迭代,而是映射 + 减少总和、最小值或最大值等聚合。因此,它们获得了多处理器的好处,几乎忽略了 I/O 延迟。

    为了你在代码中解决它:使用 int[]

     int[] parts;
     sum=0;
     for (int i:parts) {
       sum+=i;
     }
    

    您可能想要测试,如果根据您的处理器数量拆分(映射)数组并将其与 Future 并行化是否有用 - 取决于您的数据大小。

    【讨论】:

      【解决方案3】:

      性能差异仅仅是因为要计算 SUM,您不需要将所有数据同时存储在内存中。

      当您直接向数据库发出请求 SUM 的查询时,它可以从磁盘读取每条记录,将运行总计累积到内存中的单个变量中,然后读取下一条记录 - 它永远不需要保留所有记录同时在记忆中。更重要的是,它不需要通过网络将这些记录发送到任何其他服务器进行处理 - 它只需要在最后将生成的 SUM 作为单个数字发送。

      此外,由于整体上的 SUM 等于整体的任何不同子集的 SUM,因此 SUM 可以并行化 - 例如如果数据是分区的,数据库可以发出多个查询以在不同的会话中运行,每个查询都会对其部分数据进行求和,然后控制会话可以简单地对每个分区的结果求和。

      当您在 Java 程序中使用 Array 计算总和时,它必须首先向数据库发出查询,询问它需要的所有数据;所有数据都需要从数据库传输到应用服务器,并且需要分配内存来存储所有数据。只有在那之后,您的程序才会遍历内存中的数组并计算总和;那么,它可能需要从内存中释放 Array。

      如果数据量低,性能差异可能微不足道。但是,如果数量很大,则可以预期差异会非常显着。

      【讨论】:

        【解决方案4】:

        聚合通常只是对结果集进行迭代,然后它们会执行聚合,无论是求和、平均还是计数等。

        如果您谈论的是操作的复杂性,它几乎总是 O(n),其中 n 是您的结果集中的记录数,用于简单的聚合。

        我不明白为什么在 java 中需要更长的时间,因为您的数组将被实例化到主内存中,这比从磁盘读取要快,就像 RDBMS 那样。老实说,来自 RDBMS 的聚合应该比 arraylist 聚合稍慢。

        为了扩展这一点,如果您想要一个特定条目(带有 PK 或索引)的一行,对于一个数组列表来说它是 O(1),对于一个具有适当索引的 RDBMS 来说是 O(1)(对于一个标准的链表,获取该行将是 o(n),但与聚合的 arraylist 相同)。遍历整个数据集(无论是数组还是表),然后执行聚合几乎总是 O(n)。

        【讨论】:

          【解决方案5】:

          有趣的问题。

          精心编写的 rdbms 是博士数学家和数据库专家数千小时工作的结晶。您模仿 MSSQL 或 postgressql 的性能的尝试令人钦佩,但在风车上倾斜(如果您不熟悉唐吉诃德,则阅读是徒劳的)。

          对 rdbms 的一个常见误解是关系意味着相关表。相关实际上是指数学关系。基本上 - rdbms 专注于集合论。即使有很好的 rdbms,开发人员也可以通过逐行计算来破坏性能,而不是使用固有的原生集合。这实际上是对您所遇到的性能差异的恰当比较。

          如果您仅限于在 java 而不是 db 中执行此计算,您应该考虑优化数据结构(最小数据类型)和循环效率。你仍然无法与 sql server 或 postgres 竞争。如果您确实需要改进的性能,可能值得将项目存储在数据库中并从 java 调用它们。

          【讨论】:

            猜你喜欢
            • 2017-01-29
            • 2014-09-08
            • 1970-01-01
            • 1970-01-01
            • 2013-08-14
            • 2016-04-14
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多