使用 distinct 和 row_number 分页时的性能

Posted

技术标签:

【中文标题】使用 distinct 和 row_number 分页时的性能【英文标题】:Performance when using distinct and row_number pagination 【发布时间】:2018-08-15 05:32:24 【问题描述】:

我有一个类似这样的 SQL:

SELECT A,B,C,FUN(A) AS A FROM someTable

问题是FUN()是一个很慢的函数,所以如果someTable中有很多记录,就会有很大的性能问题。

如果我们使用分页,我们可以解决这个问题,我们像这样进行分页:

SELECT * FROM(
SELECT A,B,C,FUN(A), Row_number()OVER( ORDER BY B ASC) AS rownum FROM someTable
)T WHERE T.rownum >=1 AND T.rownum<20

在这个脚本中,FUN() 只会执行 20 次,所以性能还可以。 但是我们需要使用别名来排序,所以我们不能内联写rownum,必须移动到子查询或CTE,我们选择了CTE,它看起来像这样:

;WITH CTE AS (
   SELECT A,B AS alias,C,FUN(A) FROM someTable
)
SELECT * FROM(
SELECT *,Row_number()OVER( ORDER BY alias ASC) AS rownum FROM CTE
)T WHERE T.rownum >=1 AND T.rownum<20

到目前为止,我们一切顺利,我们通过分页来解决性能问题,我们解决了别名顺序问题,但不知何故我们需要在查询中添加DISTINCT

 ;WITH CTE AS (
       SELECT DISTINCT A,B AS alias,C,FUN(A) FROM someTable
    )
    SELECT * FROM(
    SELECT *,Row_number()OVER( ORDER BY alias ASC) AS rownum FROM CTE
    )T WHERE T.rownum >=1 AND T.rownum<20

此后,这条 SQL 的优化似乎消失了,FUN() 将执行多次 someTable 中的记录数,我们再次遇到性能问题。

基本上我们都卡在这里了,有什么建议吗?

【问题讨论】:

FUN() 是什么? FUN 是确定性的吗? (意味着每次获得相同的输入时它会返回相同的值) @Zohar Peled,不,它是动态的,实际上它可能是一个子查询,所以很难为它缓存完整的结果或地图,我们试过了。 【参考方案1】:

问题在于,为了获得不同的值,数据库引擎必须对所有被选中的记录运行fun(a) 函数。

如果您只在最终选择中执行fun(a)distinct 应该不会影响它,因此它应该只在最后 20 条记录上运行。

我已将您使用的派生表更改为另一个 cte(但这是个人喜好 - 在我看来,不要将派生表和 ctes 混合使用更整洁):

;WITH CTE1 AS (
    SELECT DISTINCT A,B AS alias,C
    FROM someTable
), 
CTE2 AS
(
    SELECT *, ROW_NUMBER() OVER(ORDER BY alias) As RowNum
    FROM CTE1
)

SELECT *, FUN(A)
FROM CTE2
WHERE RowNum >= 1 
AND RowNum < 20

请注意,由于 fun 函数不是确定性的,您可能会得到与原始查询不同的结果 - 因此在调整此解决方案之前先比较结果。

【讨论】:

是的,你的笔记是我关心的,这样FUN(A)字段不会被DISTINCT考虑,所以结果会不一样。 好吧,也许更好的办法是提高FUN 的性能 - 但这应该是另一个问题的主题。 我认为你是对的,只要数据库必须对所有记录执行该功能,我们就无能为力。我会尝试将函数字段替换为其他一些字段组合以达到相同的目的,然后在分页后运行函数,感谢您的帮助! 很高兴为您提供帮助 :-)

以上是关于使用 distinct 和 row_number 分页时的性能的主要内容,如果未能解决你的问题,请参考以下文章

详述 SQL 中的 distinct 和 row_number() over() 的区别及用法

Distinct vs row_number() - 使用相同条件的查询在oracle中给出不同的结果?

sql 查询与 Row_Number 不同

两个字段都相同的记录如何去重

Oracle 根据特定属性(列)去除重复数据

Postgresql 根据单列或几列分组去重row_number() over() partition by