是否可以索引运行总计以加快窗口功能?
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] <query>
来获得良好的性能指示?不要自己计算或思考引擎盖下可能发生的事情..【参考方案2】:
这是一个可能对您的查询有所帮助的索引:
CREATE INDEX idx ON purchases (purchase_time, cust_id, cost);
这至少应该让 mysql 丢弃所有不符合购买时间范围的记录。然后,该索引还涵盖了cust_id
和cost
列,这意味着MySQL 只需执行一次索引扫描即可计算每组客户记录的成本总和。
【讨论】:
这是一个改进,但不是我在这个问题中想要做的。在人为设计的示例中,它运行良好,但如果数据集更大(可能有 100 万个独特的客户,每天有 100 次购买),差异会更加明显。 我回答了您问题的第一部分,即调整该查询。如果您需要有关解决方法的帮助,我不明白,因此您可能需要更好地解释。是的——使用触发器跟踪滚动总和实际上是避免运行第一个查询的一种方法。 我的问题是“是否可以索引运行总计以加快窗口功能?”,如标题所示;不是“索引可以使这个查询更快吗?”触发方法是一个更适用的答案,但据我所知,它需要查询应用程序知道生成的字段,而不是让查询优化器在聚合查询中自动使用它。 你没有回答我的问题。请更好地解释您使用窗口函数(我无法想象)的解决方法背后的逻辑。你 d @Rollie 我认为你上面的回答很粗鲁。您的问题是双重的:“这是否可以作为索引?” (从中我们可以推断出答案是否定的)和“或者是否有另一种方法可以实现相同的目标?” TB 慷慨地试图回答这个问题。以上是关于是否可以索引运行总计以加快窗口功能?的主要内容,如果未能解决你的问题,请参考以下文章