没有返回任何内容时,SQL Server 查询运行速度较慢

Posted

技术标签:

【中文标题】没有返回任何内容时,SQL Server 查询运行速度较慢【英文标题】:SQL Server query runs slower when nothing is returned 【发布时间】:2015-01-14 19:17:34 【问题描述】:

当结果集为空时,我的查询运行缓慢。有东西要还,快如闪电。

    ;with tree(NodeId,CategoryId,ParentId) as (
        select ct.NodeId, ct.CategoryId, ct.ParentId
        from dbo.CategoryTree as ct
        where ct.ParentId = 6
        union all
        select t.NodeId, t.CategoryId, t.ParentId from dbo.CategoryTree as t
        inner join tree as t2 on t.ParentId = t2.NodeId
    ), branch(NodeId,CategoryId,ParentId) as
    (
        select NodeId, CategoryId, ParentId from dbo.CategoryTree as t
        where t.NodeId = 6
        union all
        select NodeId, CategoryId, ParentId
        from tree as t
    ),facil(FacilityId) as(
        select distinct fct.FacilityId
        from dbo.FacilitiesCategoryTree as fct
        inner join branch b on b.NodeId = fct.CategoryNodeId
    )

    select top 51 f.Id, f.CityId, f.NameGEO,
     f.NameENG, f.NameRUS, f.DescrGEO, f.DescrENG,
     f.DescrRUS, f.MoneyMin, f.MoneyAvg, f.Lat, f.Lng, f.SortIndex,
     f.FrontImgUrl from dbo.Facilities f
     inner join facil t2 on t2.FacilityId = f.Id
        and f.EnabledUntil > 'Jan 14 2015 10:23PM'
     order by f.SortIndex

主要表是: 设施表保存设施,256k 记录。 CategoryTree 用于对层次结构中的类别进行分组。

NodeId int,
CategoryId int,
ParentId int

FacilitiesCategoryTree 用于将 CategoryTree 链接到设施。

给定 NodeId,第二个 CTE 返回给定节点的所有后代节点,包括它自己。然后是第三个 CTE,它返回属于这些节点的设施 ID。

最后,最后一个 CTE 加入到实际设施表中。结果由 SortIndex 排序,用于手动指示设施的顺序。

即使我包含更多谓词(包括全文搜索和其他),当有要返回的内容时,此查询也会运行得非常快,但是当给定分支没有任何设施时,此查询大约需要 .运行 2 秒。

如果我排除 order by 子句,查询再次运行得非常快。所有这些表都已编入索引,查询优化器并未提出任何改进建议。

您认为问题出在哪里,可以采取哪些措施来提高空结果查询的性能?

谢谢。

更新1: 我正在添加执行计划。

http://www.filedropper.com/withorderby

http://www.filedropper.com/withoutorderby

更新 2: 我查看了 oryol 的建议,并尝试将设施 ID 从树保存到表变量中,并将其与设施表连接起来,并按 SortIndex 排序。它消除了空结果的问题,但将结果集的查询的执行时间从 250 毫秒增加到了 950 毫秒。

我还将查询更改为从设施中选择并加入设施并添加选项(强制订单)。结果和上面一样。

最后,我对设施/类别映射表进行了非规范化,以在该表中包含 SortIndex。它将普通查询的执行时间从 250 毫秒略微增加到 300 毫秒,但它解决了空结果集的问题。我想,我会坚持这个方法。

【问题讨论】:

ORDER BY 一定给查询优化器提供了错误的提示,比如使用了低效的索引。您可以发布带有和不带有ORDER BY 的执行计划吗? 我在更新后的帖子中添加了两个执行计划。 【参考方案1】:

第一件事 - 您可以将前两个 CTE 稍微简化为一个:

with tree(NodeId,CategoryId,ParentId) as (
        select ct.NodeId, ct.CategoryId, ct.ParentId
        from dbo.CategoryTree as ct
        where ct.NodeId = 6
        union all
        select t.NodeId, t.CategoryId, t.ParentId from dbo.CategoryTree as t
        inner join tree as t2 on t.ParentId = t2.NodeId
    )

优化器不知道或错误估计将为您的类别返回的设施数量的主要问题。因为您需要SortIndex 订购的设施,优化器决定:

查看SortIndex 订购的所有设施(使用适当的索引) 跳过未被其他过滤器覆盖的行 (EnabledUntil) 使用给定的Facility Id 从类别树中查找设施中的一行。如果存在则返回结果行。如果没有 - 跳过这个设施。 重复这些迭代,直到返回 51 行

因此,在最坏的情况下(如果没有 51 个这样的设施或者它们有非常大的SortIndex)将需要扫描所有idx_Facilities_SortIndex,并且需要很多时间。

有几种方法可以解决此问题(包括提示优化器以告知行数或连接顺序)以找到更好地使用真实数据库的最佳方法。可以尝试的第一个选项是将查询更改为:

将设施 ID 从树保存到表变量 将其与设施表连接并按 SortIndex 排序

另一个选项(也可以与第一个选项一起使用)是尝试使用FORCE ORDER 查询提示。在这种情况下,您需要修改您的 select 语句以从 facil 中进行选择,并将其连接到 Facilities 并将 option (force order) 查询提示添加到语句的末尾。

通过从树中选择所有设施进行无顺序查询。然后从设施表中提取其他设施字段。

此外,了解树中设施的实际大小也很重要(根据执行计划中的估计,没有顺序,它真的很大 - 395982)。这个估计(或多或少)正确吗?

如果您在加入类别树和设施/类别映射表后确实返回了大量设施,那么最好的解决方案是对设施/类别映射表进行非规范化以在该表中包含 SortIndex 并通过以下方式向该表添加索引NodeIdSortIndex

所以实际上,我们需要使用真实数据来测试查询/索引。或者了解不同的数据统计:

类别数量 每个类别的设施数量和设施/类别映射表中的总行数 SortIndex 分布(是否唯一?) 等

【讨论】:

以上是关于没有返回任何内容时,SQL Server 查询运行速度较慢的主要内容,如果未能解决你的问题,请参考以下文章

SqlDataAdapter.Fill 方法不会给出任何错误,但也不会返回任何数据以用于长时间运行的查询 ado.net core SQL Server

休眠选择查询不返回任何内容

PHP sql查询没有返回任何内容

SQL 查询在它应该返回的时候没有返回任何结果

SQL Server 中的触发器丢失

SQL Server 过程不返回任何结果