【问题标题】:Selectivity estimation error on a simple query简单查询的选择性估计错误
【发布时间】:2018-12-20 13:29:22
【问题描述】:

让我们有一个像这样创建的简单表tt

WITH x AS (SELECT n FROM (VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) v(n)), t1 AS
(
  SELECT ones.n + 10 * tens.n + 100 * hundreds.n + 1000 * thousands.n + 10000 * tenthousands.n as id  
  FROM x ones,     x tens,      x hundreds,       x thousands,       x tenthousands,       x hundredthousands
)
SELECT  id,
        id % 100 groupby,
        row_number() over (partition by id % 100 order by id) orderby,
        row_number() over (partition by id % 100 order by id) / (id % 100 + 1) local_search
INTO tt
FROM t1

我有一个简单的问题 Q1:

select distinct g1.groupby,
        (select count(*) from tt g2 
         where local_search = 1 and g1.groupby = g2.groupby) as orderby
from tt g1
option(maxdop 1)

我想知道为什么 SQL Server 对第一季度的结果大小估计如此糟糕(请参阅打印屏幕)。查询计划中的大多数运算符都是精确估计的,但是,在根哈希匹配运算符引入了完全疯狂的猜测。

为了让它更有趣,我尝试了对 Q1 的不同重写。如果我应用子查询的去相关,我会得到一个等效的查询 Q2:

select main.groupby, 
       coalesce(sub1.orderby,0) orderby
from
(
    select distinct g1.groupby
    from tt g1
) main
left join
(
    select groupby, count(*) orderby
    from tt g2 
    where local_search = 1
    group by groupby
) sub1 on sub1.groupby = main.groupby
option(maxdop 1)

这个查询在两个方面很有趣:(1)估计准确(见打印屏幕),(2)它也有不同的查询计划,比第一季度的查询计划效率更高。

所以问题是:为什么 Q1 的估计是不正确的,而 Q2 的估计是精确的? 请不要发布此 SQL 的其他重写(我知道即使没有子查询),我只对选择性估计器行为的解释感兴趣。谢谢。

【问题讨论】:

  • 您在第二个查询中获得更有效计划的原因是您执行DISTINCT 操作时。在第一个中,SQL Server 将首先评估所有行的子查询,然后删除重复项,在第二个中,首先删除重复项,然后每个 groupby 的值对子查询进行一次评估。如果您在第一个查询中使用GROUP BY groupby 并删除DISTINCT,那么您将获得与第二个查询相同的计划。
  • @GarethD 嘿,SQL 是声明性语言,对吧? ;)
  • 另外一个有趣的事情是 PostgreSQL 可以精确地估计结果,但是它的查询计划很糟糕。 Oracle 也正确估计了它。

标签: sql sql-server sql-server-2016 estimation


【解决方案1】:

它无法识别orderby 值对于具有相同groupby 的所有行都是相同的,因此它认为distinct groupby, orderby 将具有比distinct groupby 更多的组合。

它将DISTINCT orderby(对我来说是35.0367)的估计值和DISTINCT groupby(对我来说是100)的估计值相乘,好像它们不相关。

我得到了第一季度根节点3503.67 的估计

此重写避免了它,因为它现在仅按单个 groupby 列分组。

SELECT groupby,
       max(orderby) AS orderby
FROM   (SELECT g1.groupby,
               (SELECT count(*)
                FROM   tt g2
                WHERE  local_search = 1
                       AND g1.groupby = g2.groupby) AS orderby
        FROM   tt g1) d
GROUP  BY groupby
OPTION(maxdop 1) 

这不是此查询的最佳方法,但正如您的 Q2 和评论 @GarethD 所表明的那样,多次运行相关子查询并丢弃重复项的效率低下。

【讨论】:

    猜你喜欢
    • 2015-06-09
    • 2012-09-16
    • 2018-12-26
    • 2018-03-20
    • 2016-04-13
    • 2012-10-03
    • 1970-01-01
    • 1970-01-01
    • 2019-05-24
    相关资源
    最近更新 更多