视图中的 SUM(...) OVER (ORDER BY ...) 导致性能不佳

Posted

技术标签:

【中文标题】视图中的 SUM(...) OVER (ORDER BY ...) 导致性能不佳【英文标题】:SUM(…) OVER (ORDER BY …) in view causes poor performance 【发布时间】:2019-01-23 15:17:27 【问题描述】:

我已经定义了一个视图,其中列出了交易以及运行总计,类似于

CREATE VIEW historyView AS
    SELECT
        a.createdDate,
        a.value,
        m.memberId,
        SUM(a.value) OVER (ORDER BY a.createdDate) as runningTotal,
        ...many more columns...
        FROM allocations a
        JOIN member m ON m.id = a.memberId
        JOIN ...many joins...

此查询查看的最大表有大约 1000 万行,但平均而言,当查询视图时,它只会返回几十行。

我的问题是,当这个SELECT 语句直接为给定成员运行时,它执行得非常快,并在几毫秒内返回结果。但是,当作为视图查询时...

SELECT h.createdDate, h.value, h.runningTotal
    FROM historyView h
    WHERE member.username = 'blah@blah.com'

...表现很糟糕。这两个查询计划非常不同——在第一种情况下它非常理想,但在后一种情况下,有大量的扫描和数十万/数百万行被读取。这显然是因为对成员的过滤器是在完成其他所有操作之后运行的最后一件事,而不是一开始就在前面运行。

如果我删除SUM(x) OVER (ORDER BY y) 子句,这个问题就会消失。

我可以做些什么来确保SUM(x) OVER (ORDER BY y) 子句不会破坏查询计划?

【问题讨论】:

这就是优化视图的方式。这是一种耻辱。使用内联表值函数可能会更好。 太可惜了!不幸的是,针对这个视图的查询是由一些 OData 中间件生成的,这些中间件将对各种事情进行过滤,所以我不确定 TVF 是否可以工作 欢迎使用 SQL。 【参考方案1】:

我的问题的一个解决方案是让查询优化器知道在运行窗口函数之前通过 PARTITION'ing 过滤 是安全的。视图的变化是:

CREATE VIEW historyView AS
    SELECT
        a.createdDate,
        a.value,
        m.memberId,
        SUM(a.value) OVER (PARTITION BY m.username ORDER BY a.createdDate) as runningTotal,
        ...many more columns...
        FROM allocations a
        JOIN member m ON m.id = a.memberId
        JOIN ...many joins...

不幸的是,如果过滤我的会员的用户名是查询的一部分,这只会创建正确的计划。

【讨论】:

【参考方案2】:

那是因为m.username 上可能有一个索引。当涉及到查询调优时,它需要一些试验和错误。 使用窗口函数时,需要考虑“POC”索引的概念 - 只需在 google 上搜索即可(Itzik Ben-Gan 对此也有很好的参考)。 摘自《使用窗口函数的高性能 T-SQL》一书:

在没有 POC 索引的情况下,该计划包括一个排序迭代器,并且对于大型输入集,它可以相当 昂贵的。排序具有 N * LOG(N) 复杂度,比线性更差。这意味着随着更多 行,您每行支付更多费用。例如 1000 * LOG(1000) = 3000 和 10000 * LOG(10000) = 40000。这意味着 10 倍的行数会导致 13 倍的工作量,而且越往前越差。

这里有一个参考 link 开始使用窗口函数和索引。

【讨论】:

以上是关于视图中的 SUM(...) OVER (ORDER BY ...) 导致性能不佳的主要内容,如果未能解决你的问题,请参考以下文章

Oracle - SELECT DENSE_RANK OVER(ORDER BY、SUM、OVER 和 PARTITION BY)

Hive 问题 - Rank() OVER (PARTITION BY Dept ORDER BY sum(salary))

RESET SUM(AMT_FIELD) OVER(PARTITION BY UNIQUE FIELD ORDER BY ROWNUM)

分析函数系列之sum(col1) over(partition by col2 order by col3):实现分组汇总或递增汇总

sum over函数

在 OVER 子句中使用 ORDER BY