Rencana kueri aneh saat menggunakan OR dalam klausa JOIN - Pemindaian konstan untuk setiap baris dalam tabel

10

Saya mencoba untuk menghasilkan contoh rencana kueri untuk menunjukkan mengapa UNION menggunakan dua set hasil dapat lebih baik daripada menggunakan ATAU dalam klausa GABUNG. Paket permintaan yang saya tulis membuat saya bingung. Saya menggunakan database StackOverflow dengan indeks nonclustered pada Users.Reputation.

Gambar rencana kueri Pertanyaannya adalah

CREATE NONCLUSTERED INDEX IX_NC_REPUTATION ON dbo.USERS(Reputation)
SELECT DISTINCT Users.Id
FROM dbo.Users
INNER JOIN dbo.Posts  
    ON Users.Id = Posts.OwnerUserId
    OR Users.Id = Posts.LastEditorUserId
WHERE Users.Reputation = 5

Rencana kueri ada di https://www.brentozar.com/pastetheplan/?id=BkpZU1MZE , durasi kueri untuk saya adalah 4:37 menit, 26612 baris dikembalikan.

Saya belum pernah melihat gaya pemindaian konstan ini dibuat dari tabel yang ada sebelumnya - Saya tidak terbiasa dengan mengapa ada pemindaian konstan dijalankan untuk setiap baris, ketika pemindaian konstan biasanya digunakan untuk satu baris yang dimasukkan oleh pengguna. misalnya SELECT GETDATE (). Mengapa ini digunakan di sini? Saya akan sangat menghargai beberapa panduan dalam membaca rencana permintaan ini.

Jika saya membagi ATAU menjadi UNION, itu menghasilkan rencana standar berjalan dalam 12 detik dengan 26612 baris yang sama dikembalikan.

SELECT Users.Id
FROM dbo.Users
    INNER JOIN dbo.Posts
       ON Users.Id = Posts.OwnerUserId
WHERE Users.Reputation = 5
UNION 
SELECT Users.Id
FROM dbo.Users
    INNER JOIN dbo.Posts
       ON  Users.Id = Posts.LastEditorUserId
WHERE Users.Reputation = 5

Saya menafsirkan rencana ini sebagai melakukan ini:

  • Dapatkan semua 41782500 baris dari Posting (jumlah aktual baris cocok dengan pemindaian CI pada Posting)
  • Untuk setiap 41782500 baris dalam Posting:
    • Menghasilkan skalar:
    • Expr1005: OwnerUserId
    • Expr1006: OwnerUserId
    • Expr1004: Nilai statis 62
    • Expr1008: LastEditorUserId
    • Expr1009: LastEditorUserId
    • Expr1007: Nilai statis 62
  • Dalam gabungan:
    • Exp1010: Jika Expr1005 (OwnerUserId) bukan nol, gunakan yang lain gunakan Expr1008 (LastEditorUserID)
    • Expr1011: Jika Expr1006 (OwnerUserId) bukan nol, gunakan itu, kalau tidak gunakan Expr1009 (LastEditorUserId)
    • Expr1012: Jika Expr1004 (62) adalah null gunakan itu, kalau tidak gunakan Expr1007 (62)
  • Dalam skalar Hitung: Saya tidak tahu apa yang dilakukan ampersand.
    • Expr1013: 4 [dan?] 62 (Expr1012) = 4 dan OwnerUserId NULL (NULL = Expr1010)
    • Expr1014: 4 [dan?] 62 (Expr1012)
    • Expr1015: 16 dan 62 (Expr1012)
  • Dalam Urutan Dengan mengurutkan berdasarkan:
    • Expr1013 Desc
    • Expr1014 Asc
    • Expr1010 Asc
    • Expr1015 Desc
  • Dalam Gabung Interval dihapus Expr1013 dan Expr1015 (ini adalah input tetapi bukan output)
  • Dalam pencarian Indeks di bawah loop bersarang bergabung itu menggunakan Expr1010 dan Expr1011 sebagai mencari predikat, tapi saya tidak mengerti bagaimana ia memiliki akses ke ini ketika belum melakukan loop bersarang bergabung dari IX_NC_REPUTATION ke subtree yang berisi Expr1010 dan Expr1011 .
  • Gabung Nested Loops hanya mengembalikan Users.IDs yang memiliki kecocokan di subtree sebelumnya. Karena pushdown predikat, semua baris yang dikembalikan dari pencarian indeks di IX_NC_REPUTATION dikembalikan.
  • Gabung Nested terakhir bergabung: Untuk setiap catatan Tulisan, menghasilkan Pengguna. Di mana kecocokan ditemukan dalam dataset di bawah ini.
Andrew
sumber
Apakah Anda mencoba dengan subquery atau subquery EXISTS? SELECT Users.Id FROM dbo.Users WHERE Users.Reputation = 5 AND ( EXISTS (SELECT 1 FROM dbo.Posts WHERE Users.Id = Posts.OwnerUserId) OR EXISTS (SELECT 1 FROM dbo.Posts WHERE Users.Id = Posts.LastEditorUserId) ) ;
ypercubeᵀᴹ
satu subquery:SELECT Users.Id FROM dbo.Users WHERE Users.Reputation = 5 AND EXISTS (SELECT 1 FROM dbo.Posts WHERE Users.Id IN (Posts.OwnerUserId, Posts.LastEditorUserId) ) ;
ypercubeᵀᴹ

Jawaban:

10

Rencananya mirip dengan yang saya masuki secara lebih rinci di sini .

The Postsmeja dipindai.

Untuk setiap baris ia mengekstrak OwnerUserIddan LastEditorUserId. Ini mirip dengan cara UNPIVOTkerjanya. Anda melihat operator pemindaian konstan tunggal dalam rencana untuk di bawah ini menciptakan dua baris output untuk setiap baris input.

SELECT *
FROM dbo.Posts
UNPIVOT (X FOR U IN (OwnerUserId,LastEditorUserId)) Unpvt

Dalam hal ini rencananya sedikit lebih kompleks karena semantiknya oradalah bahwa jika kedua nilai kolom sama, hanya satu baris yang harus dipancarkan dari gabungan pada Users(bukan dua)

Ini kemudian dimasukkan melalui interval penggabungan sehingga dalam hal nilai-nilai yang sama rentang runtuh ke bawah dan hanya satu pencarian dieksekusi melawan Users- jika tidak dua percobaan dieksekusi melawannya.

Nilainya 62adalah bendera yang berarti bahwa pencarian harus merupakan pencarian kesetaraan.

Mengenai

Saya tidak mengerti bagaimana ia memiliki akses ke ini ketika belum melakukan loop bersarang bergabung dari IX_NC_REPUTATION ke subtree yang berisi Expr1010 dan Expr1011

Ini didefinisikan dalam operator gabungan berwarna kuning. Ini ada di sisi luar dari loop bersarang berwarna kuning yang disorot. Jadi ini berjalan sebelum pencarian yang disorot kuning di bagian dalam loop bersarang.

masukkan deskripsi gambar di sini

Menulis ulang yang memberikan rencana yang sama (meskipun dengan interval gabungan digantikan oleh gabungan serikat pekerja) di bawah dalam kasus ini membantu.

SELECT DISTINCT D2.UserId
FROM   dbo.Posts p
       CROSS APPLY (SELECT Users.Id AS UserId
                    FROM   (SELECT p.OwnerUserId
                            UNION /*collapse duplicate to single row*/
                            SELECT p.LastEditorUserId) D1(UserId)
                           JOIN Users
                             ON Users.Id = D1.UserId) D2
OPTION (FORCE ORDER) 

masukkan deskripsi gambar di sini

Bergantung pada indeks apa yang tersedia pada Poststabel, varian dari kueri ini mungkin lebih efisien daripada UNION ALLsolusi yang Anda usulkan . (salinan database yang saya tidak memiliki indeks berguna untuk ini dan solusi yang diusulkan melakukan dua pemindaian penuh Posts. Di bawah ini melakukannya dalam satu pemindaian)

WITH Unpivoted AS
(
SELECT UserId
FROM dbo.Posts
UNPIVOT (UserId FOR U IN (OwnerUserId,LastEditorUserId)) Unpivoted
)
SELECT DISTINCT Users.Id
FROM dbo.Users INNER HASH JOIN Unpivoted
       ON  Users.Id = Unpivoted.UserId
WHERE Users.Reputation = 5

masukkan deskripsi gambar di sini

Martin Smith
sumber