So richten Sie die indizierte Ansicht ein, wenn Sie TOP 1 mit ORDER BY aus verschiedenen Tabellen auswählen

11

Ich habe Probleme, im folgenden Szenario eine indizierte Ansicht einzurichten, damit die folgende Abfrage ohne zwei Clustered-Index-Scans ausgeführt wird. Immer wenn ich eine Indexansicht für diese Abfrage erstelle und sie dann verwende, scheint sie jeden Index zu ignorieren, den ich darauf gesetzt habe:

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

Das Tabellen-Setup ist wie folgt:

  • zwei Tische
  • Sie werden durch einen inneren Join durch die obige Abfrage verbunden
  • und sortiert nach einer Spalte aus der ersten und dann nach einer Spalte aus der zweiten Tabelle durch die obige Abfrage; Es wird nur TOP 1 ausgewählt
  • (Im folgenden Skript gibt es auch einige Zeilen zum Generieren von Testdaten, nur für den Fall, dass dies zur Reproduktion des Problems beiträgt.)

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

Die indizierte Ansicht sollte wahrscheinlich wie folgt definiert werden und die resultierende TOP 1-Abfrage ist unten. Aber welche Indizes benötige ich, damit diese Abfrage besser funktioniert als ohne die indizierte Ansicht?

    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
quelle

Antworten:

12

Es scheint jeden Index zu ignorieren, den ich darauf gesetzt habe

Sofern Sie nicht SQL Server Enterprise Edition (oder gleichwertig Testversion und Entwickler) verwenden, müssen Sie WITH (NOEXPAND)die Ansichtsreferenz verwenden, um sie verwenden zu können. Selbst wenn Sie Enterprise verwenden, gibt es gute Gründe, diesen Hinweis zu verwenden .

Ohne den Hinweis kann das Abfrageoptimierungsprogramm (in Enterprise Edition) eine kostenbasierte Wahl zwischen der Verwendung der materialisierten Ansicht oder dem Zugriff auf die Basistabellen treffen. Wenn die Ansicht so groß wie die Basistabellen ist, kann diese Berechnung die Basistabellen bevorzugen.

Ein weiterer interessanter Punkt ist, dass ohne a NOEXPAND Ansichtsreferenzen Hinweis immer auf die Basisabfrage erweitert werden, bevor die Optimierung beginnt. Im Verlauf der Optimierung kann der Optimierer die erweiterte Definition abhängig von der vorherigen Optimierungsaktivität möglicherweise wieder mit der materialisierten Ansicht abgleichen oder nicht. Dies ist mit ziemlicher Sicherheit bei Ihrer einfachen Anfrage nicht der Fall, aber ich erwähne sie der Vollständigkeit halber.

Die Verwendung des NOEXPANDTabellenhinweises ist also Ihre Hauptoption, aber Sie können auch darüber nachdenken, nur die Basistabellenschlüssel und die Spalten zu materialisieren, die für die Bestellung in der Ansicht erforderlich sind. Erstellen Sie einen eindeutigen Clustered-Index für die kombinierten Schlüsselspalten und anschließend einen separaten Nonclustered-Index für die Ordnungsspalten.

Dadurch wird die Größe der materialisierten Ansicht verringert und die Anzahl der automatischen Aktualisierungen begrenzt, die durchgeführt werden müssen, um die Ansicht mit den Basistabellen synchron zu halten. Ihre Abfrage kann dann geschrieben werden, um die Top-1-Schlüssel in der erforderlichen Reihenfolge aus der Ansicht abzurufen (idealerweise mit NOEXPAND), und dann wieder mit den Basistabellen verbunden werden, um alle verbleibenden Spalten mithilfe der Schlüssel aus der Ansicht abzurufen.

Eine andere Variante besteht darin, die Ansicht in den Ordnungsspalten und Tabellenschlüsseln zu gruppieren und dann die Abfrage zu schreiben, um die Nichtansichtsspalten mithilfe der Schlüssel manuell aus der Basistabelle abzurufen. Die beste Option für Sie hängt vom breiteren Kontext ab. Eine gute Möglichkeit, sich zu entscheiden, besteht darin, es mit den tatsächlichen Daten und der Arbeitslast zu testen.

Grundlösung

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;

Ausführungsplan:

Brute-Force-Index

Verwenden eines nicht gruppierten Index

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

Ausführungsplan:

Nicht gruppierter Index für die Bestellung

Dieser Plan enthält eine Suche, die jedoch nur zum Abrufen einer einzelnen Zeile verwendet wird.

Minimale indizierte Ansicht

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

Abfrage:

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;

Ausführungsplan:

Endgültiger Abfrageplan

Dies zeigt die Tabellenschlüssel, die abgerufen werden (ein einzelner Zeilenabruf aus dem Ansichtsclusterindex in der angegebenen Reihenfolge), gefolgt von zwei einzeiligen Suchvorgängen in den Basistabellen, um die verbleibenden Spalten abzurufen.

Paul White 9
quelle