【问题标题】:Slow query with where clause使用 where 子句的慢查询
【发布时间】:2019-09-05 02:56:47
【问题描述】:

我有以下 sql 查询只需 1 秒即可执行:

select a.date, b.rate, c.type, a.value from

a inner join b on a.id = b.aid
c inner join b.id = c.bid
where a.name = 'xxx'

但是我需要一个结果集来获得比率大于 0 的结果。所以当我将查询更改为此需要 7 分钟来执行:

select a.date, b.rate, c.type, a.value from

a inner join b on a.id = b.aid
c inner join b.id = c.bid
where a.name = 'xxx' and b.rate>0

为什么这会使查询时间从 1 秒增加到 7 分钟?由于 b 表很大,我什至尝试使用 CTE,但这也没有提高性能。我认为使用 CTE 将有一组更小的值可供过滤,因此它应该更快,但这并没有帮助:

;with x as
(select a.date, b.rate, c.type, a.value from

a inner join b on a.id = b.aid
c inner join b.id = c.bid
where a.name = 'xxx')
select * from x where rate>0

我无法包含执行计划,因为除了查询之外我没有对 db 的权限。

【问题讨论】:

  • rate 列上是否有索引?
  • SQL 不会像那样天真地执行。您几乎肯定会发现,无论有没有 CTE,您都会得到完全相同的执行计划。尝试更新所有统计信息(如果代码在存储过程中,则重新编译代码),确保您拥有所有必要的索引,然后重试。
  • a.name 上有索引吗?有多少行与您所追求的名称相同?其中有多少的比率> 0?一共有多少行? 7 分钟对我来说似乎太长了,就像我们不知道的其他事情正在严重错误一样。
  • 列率没有索引。
  • @Yaqub 在提出这样的含糊建议时,请更加具体。您是指具有多个列的单个索引,还是两个单独的索引?

标签: sql sql-server


【解决方案1】:

我的猜测是,缓慢的执行计划正在以一种不幸的方式执行rate>0 过滤器,例如作为循环连接内部扫描的一部分或其他东西。

如果归根结底,一种解决方案是存储中间结果集并在单独的语句中对其进行过滤。

我建议您这样做,因为您无法更改供应商的数据库并且您基本上被卡住了。这实质上是从优化器中剥夺了一些控制权——这是您通常不想做的事情——并且还通过创建临时表增加了相对少量的开销。但它应该缓解这种情况下的缓慢。如果可能,我将继续与您的供应商合作制定索引策略。

select a.date, b.rate, c.type, a.value 
into #t
from a inner join b on a.id = b.aid
c inner join b.id = c.bid
where a.name = 'xxx' 

select * from #t where rate>0

【讨论】:

  • 谢谢,我听从了您的建议,现在查询速度很快。我使用您的方法作为快速修复,因为那时我可以从 DBA 获得访问权限并获得供应商的批准以添加额外的索引至少需要几个月的时间。但我们已经开始与供应商合作。再次感谢
  • 我建议使用后drop table;否则,它只会在用户结束他们的会话时被释放,这可能不在第二个select 之后。更好的是:使用@table_variable,但不幸的是没有方便/懒惰的into 语法可用于这些。
【解决方案2】:

首先让我解释一下为什么你的计划不好。一件事是我现在能想到的。 当没有 rate.The table a 是第一个并且基于 sarg 和 index 说它估计有 10000 行。 这可能使用了合并连接。现在与表 b 连接(截至目前,假设存在 1 到 n 映射,并且在 avg 上,每行表格 a 有 2 行)。然后连接后的估计行数为 20000,然后它与表 C 连接。现在根据表 C 的大小,它可以使用说合并连接。假设实际行数为 100 的数千

但是当您添加 SARG rate > 0。然后优化器在 A 和 B 连接后没有估计 20K 行而是估计 6667 行(当没有 auto_create_statistics 时,默认为 > 0 的 30%)然后它可能已经选择说与表 C 的嵌套循环连接而不是合并连接。但实际行可能更多,因此嵌套循环连接可能已用于 100 的数千个,因此最终的嵌套循环连接可能会占用这么多时间。

总而言之,我想说的是,由于额外的 sarg,优化器的估计不正确,因此是一个糟糕的计划。

您根本不在这里创建索引。索引不是每件事的解决方案,拥有它们的开销太多了,特别是在您的情况下,限制较少的查询运行得更好,所以问题是不做与索引有关,但更多地与统计数据有关。检查以下

您的数据库是否开启或关闭了 auto_create_statistics?或者表 b 具有列速率的统计信息? 何时为这些表更新/创建统计信息? 如果不是首先为该列创建统计信息,我相信您的计划将是正常的。 如果您无法创建统计信息,请尝试强制执行与您获得的相同计划,但 rate > 0 CTE 并没有像这样提高代码性能,可能会有异常。这些是为了使 代码更具可读性。

【讨论】:

    【解决方案3】:

    如果您试图告诉服务器如何使用 CTE 优化您的查询,我认为您是在开玩笑。使用索引您将获得更好的结果。但无论如何,您似乎已经将 CTE 部分放在了头上。您想要求服务器首先执行 CTE,然后评估外部查询。大概就是这样……

    ;with x as (Select a.id, a.date, a.value from a where a.name='xxx')
    Select x.date, b.rate, c.type, x.value
    From x
      Inner join b on x.id=b.aid
      Inner Join c on b.id=c.bid
    where b.rate>0
    

    ;with x as (
       Select b.id, b.rate, c.type 
       From b 
         Inner Join c on b.id=c.bid
       where b.rate>0)
    Select a.date, x.rate, x.type, a.value
    From a
      Inner join x on a.id=b.id
    Where a.name='xxx'
    

    【讨论】:

      【解决方案4】:

      ratename 列上创建 indexes,您会看到很大的不同。

      编辑:

      由于列属于两个不同的表,因此需要两个单独的索引

      编辑:

      Indexing guidelines

      【讨论】:

      • 您还必须考虑连接。在考虑连接时,选择性的顺序(只写a JOIN b JOIN c 并不意味着执行计划按此顺序执行,重要的是PLAN 的顺序)。简而言之;这个建议有可取之处,但非常不完整。例如; b(id,rate)b(rate.id) 上的复合索引将比 b(rate) 上的单个字段索引更合适
      • 我知道没有正确的索引会有点困难,但这里的问题是这是一个供应商数据库,我们无法对其进行任何更改,并且我们拥有它的只读访问权限。
      • @user799891 那么您可能应该向您的供应商而不是我们寻求帮助。 :-)
      【解决方案5】:

      您可以通过强制合并连接将优化器发送到正确的路径:

      选择... 从表 A left merge join TableB on ...

      【讨论】:

        【解决方案6】:

        b>0 部分彻底改变了它,因为服务器无法再使用聚集索引和其他方法优化查询。当您添加 b>0 时,会将这个查询变成一个范围查询,这将需要更多时间,因为它确实需要检查更多值。我相信有一些优化已经到位,例如针对此类查询的 B-tree 组织,但不会有太大改进。

        【讨论】:

        • 这部分不完整。尽管这些因素确实创建了不同的执行计划,但花费超过 400 倍的时间来返回更少的结果意味着一些重要的事情:执行计划是错误的。使用旧计划会更快,然后天真地过滤掉结果。对我而言,这表明 统计数据 或其他因素已过时。它并不表明 (有些人可能会认为这个答案已经说明了这一点) 添加 b.rate > 0 将会而且应该总是使这个查询如此显着慢点。 [虽然我不同意这个答案,但我选择不投反对票。]
        • 表达式 b>0 仍然是一个 SARGable 表达式,可以被优化器有效地使用。
        猜你喜欢
        • 1970-01-01
        • 2020-12-30
        • 1970-01-01
        • 2017-05-26
        • 1970-01-01
        • 2022-09-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多