Оптимизировать временную таблицу с помощью CTE - PullRequest
0 голосов
/ 04 марта 2019

Я создаю временную таблицу, чтобы установить уровень:

CREATE TABLE [#DesignLvl]
(
    [DesignKey] INT,
    [DesignLevel] INT
);

WITH RCTE AS 
(
    SELECT
        *,
        1 AS [Lvl]
    FROM 
        [Design]
    WHERE 
        [ParentDesignKey] IS NULL

    UNION ALL

    SELECT
        [D].*,
        [Lvl] + 1 AS [Lvl]
    FROM 
        [dbo].[Design] AS [D]
    INNER JOIN 
        [RCTE] AS [rc] ON [rc].[DesignKey] = [D].[ParentDesignKey]
)
INSERT INTO [#DesignLvl]
    SELECT
        [DesignKey], [Lvl]
    FROM 
        [RCTE]

После создания я использовал LEFT JOIN в действительно большом запросе как:

SELECT... 
FROM.. 
LEFT JOIN [#DesignLvl] AS [dl] ON d.DesignKey = dl.DesignKey
WHERE ...

Запрос работает, но производительность падаетзапрос сейчас слишком медленный, есть ли способ оптимизировать эту таблицу?

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

enter image description here

Я пытаюсь добавить индекс CLUSTERED как:

CREATE TABLE [#DesignLvl]
(
    [DesignKey] INT,
    [DesignLevel] INT
);

CREATE CLUSTERED INDEX ix_DesignLvl 
    ON [#DesignLvl] ([DesignKey], [DesignLevel]);

Также попробуйте:

    CREATE TABLE [#DesignLvl] 
( [DesignKey] INT INDEX IX1 CLUSTERED ,
 [DesignLevel] INT INDEX IX2 NONCLUSTERED );

Но я получаю тот же результат, выполнение которого заняло много времени

Ответы [ 9 ]

0 голосов
/ 12 марта 2019

Проблема может быть в том, что таблица Design огромна, и ее объединение без каких-либо основных условий фильтрации приводит к сканированию всей таблицы.

Поскольку вас интересует только очень мало столбцов, таких как designkey и parentdesignkey, попробуйте разбить запрос на заполнение данных (вставьте в #designlvl) на несколько частей.

Убедитесь, что у вас есть индекс (designkey, parentdesignkey)

INSERT INTO #DesignLevel SELECT DISTINCT DesignKey, 1 ОТ Designer ГДЕ ParentDesignKey IS NULL

INSERT INTO #DesignLevel SELECT DISTINParentDesignKey, Lvl + 1 ОТ дизайна, ГДЕ ParentDesignKey НЕ НЕДЕЙСТВИТЕЛЕН

0 голосов
/ 14 марта 2019
Make sure there are no nulls in DesignKey.ParentDesignKey and #DesignLv1.DesignKey 
columns and if so, use is not null constraint where you can. i have seen nulls to create cross joins.

If Design table is a transactional table that is being written to very frequently, rebuild indexes on this table frequently.

Create one non clustered index on Design.DesignKey and Design.ParentDesignKey in that sequence.

Create a non clustered index on #DesignLvl DesignKey.

If Design table is large ( > 10 million rows) and a whole bunch of columns, create a indexed view of the distinct columns that you need only for this query and use that.

Check System event log for disk read write failures on disk that has tempdb and (You should put the tempdb on either a RAID 1 or RAID 10 array as they're optimized for high-write applications.) from ( https://searchsqlserver.techtarget.com/tip/SQL-Server-tempdb-best-practices-increase-performance )
0 голосов
/ 11 марта 2019

Вы пробовали таблицы, оптимизированные для памяти ?Я использовал их в похожем процессе (рекурсивный CTE), и у меня были впечатляющие результаты.В SQL Server 2017 должен быть включен также Standard Edition.Сначала вам нужно создать файловую группу для данных, оптимизированных для памяти:

ALTER DATABASE MyDB 
ADD FILEGROUP mem_data CONTAINS MEMORY_OPTIMIZED_DATA; 
GO 
ALTER DATABASE MyDB 
ADD FILE (NAME = 'MemData', FILENAME = 'D:\Data\MyDB_MemData.ndf') TO FILEGROUP mem_data; 

Затем вы создаете (или конвертируете) свою таблицу:

CREATETABLE dbo.MemoryTable
(
Col1 INT IDENTITY PRIMARY KEY
...
)
WITH (MEMORY_OPTIMIZED = ON, DURABILITY = SCHEMA_AND_DATA);
0 голосов
/ 12 марта 2019

Вы пытались изменить SELECT * на SELECT DesignLevel, я обнаружил, что для широких рядов этого было достаточно, чтобы изменить план выполнения и выбрать использование активной очереди для сканирования индекса:

WITH RCTE AS 
(
    SELECT
        [DesignKey],
        1 AS [Lvl]
    FROM 
        [Design]
    WHERE 
        [ParentDesignKey] IS NULL

    UNION ALL

    SELECT
        [D].[DesignKey],
        [Lvl] + 1 AS [Lvl]
    FROM 
        [dbo].[Design] AS [D]
    INNER JOIN 
        [RCTE] AS [rc] ON [rc].[DesignKey] = [D].[ParentDesignKey]
)
INSERT INTO [#DesignLvl]
    SELECT
        [DesignKey], [Lvl]
    FROM 
        [RCTE]

План и тест SQL можно найти здесь: https://www.brentozar.com/pastetheplan/?id=BymxTD4wV

0 голосов
/ 11 марта 2019

Как уже говорили другие, не совсем понятно, какая часть вашего запроса медленная.Мы также не имеем ни малейшего представления о количестве записей (может быть 100, может быть 100 миллионов) или о фактическом времени (вы могли бы рассмотреть 10 секунд для медленной загрузки миллионов строк?!?).

Мы такжене знаю, насколько труден твой really big query;Насколько нам известно, он может быть медленным и без LEFT OUTER JOIN.

В любом случае, чтобы получить лучшее представление, что произойдет, если вы запустите это: (непроверенный код, вам может потребоваться исправить некоторые вещи)

DECLARE @level int = 0,
        @rowcount int

-- create working table to calculate levels
SELECT lvl = @level,
       D.[DesignKey]
  INTO #hierarchy
  FROM [Design] D
 WHERE D.[ParentDesignKey] IS NULL

SELECT @rowcount = @@ROWCOUNT

PRINT Convert(nvarchar, CURRENT_TIMESTAMP, 113) + ' - Loaded ' + Convert(nvarchar, @rowcount) + N' level ' + Convert(nvarchar, @rowcount) + ' records...'

CREATE UNIQUE CLUSTERED INDEX uq0 ON #hierarchy (lvl, [DesignKey])

WHILE @rowcount > 0
    BEGIN

        INSERT #hierarchy        
        SELECT lvl = @level + 1,
               D.[DesignKey]
          FROM #hierarchy t
          JOIN [Design] D
            ON D.[ParentDesignKey] = t.[DesignKey]
         WHERE t.lvl = @level

        SELECT @rowcount = @@ROWCOUNT,
               @level = @level + 1

        PRINT Convert(nvarchar, CURRENT_TIMESTAMP, 113) + ' - Loaded ' + Convert(nvarchar, @rowcount) + N' level ' + Convert(nvarchar, @rowcount) + ' records...'
    END

GO

-- we now have a lvl value for each DesignKey but the index is backwards for future use; so add index in the other direction
PRINT Convert(nvarchar, CURRENT_TIMESTAMP, 113) + ' - re-indexing...'
CREATE UNIQUE INDEX uq1 ON #hiearchy ([DesignKey]) INCLUDE (lvl) WITH (FILLFACTOR = 100)

PRINT Convert(nvarchar, CURRENT_TIMESTAMP, 113) + ' - done.'

GO

PRINT Convert(nvarchar, CURRENT_TIMESTAMP, 113) + ' - Starting query...'

-- actual use:
;WITH DesignLvlCTE
   AS (SELECT h.lvl, D.*
         FROM [Design] D
         JOIN #hierarchy h
           ON h.[DesignKey] = D.[DesignKey])
SELECT... 
 INTO #result -- leave this in to exclude overhead time of client
FROM.. 
LEFT JOIN DesignLvlCTE AS [dl] ON d.DesignKey = dl.DesignKey
WHERE ...

PRINT Convert(nvarchar, CURRENT_TIMESTAMP, 113) + ' - Done fetching data.'

-- get results
SELECT * FROM #result

PRINT Convert(nvarchar, CURRENT_TIMESTAMP, 113) + ' - Done.'

-- DROP TABLE #result
0 голосов
/ 08 марта 2019

попробуйте @table, вы запрашиваете временную таблицу памяти вместо временной таблицы диска

declare @DesignLvl table
(
    [DesignKey] INT,
    [DesignLevel] INT
);

WITH RCTE AS 
(
    SELECT
        *,
        1 AS [Lvl]
    FROM 
        [Design]
    WHERE 
        [ParentDesignKey] IS NULL

    UNION ALL

    SELECT
        [D].*,
        [Lvl] + 1 AS [Lvl]
    FROM 
        [dbo].[Design] AS [D]
    INNER JOIN 
        [RCTE] AS [rc] ON [rc].[DesignKey] = [D].[ParentDesignKey]
)
INSERT INTO @DesignLvl
    SELECT
        [DesignKey], [Lvl]
    FROM 
        [RCTE]

может немного помочь, о скольких строках мы говорим и какой выпуск sql server?@@ версия?

0 голосов
/ 07 марта 2019

Ваш вопрос не завершен, запрос медленный, но какая часть запроса медленная?

CTEQuery или LEFT JOIN in really big query

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

Подробно расскажите о большом запросе.

также дайте нам знать, если какой-либо UDF включен в условие соединения.

Почему вы left join Temp таблица?ПОЧЕМУ НЕ INNER JOIN

Проверьте производительность отдельно или CTE и Big Query.

После использования [D].[ParentDesignKey] is not null в рекурсивной части,

SELECT
        [D].*,
        [Lvl] + 1 AS [Lvl]
    FROM 
        [dbo].[Design] AS [D]
    INNER JOIN 
        [RCTE] AS [rc] ON [rc].[DesignKey] = [D].[ParentDesignKey]
and [D].[ParentDesignKey] is not null

ПРИМЕЧАНИЕ: В CTE используйте только те столбцы, которые требуются.

Если возможно Pre- Calculate [Lvl], потому что Recursive CTE производительность особенно плохая, с большим количеством записей.

Сколько строкбудет обрабатываться в среднем в каждом запросе CTE?

Если временная таблица будет содержать более 100 rows, тогда да, создайте кластерный индекс для нее,

  CREATE CLUSTERED INDEX ix_DesignLvl 
        ON [#DesignLvl] ([DesignKey], [DesignLevel]);

Если вы не используете [DesignLevel] в состоянии соединения, затем удалите из индекса.

Кроме того, покажите индекс таблицы [dbo].[Design] и несколько данных DesignKey и ParentDesignKey.

Существует несколько причин для получения Index Scan, одна изони равны Selectivity of Key.

Таким образом, у одного DesignKey может быть сколько строк, а у одного ParentDesignKey может быть сколько строк?

Таким образом, в зависимости от ответа выше Create Composite Clustered Index на оба ключатаблицы [dbo].[Design]

Так что считайте мой ответ неполным, я обновлю его соответственно.

0 голосов
/ 07 марта 2019

Производительность может быть ниже, поскольку доступ к кластерному индексу в таблице dbo.Design осуществляется во вложенном цикле.Согласно смете, база данных тратит 66% своего времени на сканирование этого индекса.Зацикливание на этом только ухудшает ситуацию.

См. связанный вопрос

Попробуйте изменить индекс на dbo.Design на некластеризованный или попробуйте создать другую временную таблицу снекластеризованный индекс и используйте его для рекурсивного запроса:

CREATE TABLE [#DesignTemp]
(
    ParentDesignKey INT,
    DesignKey INT
);

-- Insert the data, then create the index.
INSERT INTO [#DesignTemp]
SELECT
ParentDesignKey,
DesignKey
FROM [dbo].[Design];

COMMIT;

-- Try this index, or create indexes for individual columns if the plan works better at high volumes.
CREATE NONCLUSTERED INDEX ix_DesignTemp1 ON [#DesignTemp] (ParentDesignKey, DesignKey);

CREATE TABLE [#DesignLvl]
(
    [DesignKey] INT,
    [DesignLevel] INT
);

WITH RCTE AS 
(
    SELECT
        *,
        1 AS [Lvl]
    FROM 
        [DesignTemp]
    WHERE 
        [ParentDesignKey] IS NULL

    UNION ALL

    SELECT
        [D].*,
        [Lvl] + 1 AS [Lvl]
    FROM 
        [DesignTemp] AS [D]
    INNER JOIN 
        [RCTE] AS [rc] ON [rc].[DesignKey] = [D].[ParentDesignKey]
)
INSERT INTO [#DesignLvl]
    SELECT
        [DesignKey], [Lvl]
    FROM 
        [RCTE];
0 голосов
/ 04 марта 2019

Согласно моему тестированию, опубликованному в этой статье , цикл на основе множеств может дать вам повышение производительности по сравнению с рекурсивным CTE.

DECLARE @DesignLevel int = 0;

INSERT INTO [#DesignLvl]
SELECT [DesignKey], 1
FROM [RCTE];

WHILE @@ROWCOUNT > 0
BEGIN
    SET @DesignLevel += 1;

    INSERT INTO [#DesignLvl]
    SELECT [D].[DesignKey], dl.DesignLevel
    FROM [dbo].[Design] AS [D]
    JOIN [#DesignLvl] AS [dl] ON [dl].[DesignKey] = [D].[ParentDesignKey]
    WHERE dl.DesignLevel = @DesignLevel;
END;
...