仅当过滤的部分位于JOIN而不是WHERE时才使用过滤的索引


10

我在下面创建了过滤索引,但是当我进一步向下运行2个查询时,该索引仅在JOIN中具有END_DTTM而不是where子句的第一个示例中用于查找(这是查询中的唯一区别) 。谁能解释为什么会这样?

索引创建

CREATE NONCLUSTERED INDEX [ix_PATIENT_LIST_BESPOKE_LIST_ID_includes] ON [dbo].[PATIENT_LIST_BESPOKE] 
(
    [LIST_ID] ASC,
    [END_DTTM] ASC
)
WHERE ([END_DTTM] IS NULL)

查询

DECLARE @LIST_ID INT = 3655

--This one seeks on the index

SELECT  
    PATIENT_LISTS.LIST_ID
FROM    
    DBO.PATIENT_LISTS
    LEFT JOIN DBO.PATIENT_LIST_BESPOKE ON PATIENT_LISTS.LIST_ID = PATIENT_LIST_BESPOKE.LIST_ID  
                                      AND PATIENT_LIST_BESPOKE.END_DTTM IS NULL
WHERE
    PATIENT_LISTS.LIST_ID = @LIST_ID

--This one scans on the index

SELECT  
    PATIENT_LISTS.LIST_ID
FROM    
    DBO.PATIENT_LISTS
    LEFT JOIN DBO.PATIENT_LIST_BESPOKE ON PATIENT_LISTS.LIST_ID = PATIENT_LIST_BESPOKE.LIST_ID  
WHERE   
    PATIENT_LISTS.LIST_ID = @LIST_ID AND
    PATIENT_LIST_BESPOKE.END_DTTM IS NULL   

Answers:


12

为了使优化程序将谓词与索引(经过过滤或其他方式)进行匹配,该谓词必须出现在逻辑查询树中与Get操作相邻的位置。为了促进这一点,通常在优化开始之前将谓词推到尽可能靠近逻辑树的叶子的位置。

为了大大简化,物理索引策略的实现是这样做的:

Predicate + Logical Get -> Physical Get (using Index)

您感兴趣的查询从外部联接上方的谓词开始:

Predicate on T2 --+-- LOJ -- Get (T1)
                       |
                       +---- Get (T2)

此形状与索引策略规则不匹配,因为该谓词与Get不相邻。因此,答案的第一部分是过滤后的索引匹配将失败,除非可以将谓词推到外部联接之外。

第二部分仅仅是,优化器不包含将谓词移过保留侧上的外部联接的必要探索规则,因为该转换很少有效。优化程序的一个普遍功能是仅执行最常用的规则。

结果,在这种情况下,匹配过滤索引将失败。需要明确的是,重写将在您提到的非常特殊的情况下有效(第二次查询)。

对于第一个查询形式(具有不同的语义),谓词从一开始就与联接相关联,并且谓词下推逻辑可以将其移至与Get的距离很短,因为它不必经过外部联接即可。以上说明。

背景和更多信息:


9

这些查询在语义上并不相同,因为一个查询可以在连接之前进行过滤,而另一个查询可以在连接之后进行过滤。让我用一个简单的例子来说明:

CREATE TABLE dbo.Lefty(LeftyID INT PRIMARY KEY);

CREATE TABLE dbo.Righty(LeftyID INT, SomeList INT);

INSERT dbo.Lefty(LeftyID) VALUES(1),(2),(3);

INSERT dbo.Righty(LeftyID, SomeList) VALUES(1,1),(1,NULL),(2,2);

查询1返回所有三行:

SELECT l.LeftyID, r.SomeList
FROM dbo.Lefty AS l
LEFT OUTER JOIN dbo.Righty AS r
ON l.LeftyID = r.LeftyID
AND r.SomeList IS NULL;

但是,查询2省略了LeftyID 2:

SELECT l.LeftyID, r.SomeList
FROM dbo.Lefty AS l
LEFT OUTER JOIN dbo.Righty AS r
ON l.LeftyID = r.LeftyID
WHERE r.SomeList IS NULL;

SQLfiddle证明

如果您尝试执行反半联接,则被测试的列必须不可为null。仅在处理INNER联接时,在ON和WHERE之间移动条件没有逻辑上的区别,但是与OUTER则存在显着差异。而且,与是否可以使用经过筛选的索引相比,您应该更加关注结果是否正确。


感谢您的回答,但我不是在说查询是相同的,而是在问为什么一个查询使用过滤索引,而另一个查询不使用过滤索引。
克里斯,2015年

@chris您是否尝试通过索引提示强制使用该索引?我很好奇,比较有无提示的实际,执行后计划。对我来说,很明显,当优化器认为自己正在执行反半联接时(因为在那种情况下它不希望使用可为空的列),它会忽略该索引,但是我不确定这是否要用成本核算或操作顺序或一些基础知识来做,即左侧可能比过滤索引中的行更多。查看计划可能会有所帮助。
亚伦·伯特兰

3

这两个查询是不同的-含义和结果。这是一个重写,因此两个查询在做什么很明显:

-- 1st query
SELECT  
    a.LIST_ID
FROM    
      ( SELECT LIST_ID 
        FROM   DBO.PATIENT_LISTS
        WHERE  LIST_ID = @LIST_ID
      ) AS a
    LEFT JOIN  
      ( SELECT LIST_ID                    -- the filtered index
        FROM   DBO.PATIENT_LIST_BESPOKE   -- can be used
        WHERE  END_DTTM IS NULL           -- for the subquery
      ) AS b
    ON  a.LIST_ID = b.LIST_ID ;           -- and the join

和第二:

-- 2nd query
SELECT  
    a.LIST_ID
FROM    
      ( SELECT LIST_ID 
        FROM   DBO.PATIENT_LISTS
        WHERE  LIST_ID = @LIST_ID
      ) AS a
    JOIN  
      ( SELECT LIST_ID                    -- the filtered index
        FROM   DBO.PATIENT_LIST_BESPOKE   -- can be used
        WHERE  END_DTTM IS NULL           -- for the subquery
      ) AS b
    ON  a.LIST_ID = b.LIST_ID             -- and the join

UNION ALL

SELECT  
    a.LIST_ID
FROM    
      ( SELECT LIST_ID 
        FROM   DBO.PATIENT_LISTS
        WHERE  LIST_ID = @LIST_ID
      ) AS a
WHERE NOT EXISTS  
      ( SELECT *
        FROM   DBO.PATIENT_LIST_BESPOKE AS b
        WHERE  a.LIST_ID = b.LIST_ID         -- but not for this
      ) ;

我认为对于2nq查询的第二部分,现在已经很明显了,不能使用过滤索引。


详细地,关于这些查询,LIST_ID第一个表中有4种类型的值:

  • (a)在第二张表中具有匹配行的值,所有值均带有END_DTTM IS NULL

  • (b)在第二个表中具有相匹配的行的值,同时带有END_DTTM IS NULLEND_DTTM IS NOT NULL

  • (c)在第二张表中具有匹配行的值,所有值均带有END_DTTM IS NOT NULL

  • (d)第二个表中没有匹配行的值。

现在,第一个查询将可能多次返回类型(a)和(b)的所有值(与它们在第二个表中具有匹配行的一样多END_DTTM IS NULL),而类型(c)和(d)的所有行恰好返回一次(这是外部联接的不匹配部分)。

第二个查询将可能多次返回(a)和(b)类型的所有值(与第二个表中具有匹配行的一样多END_DTTM IS NULL),而所有(d)类型的行恰好返回一次。
不会返回任何类型(c)的值,因为该联接将在第二个表中找到匹配的行(但它们将具有END_DTTM IS NOT NULL),并将被后续WHERE子句删除。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.