使用条件连接语句时执行多个全索引扫描

Posted

技术标签:

【中文标题】使用条件连接语句时执行多个全索引扫描【英文标题】:Multiple full index scans being performed when using a conditional join statement 【发布时间】:2013-06-23 17:54:15 【问题描述】:

我在查询包含父/子关系行的表时遇到问题。在处理一个简化示例时,我意识到 stackexchange 架构非常相似。

假设我正在通过 stackexchange 数据资源管理器查询 *** 帖子表。我正在尝试获取所有帖子及其相关答案的子集。

有关示例查询,请参阅 https://data.stackexchange.com/***/query/121981/a-subset-of-questions-and-associated-answers。

帖子子集在视图中定义,该视图具有相当复杂且昂贵的查询计划。在下面的示例中,它已被简化为仅选择前两行。

第一种方式,使用联合:

with ExpensiveView as (select top 2 ID from Posts order by ID)

select Posts.*
from ExpensiveView
left outer join Posts
  ON ExpensiveView.Id = Posts.Id 
  
union all

select Posts.*
from ExpensiveView
left outer join Posts
  ON ExpensiveView.Id = Posts.ParentId

我很想避免这种方式,因为ExpensiveView 被评估了两次。对于上面的简化版本显然不是问题,但会导致更复杂的版本出现问题。

第二种方式,使用带有条件连接子句的单选:

with ExpensiveView as (select top 2 ID from Posts order by ID)

select Posts.*
from ExpensiveView
left outer join Posts
  ON ExpensiveView.Id = Posts.Id or ExpensiveView.Id = Posts.ParentId

这避免了ExpensiveView 被评估两次,但会导致非常大的聚集索引扫描。它似乎正在扫描ExpensiveView 中每个 ID 的整个索引(因此 2 * 14977623 = ~3000 万行)。这很慢。

两个问题

为什么第二个查询中的条件连接会导致这么大的索引扫描?

有什么方法可以在不对ExpensiveView 进行多次评估的情况下获得我正在寻找的结果?

【问题讨论】:

关于您的OR 问题,请参阅Is having an 'OR' in an INNER JOIN condition a bad idea?。为什么你不能将ExpensiveView 实现为#temp 表? 链接的答案说,当在连接子句中使用 or 时,连接不能是合并或散列,因此将是循环连接 - 暗示循环连接是大的原因表扫描。但是,如果我删除其中一个连接子句,那么循环连接仍然存在,它只是不进行大扫描。 关于使用临时表,我有点担心所涉及的开销。这是一个运行相当频繁的查询(最多每秒几次)。 另外,ExpensiveView.Id IN (Posts.Id, Posts.ParentId) 也不能满足 Posts 上的索引查找。原则上它可以在Posts.IdPosts.ParentId 上进行两次索引搜索,然后删除与这两个搜索匹配的任何重复行,尽管我不确定SQL Server 是否会在实践中给出该计划。 (我确实想到你可能可以用OUTER APPLY 模拟类似的东西) 这对您的数据有何影响? with ExpensiveView as (select top 2 ID from Posts order by ID) select Posts.* from ExpensiveView OUTER APPLY (SELECT * FROM Posts WHERE Id = ExpensiveView.Id UNION SELECT * FROM Posts WHERE ParentId = ExpensiveView.Id) Posts 【参考方案1】:

试试这个

with
ExpensiveView as (select top 2 ID from Posts order by ID),
CTE_Posts as (
    select *, NP.Id as New_Post_ID
    from Posts as P
        outer apply (select P.Id union all select P.ParentId) as NP
)
select
    P.*
from ExpensiveView as E
    left outer join CTE_Posts as P on P.New_Post_ID = E.ID

【讨论】:

以上是关于使用条件连接语句时执行多个全索引扫描的主要内容,如果未能解决你的问题,请参考以下文章

sql语句中where后边的哪些条件会使索引失效 -- SQL语句优化

MySQL---sql语句优化

MySQL---sql语句优化

sql语句提高数据库查询效率

怎么样优化数据库语句?

如何优化Oracle在where条件中用了自定义函数的SQL语句