是否可以索引运行总计以加快窗口功能?

Posted

技术标签:

【中文标题】是否可以索引运行总计以加快窗口功能?【英文标题】:Is it possible to index a running total to speed up window functions? 【发布时间】:2019-10-17 04:47:04 【问题描述】:

我要执行:

    SELECT cust_id, SUM(cost)
    FROM purchases
    WHERE purchase_time BETWEEN('2018-01-01', '2019-01-01')
    GROUP BY cust_id

但是对于大量行,我希望必须加载每条记录以聚合适当的 SUM。我想要做的是有一个像这样的索引:

    CREATE INDEX purchase_sum 
    ON purchases(cust_id, purchase_time, 
    SUM(cost) OVER (PARTITION BY cust_id 
    ORDER BY purchase_time) AS rolling_cost)

我想要一个看起来像这样的索引:

 cust_id    time    rolling_cost
--------   ------  --------------
   1        Jan 1       5
   1        Jan 2       12
   1        Jan 3       14
   1        Jan 4       20
   1        Jan 5       24
   2        Jan 1       1
   2        Jan 2       7
   2        Jan 3       11
   2        Jan 4       14
   2        Jan 5       19
   3        Jan 1       4
   3        Jan 2       5
   3        Jan 3       10
   3        Jan 4       21
   3        Jan 5       45

据此,我的原始查询可以通过简单地为每个 cust_id 减去 2 个已知值来计算,大致为 cost_in_window = rolling_cost('2019-01-01') - rolling_cost('2018-01-01'),这不需要从源表中加载任何内容。

这可以作为索引吗?还是有其他方法可以实现相同的目标?

【问题讨论】:

an index that looks like ...索引是一种数据结构,例如B树。这不是一张桌子。 ...考虑跳到支持本机Materialized view的RDMS,这就是您在这里要问的问题。SQL Server支持它WITH SCHEMABINDING,其索引意味着物化视图是自动的当数据发生变化时更新,或多或少与触发器如何做到这一点相同.. 【参考方案1】:

您可能会发现这样更快:

select c.cust_id,
       (select sum(p.cost)
        from purchases p
        where p.cust_id = c.cust_id and
              p.purchase_time >= '2018-01-01' and
              p.purchase_time < '2019-01-01' and
       ) as total_cost
from customers c
having total_cost is not null;

然后,这可以使用purchases(cust_id, purchase_time, cost) 上的索引。 只有索引是计算金额所需要的。那是一种节省。更重要的是,没有整体聚合——这可以节省更多,弥补对所有客户的计算。

但是,如果使用相同的索引,这可能会更好一些:

select c.cust_id,
       (select sum(p.cost)
        from purchases p
        where p.cust_id = c.cust_id and
              p.purchase_time >= '2018-01-01' and
              p.purchase_time < '2019-01-01'
       ) as total_cost
from customers c
where exists (select 1
              from purchases p
              where p.cust_id = c.cust_id and
                    p.purchase_time >= '2018-01-01' and
                    p.purchase_time < '2019-01-01' 
             );

编辑:

实现所需功能的唯一方法是在数据中显式包含累积总和列。这将需要改写查询(进行所需的减法)并使用触发器来维护该值。

如果历史数据从不改变,这可能是一种合理的方法。但是,更新或插入较早的行可能会变得非常昂贵。

【讨论】:

这当然比未索引的方法要好,但仍然没有达到我想要的效果。没有 GROUP BY 的相同想法:想象 1B 条购买记录,并且您想要今年的总和,可能有 100M 条这样的记录。即使使用您建议的索引,也会发生 100M 的添加。不是很好。但是,如果我保持一个滚动总数(即,foreach 记录,存储从开始到该记录时间的所有记录的总和),我可以在 2*logn 时间内计算它。鉴于没有人说这是可能的,我想我可以推断它不是:) " 想象一下 1B 条购买记录,而您想要今年的总和,可能有 1 亿条这样的记录" @Rollie 那将是雇用 DBA 的好时机.. SQL 语言的 Big O 概念也毫无意义,因为 SQL 本质上是声明性的,您可以在其中定义想要拥有的内容而不是如何获取它。在内部,优化器可以优化并将此 SQL 重写为其他 SQL 如果您是否会使用EXPLAIN [FORMAT=JSON] &lt;query&gt; 来获得良好的性能指示?不要自己计算或思考引擎盖下可能发生的事情..【参考方案2】:

这是一个可能对您的查询有所帮助的索引:

CREATE INDEX idx ON purchases (purchase_time, cust_id, cost);

这至少应该让 mysql 丢弃所有不符合购买时间范围的记录。然后,该索引还涵盖了cust_idcost 列,这意味着MySQL 只需执行一次索引扫描即可计算每组客户记录的成本总和。

【讨论】:

这是一个改进,但不是我在这个问题中想要做的。在人为设计的示例中,它运行良好,但如果数据集更大(可能有 100 万个独特的客户,每天有 100 次购买),差异会更加明显。 我回答了您问题的第一部分,即调整该查询。如果您需要有关解决方法的帮助,我不明白,因此您可能需要更好地解释。是的——使用触发器跟踪滚动总和实际上是避免运行第一个查询的一种方法。 我的问题是“是否可以索引运行总计以加快窗口功能?”,如标题所示;不是“索引可以使这个查询更快吗?”触发方法是一个更适用的答案,但据我所知,它需要查询应用程序知道生成的字段,而不是让查询优化器在聚合查询中自动使用它。 你没有回答我的问题。请更好地解释您使用窗口函数(我无法想象)的解决方法背后的逻辑。你 d @Rollie 我认为你上面的回答很粗鲁。您的问题是双重的:“这是否可以作为索引?” (从中我们可以推断出答案是否定的)和“或者是否有另一种方法可以实现相同的目标?” TB 慷慨地试图回答这个问题。

以上是关于是否可以索引运行总计以加快窗口功能?的主要内容,如果未能解决你的问题,请参考以下文章

SQL窗口函数和运行总计

计算值在分区上更改时的运行总计

在 Spark 数据集中创建具有运行总计的列

双连接查询需要 540 秒才能运行 - 我怎样才能加快速度?

使用两列运行总计

怎样加快电脑运行速度