Как настроить индексированное представление при ВЫБОРЕ ТОП 1 с ORDER BY из разных таблиц

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

Таблица настройки выглядит следующим образом:

  • два стола
  • они соединены внутренним соединением по запросу выше
  • и упорядоченный по столбцу из первой, а затем по столбцу из второй таблицы по вышеуказанному запросу; выбран ТОП 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
ManOnAMission
источник

Ответы:

12

Кажется, он игнорирует любой индекс, который я ставлю на него

Если вы не используете SQL Server Enterprise Edition (или, что эквивалентно, Trial и Developer), вам нужно будет использовать WITH (NOEXPAND)ссылку на представление для ее использования. На самом деле, даже если вы используете Enterprise, есть веские причины использовать эту подсказку .

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

Другой интересный момент заключается в том, что NOEXPANDперед началом оптимизации ссылки на представления всегда расширяются до базового запроса. По мере продвижения оптимизации оптимизатор может или не сможет сопоставить расширенное определение с материализованным представлением, в зависимости от предыдущей операции оптимизации. Это почти наверняка не так с вашим простым запросом, но я упомяну его для полноты.

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

Это уменьшит размер материализованного представления и ограничит число автоматических обновлений, которые необходимо выполнить, чтобы синхронизировать представление с базовыми таблицами. Затем ваш запрос может быть написан так, чтобы получить первые 1 ключи в требуемом порядке из представления (в идеале с 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;

План выполнения:

Окончательный план запроса

Здесь показаны извлекаемые ключи таблиц (выборка одной строки из кластеризованного индекса представления по порядку), а затем два поиска по одной строке в базовых таблицах для выборки оставшихся столбцов.

Пол Уайт 9
источник