SQL 未返回任何内容时服务器查询运行速度变慢

SQL Server query runs slower when nothing is returned

当结果集为空时,我的查询 运行 很慢。有事return,快如闪电。

    ;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

校长table是: Facilities table 拥有设施,256k 条记录。 CategoryTree 用于在层次结构中对类别进行分组。

NodeId int,
CategoryId int,
ParentId int

FacilitiesCategoryTree 用于 link CategoryTree 到 Facilities。

给定 NodeId,第二个 CTE return 是给定节点的所有后代节点,包括其自身。然后是第三个 CTE,returns 属于这些节点的设施 ID。

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

这个查询 运行 在需要 return 的情况下非常快,即使我包含更多的谓词,包括全文搜索和其他,但是当给定的分支没有任何设施时,这个查询大约需要。 2 秒到 运行.

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

您认为问题是什么?如何提高空结果查询的性能?

谢谢。

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

http://www.filedropper.com/withorderby

http://www.filedropper.com/withoutorderby

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

我还将查询更改为 select,从 facil 和 join 到 Facilities 并添加了选项(强制顺序)。结果同上。

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

第一件事 - 您可以将前两个 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 从类别树中查找设施中的一行。如果它存在 returns 结果行。如果没有 - 跳过此功能。
  • 重复这些迭代直到返回 51 行

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

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

  • 将树中的设施 ID 保存到 table 变量
  • 将其与设施 table 合并并按 SortIndex
  • 排序

另一个选项(也可以与第一个选项结合使用)是尝试使用 FORCE ORDER 查询提示。在这种情况下,您需要将 select 语句从 facil 修改为 select 并将其加入 Facilities 并将 option (force order) 查询提示添加到末尾声明。

按 select 树中的所有设施进行无序查询。然后从 facilities table.

中提取其他 facility 字段

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

如果在使用类别树和 facility/categories 映射 table 连接后确实有大量设施返回,那么最好的解决方案是非规范化 facility/category 映射 table 在此 table 中包含 SortIndex 并通过 NodeIdSortIndex.

向此 table 添加索引

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

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