Як налаштувати індексований вигляд при виборі ТОП 1 за допомогою ЗАМОВЛЕННЯ з різних таблиць


11

Я намагаюся налаштувати індексований вигляд у наступному сценарії, щоб наступний запит виконувався без двох кластерних сканувань індексів. Кожен раз, коли я створюю індексний вигляд для цього запиту, а потім використовую його, він, мабуть, ігнорує будь-який індекс, який я ставлю на нього:

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

Установка таблиці така:

  • дві таблиці
  • до них приєднується внутрішнє з'єднання за запитом, поданим вище
  • і впорядковано стовпцем з першої, а потім стовпчиком з другої таблиці за запитом вище; вибрано лише TOP 1
  • (у нижченаведеному сценарії також є кілька рядків для генерування тестових даних, на всякий випадок, якщо це допомагає відтворити проблему)

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

Індексований вигляд, мабуть, слід визначити наступним чином, і отриманий запит TOP 1 знаходиться нижче. Але які індекси мені потрібні, щоб цей запит працював краще, ніж без індексованого перегляду?

    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

Відповіді:


12

Здається, ігноруйте будь-який індекс, який я наклав на нього

Якщо ви не використовуєте SQL Server Enterprise Edition (або, що еквівалентно, пробну версію та розробник), вам потрібно буде скористатись WITH (NOEXPAND)посиланням на представлення даних, щоб ним скористатися. Насправді, навіть якщо ви використовуєте Enterprise, є вагомі причини використовувати цей підказку .

Без підказки оптимізатор запитів (у Enterprise Edition) може зробити вибір на основі витрат між використанням матеріалізованого представлення даних або зверненням до базових таблиць. Якщо представлення великого розміру, як базові таблиці, цей розрахунок може сприяти базовим таблицям.

Інша цікава точка полягає в тому, що без NOEXPANDпідказки посилання на перегляд завжди розширюються на базовий запит до початку оптимізації. По мірі просування оптимізатора оптимізатор може або не зможе співставити розширене визначення назад з матеріалізованим поданням, залежно від попередньої активності оптимізації. Це майже точно не стосується вашого простого запиту, але я згадую його для повноти.

Отже, використання NOEXPANDпідказки таблиці є вашим головним варіантом, але ви також можете подумати про просто матеріалізацію основних клавіш таблиці та стовпців, необхідних для замовлення у поданні. Створіть унікальний кластерний індекс на комбінованих стовпцях клавіш, потім окремий некластеризований індекс на стовпчиках замовлення.

Це зменшить розмір матеріалізованого перегляду та обмежить кількість автоматичних оновлень, які необхідно здійснити, щоб подання синхронізувалося з базовими таблицями. Потім ваш запит може бути написаний для отримання перших перших клавіш у необхідному порядку з виду (в ідеалі з NOEXPAND), а потім приєднатися до базових таблиць, щоб отримати будь-які інші колонки, використовуючи клавіші з виду.

Інший варіант полягає в кластеризації подання на стовпці замовлення та клавіші таблиць, а потім запит запиту, щоб вручну отримати стовпці без перегляду з базової таблиці за допомогою клавіш. Найкращий варіант для вас залежить від більш широкого контексту. Хороший спосіб прийняти рішення - протестувати його з реальними даними та навантаженням.

Основне рішення

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;

План виконання:

Індекс грубої сили

Використання некластеризованого індексу

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

План виконання:

Некластерний індекс для замовлення

У цьому плані є пошук, але він використовується лише для отримання одного рядка.

Мінімальний індексований вид

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

Запит:

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;

План виконання:

Остаточний план запитів

Це показує, що отримані ключі таблиці (отримання одного рядка з індексу кластеризованого перегляду в порядку) з наступними двома однорядними підходами на базових таблицях для отримання решти стовпців.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.