Indeks yang difilter hanya digunakan ketika bagian yang difilter berada di GABUNG bukan DI MANA

10

Saya telah membuat indeks yang difilter di bawah ini tetapi ketika saya menjalankan 2 kueri lebih jauh ke bawah, indeks ini hanya digunakan untuk pencarian dalam contoh pertama yang memiliki END_DTTM dalam GABUNGAN daripada klausa di mana (itulah satu-satunya perbedaan dalam kueri) . Adakah yang bisa menjelaskan mengapa ini terjadi?

Pembuatan Indeks

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)

Pertanyaan

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   
chris
sumber

Jawaban:

12

Agar pengoptimal mencocokkan predikat dengan indeks (difilter atau yang lain), predikat tersebut harus tampak bersebelahan dengan operasi Dapatkan di pohon kueri logis. Untuk memfasilitasi ini, predikat umumnya didorong sedekat mungkin ke daun pohon logis sebelum optimasi dimulai.

Untuk mempermudah, implementasi strategi indeks fisik melakukan ini:

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

Permintaan yang Anda minati dimulai dengan predikat di atas gabungan luar:

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

Bentuk ini tidak cocok dengan aturan strategi indeks karena predikat tidak berdekatan dengan Dapatkan. Jadi, bagian pertama dari jawabannya adalah bahwa pencocokan indeks yang disaring akan gagal kecuali predikat dapat didorong melewati gabungan luar.

Bagian kedua adalah bahwa pengoptimal tidak mengandung aturan eksplorasi yang diperlukan untuk memindahkan predikat melewati gabungan luar pada sisi yang diawetkan, karena transformasi sangat jarang valid. Ini adalah fitur umum dari pengoptimal bahwa hanya aturan yang paling berguna yang diimplementasikan.

Akibatnya, pencocokan indeks yang difilter gagal dalam kasus ini. Agar jelas, penulisan ulang akan valid dalam kasus yang sangat spesifik yang Anda kutip (permintaan kedua).

Untuk formulir kueri pertama (dengan semantik berbeda), predikat dikaitkan dengan gabungan dari awal, dan logika push-down predikat dapat memindahkan jarak pendek ke Get karena tidak harus bergerak melewati gabungan luar seperti dijelaskan di atas.

Latar belakang dan informasi lebih lanjut:

Paul White 9
sumber
9

Ini bukan permintaan yang sama secara semantik, karena seseorang dapat memfilter sebelum gabungan yang lain dapat memfilter setelahnya. Biarkan saya ilustrasikan dengan contoh sederhana:

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);

Kueri 1 mengembalikan ketiga baris:

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;

Namun, Kueri 2 mengabaikan 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;

Bukti SQLfiddle

Jika Anda mencoba melakukan anti-semi join, kolom yang diuji harus tidak dapat dibatalkan . Memindahkan kriteria antara ON dan WHERE tidak membuat perbedaan logis ketika Anda berurusan dengan INNER bergabung saja, tetapi dengan OUTER ada perbedaan yang signifikan. Dan Anda harus lebih peduli bahwa hasil Anda benar daripada apakah indeks yang difilter bahkan dapat digunakan.

Aaron Bertrand
sumber
terima kasih atas jawabannya, tetapi saya tidak mengklaim bahwa pertanyaannya sama, saya bertanya mengapa satu permintaan menggunakan indeks yang difilter dan yang lainnya tidak.
chris
@ Chris Apakah Anda mencoba memaksa indeks itu dengan petunjuk indeks? Saya ingin tahu membandingkan rencana aktual, pasca-eksekusi dengan dan tanpa petunjuk itu. Bagi saya jelas bahwa pengoptimal mengabaikan indeks itu ketika percaya bahwa itu melakukan anti-semi join (karena tidak akan mengharapkan kolom nullable untuk digunakan dalam kasus itu), tapi saya tidak yakin apakah itu untuk lakukan dengan penetapan biaya atau urutan operasi atau beberapa pengetahuan mendasar bahwa ada potensi lebih banyak baris yang berasal dari sisi kiri daripada yang ada dalam indeks yang difilter. Melihat rencana itu bisa membantu.
Aaron Bertrand
3

Dua pertanyaan berbeda - dalam arti dan hasil. Ini adalah penulisan ulang, jadi lebih jelas apa yang dilakukan dua pertanyaan:

-- 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

dan ke-2:

-- 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
      ) ;

Saya pikir sekarang cukup jelas bahwa untuk bagian 2 dari permintaan 2nq, indeks yang difilter tidak dapat digunakan.


Secara detail, mengenai kueri ini, ada 4 jenis LIST_IDnilai di tabel pertama:

  • (a) nilai yang memiliki baris yang cocok di tabel kedua, semuanya dengan END_DTTM IS NULL.

  • (B) nilai-nilai yang memiliki baris yang cocok di tabel kedua, baik dengan END_DTTM IS NULLdan dengan END_DTTM IS NOT NULL.

  • (c) nilai yang memiliki baris yang cocok di tabel kedua, semuanya dengan END_DTTM IS NOT NULL.

  • (D) nilai-nilai yang tidak memiliki baris yang cocok di tabel kedua.

Sekarang, kueri pertama akan mengembalikan semua nilai tipe (a) dan (b) mungkin berkali-kali (sebanyak mereka memiliki baris yang cocok di tabel kedua dengan END_DTTM IS NULL) dan semua baris tipe (c) dan (d) tepat sekali ( itu adalah bagian yang tidak cocok dari bagian luar).

Kueri ke-2 akan mengembalikan semua nilai tipe (a) dan (b) mungkin berkali-kali (sebanyak mereka memiliki baris yang cocok di tabel kedua dengan END_DTTM IS NULL) dan semua baris tipe (d) tepat sekali.
Itu tidak akan mengembalikan nilai jenis (c) karena gabungan akan menemukan baris yang cocok di tabel kedua (tetapi ini akan memiliki END_DTTM IS NOT NULL) dan mereka akan dihapus oleh WHEREklausa berikutnya .

ypercubeᵀᴹ
sumber