Cara mengatur tampilan yang diindeks saat MEMILIH TOP 1 dengan ORDER BY dari tabel yang berbeda

11

Saya berjuang untuk menyiapkan tampilan yang diindeks dalam skenario berikut sehingga kueri berikut berfungsi tanpa dua pemindaian indeks berkerumun. Setiap kali saya membuat tampilan indeks untuk permintaan ini dan kemudian menggunakannya, tampaknya mengabaikan indeks apa pun yang saya masukkan:

    -- +++ THE QUERY THAT I WANT TO IMPROVE PERFORMANCE-WISE +++

    SELECT TOP 1 *
    FROM    dbo.TB_test1 t1
            INNER JOIN dbo.TB_test2 t2 ON t1.PK_ID1 = t2.FK_ID1
    ORDER BY t1.somethingelse1
           ,t2.somethingelse2;


    GO

Pengaturan tabel adalah sebagai berikut:

  • dua meja
  • mereka bergabung dengan gabungan batin dengan kueri di atas
  • dan dipesan oleh kolom dari yang pertama dan kemudian kolom dari tabel kedua dengan permintaan di atas; hanya TOP 1 yang dipilih
  • (dalam skrip di bawah ini ada juga beberapa baris untuk menghasilkan data uji, kalau-kalau itu membantu mereproduksi masalah)

    -- +++ TABLE SETUP +++
    
    CREATE TABLE [dbo].[TB_test1]
        (
         [PK_ID1] [INT] IDENTITY(1, 1)  NOT NULL
        ,[something1] VARCHAR(40) NOT NULL
        ,[somethingelse1] BIGINT NOT NULL
            CONSTRAINT [PK_TB_test1] PRIMARY KEY CLUSTERED ( [PK_ID1] ASC )
        );
    
    GO
    
    create TABLE [dbo].[TB_test2]
        (
         [PK_ID2] [INT] IDENTITY(1, 1)  NOT NULL
        ,[FK_ID1] [INT] NOT NULL
        ,[something2] VARCHAR(40) NOT NULL
        ,[somethingelse2] BIGINT NOT NULL
            CONSTRAINT [PK_TB_test2] PRIMARY KEY CLUSTERED ( [PK_ID2] ASC )
        );
    
    GO
    
    ALTER TABLE [dbo].[TB_test2]  WITH CHECK ADD  CONSTRAINT [FK_TB_Test1] FOREIGN KEY([FK_ID1])
    REFERENCES [dbo].[TB_test1] ([PK_ID1])
    GO
    
    ALTER TABLE [dbo].[TB_test2] CHECK CONSTRAINT [FK_TB_Test1]
    
    GO
    
    
    -- +++ TABLE DATA GENERATION +++
    
    -- this might not be the quickest way, but it's only to set up test data
    
    INSERT INTO dbo.TB_test1
            ( something1, somethingelse1 )
    VALUES  ( CONVERT(VARCHAR(40), NEWID())  -- something1 - varchar(40)
              ,ISNULL(ABS(CHECKSUM(NewId())) % 92233720368547758078, 1)   -- somethingelse1 - bigint
              )
    
    GO 100000
    
    RAISERROR( 'Finished setting up dbo.TB_test1', 0, 1) WITH NOWAIT    
    
    GO    
    
    INSERT INTO dbo.TB_test2
            ( FK_ID1, something2, somethingelse2 )
    VALUES  ( ISNULL(ABS(CHECKSUM(NewId())) % ((SELECT MAX(PK_ID1) FROM dbo.TB_test1) - 1), 0) + 1 -- FK_ID1 - int
              ,CONVERT(VARCHAR(40), NEWID())  -- something2 - varchar(40)
              ,ISNULL(ABS(CHECKSUM(NewId())) % 92233720368547758078, 1)   -- somethingelse2 - bigint
              )
    
    GO 100000
    
    RAISERROR( 'Finished setting up dbo.TB_test2', 0, 1) WITH NOWAIT          
    
    GO

Tampilan yang diindeks mungkin harus didefinisikan sebagai berikut dan permintaan TOP 1 yang dihasilkan di bawah. Tetapi indeks apa yang saya perlukan agar kueri ini berkinerja lebih baik daripada tanpa tampilan yang diindeks?

    CREATE VIEW VI_test
    WITH SCHEMABINDING
    AS
        SELECT  t1.PK_ID1
               ,t1.something1
               ,t1.somethingelse1
               ,t2.PK_ID2
               ,t2.FK_ID1
               ,t2.something2
               ,t2.somethingelse2
        FROM    dbo.TB_test1 t1
                INNER JOIN dbo.TB_test2 t2 ON t1.PK_ID1 = t2.FK_ID1


    GO


    SELECT TOP 1 * FROM dbo.VI_test ORDER BY somethingelse1,somethingelse2


    GO
ManOnAMission
sumber

Jawaban:

12

Tampaknya mengabaikan indeks apa pun yang saya pasang

Kecuali Anda menggunakan SQL Server Enterprise Edition (atau yang setara, Percobaan dan Pengembang), Anda harus menggunakan WITH (NOEXPAND)referensi tampilan untuk menggunakannya. Bahkan, bahkan jika Anda menggunakan Enterprise, ada alasan bagus untuk menggunakan petunjuk itu .

Tanpa petunjuk, pengoptimal kueri (dalam Edisi Perusahaan) dapat membuat pilihan berbasis biaya antara menggunakan tampilan terwujud atau mengakses tabel dasar. Di mana tampilan sama besar dengan tabel dasar, perhitungan ini dapat mendukung tabel dasar.

Hal lain yang menarik adalah bahwa tanpa NOEXPANDpetunjuk, referensi tampilan selalu diperluas ke permintaan dasar sebelum optimasi dimulai. Saat pengoptimalan berlangsung, pengoptimal mungkin atau mungkin tidak dapat mencocokkan definisi yang diperluas kembali ke tampilan terwujud, tergantung pada aktivitas pengoptimalan sebelumnya. Ini hampir pasti bukan kasus dengan permintaan sederhana Anda, tetapi saya menyebutkannya untuk kelengkapan.

Jadi, menggunakan NOEXPANDpetunjuk tabel adalah opsi utama Anda, tetapi Anda mungkin juga berpikir tentang mematerialisasi kunci tabel dasar dan kolom yang diperlukan untuk memesan dalam tampilan. Buat indeks berkerumun unik pada kolom kunci gabungan, lalu indeks nonclustered terpisah pada kolom pemesanan.

Ini akan mengurangi ukuran tampilan terwujud, dan membatasi jumlah pembaruan otomatis yang harus dilakukan untuk menjaga tampilan disinkronkan dengan tabel dasar. Permintaan Anda kemudian dapat ditulis untuk mengambil kunci 1 teratas dalam urutan yang diperlukan dari tampilan (idealnya dengan NOEXPAND), lalu bergabung kembali ke tabel dasar untuk mengambil kolom yang tersisa menggunakan kunci dari tampilan.

Variasi lain adalah mengelompokkan tampilan pada kolom pemesanan dan tombol tabel, lalu menulis kueri untuk mengambil kolom non-tampilan secara manual dari tabel dasar menggunakan tombol. Pilihan terbaik untuk Anda tergantung pada konteks yang lebih luas. Cara yang baik untuk memutuskan adalah mengujinya dengan data nyata dan beban kerja.

Solusi dasar

CREATE VIEW VI_test
WITH SCHEMABINDING
AS
    SELECT
        t1.PK_ID1,
        t1.something1,
        t1.somethingelse1,
        t2.PK_ID2,
        t2.FK_ID1,
        t2.something2,
        t2.somethingelse2
    FROM dbo.TB_test1 t1
    INNER JOIN dbo.TB_test2 t2 
        ON t1.PK_ID1 = t2.FK_ID1;
GO
-- Brute force unique clustered index
CREATE UNIQUE CLUSTERED INDEX cuq 
ON dbo.VI_test 
    (somethingelse1, somethingelse2, PK_ID1, PK_ID2);
GO
SELECT TOP (1) * 
FROM dbo.VI_test WITH (NOEXPAND)
ORDER BY somethingelse1,somethingelse2;

Rencana eksekusi:

Indeks kekuatan kasar

Menggunakan indeks nonclustered

-- Minimal unique clustered index
CREATE UNIQUE CLUSTERED INDEX cuq 
ON dbo.VI_test 
    (PK_ID1, PK_ID2)
WITH (DROP_EXISTING = ON);
GO
-- Nonclustered index for ordering
CREATE NONCLUSTERED INDEX ix 
ON dbo.VI_test (somethingelse1, somethingelse2);

Rencana eksekusi:

Indeks tidak tercakup untuk pemesanan

Ada pencarian dalam paket ini, tetapi hanya digunakan untuk mengambil satu baris.

Tampilan Terindeks Minimal

ALTER VIEW VI_test
WITH SCHEMABINDING
AS
    SELECT
        t1.PK_ID1,
        t2.PK_ID2,
        t1.somethingelse1,
        t2.somethingelse2
    FROM dbo.TB_test1 t1
    INNER JOIN dbo.TB_test2 t2 
        ON t1.PK_ID1 = t2.FK_ID1;
GO
-- Unique clustered index
CREATE UNIQUE CLUSTERED INDEX cuq 
ON dbo.VI_test 
    (somethingelse1, somethingelse2, PK_ID1, PK_ID2);

Pertanyaan:

SELECT TOP (1)
    V.PK_ID1,
    TT1.something1,
    V.somethingelse1,
    V.PK_ID2,
    TT2.FK_ID1,
    TT2.something2,
    V.somethingelse2
FROM dbo.VI_test AS V WITH (NOEXPAND)
JOIN dbo.TB_test1 AS TT1 ON TT1.PK_ID1 = V.PK_ID1
JOIN dbo.TB_test2 AS TT2 ON TT2.PK_ID2 = V.PK_ID2
ORDER BY somethingelse1,somethingelse2;

Rencana eksekusi:

Rencana kueri akhir

Ini menunjukkan kunci-kunci tabel yang sedang diambil (satu baris mengambil dari tampilan indeks berkerumun secara berurutan) diikuti oleh dua pencarian baris tunggal pada tabel dasar untuk mengambil kolom yang tersisa.

Paul White 9
sumber