Как я могу решить мою проблему производительности в моей хранимой процедуре? - PullRequest
1 голос
/ 19 июня 2019

У меня большие проблемы с хранимой процедурой.Потому что, когда я проверял результат моего теста, я понял, что «MatchxxxReferencesByIds» имеет «240,25 мс» Среднее значение LastElapsedTimeInSecond.Можете ли вы проверить мою хранимую процедуру?Мне нужна твоя помощь, чтобы улучшить мой sp.

ALTER PROCEDURE [Common].[MatchxxxReferencesByIds]
    (@refxxxIds VARCHAR(MAX),
     @refxxxType NVARCHAR(250))
BEGIN
    SET NOCOUNT ON;

    BEGIN TRAN

    DECLARE @fake_tbl TABLE (xxxid NVARCHAR(50))

    INSERT INTO @fake_tbl  
        SELECT LTRIM(RTRIM(split.a.value('.', 'NVARCHAR(MAX)'))) AS fqdn   
        FROM 
            (SELECT 
                 CAST ('<M>' + REPLACE(@refxxxIds, ',', '</M><M>') + '</M>' AS XML) AS data
            ) AS a   
        CROSS APPLY 
            data.nodes ('/M') AS split(a)

    SELECT [p].[ReferencedxxxId]  
    FROM [Common].[xxxReference] AS [p] 
    WHERE ([p].[IsDeleted] = 0) 
      AND (([p].[ReferencedxxxType] COLLATE Turkish_CI_AS  = @refxxxType COLLATE Turkish_CI_AS ) 
      AND [p].[ReferencedxxxId] COLLATE Turkish_CI_AS  IN (SELECT ft.xxxid COLLATE Turkish_CI_AS FROM @fake_tbl ft))

    COMMIT;
END;

Ответы [ 4 ]

4 голосов
/ 19 июня 2019

Можно только делать предположения, не зная схемы таблицы, индексы и размеры данных.

Жесткое кодирование может препятствовать использованию оптимизатором запросов любых индексов в столбце ReferencedEntityId.Имя поля и примеры данных '423423,423423,423432,23423' предполагают, что это в любом случае числовой столбец (int? Bigint?).Сортировка не требуется, а тип столбца переменной должен соответствовать типу таблицы.

Наконец, a.value может возвращать int или bigint напрямую, что означает, что запрос на разбиение может быть переписан как:

declare @refEntityIds nvarchar(max)='423423,423423,423432,23423';

DECLARE @fake_tbl TABLE (entityid bigint PRIMARY KEY, INDEX IX_TBL(Entityid))

INSERT INTO @fake_tbl  
SELECT split.a.value('.', 'bigint') AS fqdn   
FROM 
    (SELECT 
            CAST ('<M>' + REPLACE(@refEntityIds, ',', '</M><M>') + '</M>' AS XML) AS data
    ) AS a   
CROSS APPLY 
    data.nodes ('/M') AS split(a)

Входные данные содержат несколько дубликатов, поэтому entityid не может быть ПЕРВИЧНЫМ КЛЮЧОМ.

После этого запрос может измениться на:

SELECT [p].[ReferencedEntityId]  
FROM [Common].[EntityReference] AS [p] 
WHERE [p].[IsDeleted] = 0
  AND [p].[ReferencedEntityType] COLLATE Turkish_CI_AS  = @refEntityType COLLATE Turkish_CI_AS 
  AND [p].[ReferencedEntityId]  IN (SELECT ft.entityid FROM @fake_tbl ft)

Следующая проблема - жестко закодированная сортировка.Если это не соответствует фактическому сопоставлению столбца, это не позволяет серверу использовать любые индексы, которые охватывают этот столбец.Как это исправить, зависит от фактических данных статистики.Возможно, сортировка столбца должна измениться или, возможно, строк после фильтрации по ReferencedEntityId так мало, что это не принесет пользы.

Наконец, IsDeleted не может быть проиндексирован.Это либо bit столбцы со значениями 1/0, либо другой числовой столбец, который по-прежнему содержит 0/1.Индекс, который настолько плох при выборе строк, не будет использоваться оптимизатором запросов, потому что на самом деле он быстрее только для сканирования строк, возвращаемых другими условиями.

Общее правило - ставитьсамый селективный индексный столбец первый.База данных объединяет все столбцы, чтобы создать одно «ключевое» значение и построить из него индекс B + -дерева.Чем более избирателен ключ, тем меньше нужно сканировать индексных узлов.

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

Все это означает, что EntityReference должен иметь такой индекс.

CREATE NONCLUSTERED INDEX IX_EntityReference_ReferenceEntityID  
    ON Common.EntityReference (ReferenceEntityId, ReferenceEntityType)  
    WHERE IsDeleted =0; 

Если параметры сортировки не не совпадают, ReferenceEntityType не будет использоваться для поиска.Если это наиболее распространенный случай, мы можем удалить ReferenceEntityType из индекса и поместить его в предложение INCLUDE.Поле не будет частью ключа индекса, хотя оно все равно будет доступно для фильтрации без необходимости загрузки данных из фактической таблицы:

CREATE NONCLUSTERED INDEX IX_EntityReference_ReferenceEntityID  
    ON Common.EntityReference (ReferenceEntityId)  
    INCLUDE(ReferenceEntityType)
    WHERE IsDeleted =0; 

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

2 голосов
/ 19 июня 2019

Исходя из плана выполнения хранимой процедуры, то, что заставляет ее выполняться медленно, - это та часть, где вы собираетесь работать с XML.

Давайте переосмыслим решение:

Я создал такую ​​таблицу:

CREATE TABLE [Common].[EntityReference]
(
    IsDeleted BIT,
    ReferencedEntityType VARCHAR(100),
    ReferencedEntityId VARCHAR(10)
);
GO

и манипулировал ею следующим образом (вставьте в нее записи 1М):

DECLARE @i INT = 1000000;
DECLARE @isDeleted BIT,
        @ReferencedEntityType VARCHAR(100),
        @ReferencedEntityId VARCHAR(10);
WHILE @i > 0
BEGIN
    SET @isDeleted =(SELECT @i % 2);
    SET @ReferencedEntityType = 'TEST' + CASE WHEN @i % 2 = 0 THEN '' ELSE CAST(@i % 2 AS VARCHAR(100)) END;
    SET @ReferencedEntityId = CAST(@i AS VARCHAR(10));
    INSERT INTO [Common].[EntityReference]
    (
        IsDeleted,
        ReferencedEntityType,
        ReferencedEntityId
    )
    VALUES (@isDeleted, @ReferencedEntityType, @ReferencedEntityId);

    SET @i = @i - 1;
END;

позволяет проанализировать ваш код:

У вас есть ввод с разделителями-запятыми (@refEntityIds), который вы хотите разделить, а затем выполнить запрос к этим значениям.(стоимость поддерева вашего SP на моем ПК составляет около 376). Для этого у вас есть разные подходы:

1.Передача табличной переменной в хранимую процедуру, которая содержит refEntityIds

2.ИспользуйтеФункция STRING_SPLIT для разделения строки Давайте посмотрим на пример запроса:

INSERT INTO @fake_tbl
      SELECT value
        FROM STRING_SPLIT(@refEntityIds, ',');

Используя это, вы получите значительное улучшение производительности вашего кода. (Стоимость поддерева: 6,19 без следующих индексов) НО эту функциюнедоступно в SQL Server 2008!

Вы можете использовать замену для этой функции (прочитайте это: https://stackoverflow.com/a/54926996/1666800) и измените свой запрос на это (стоимость поддерева по-прежнему составляет около 6,19):

INSERT INTO @fake_tbl
    SELECT value FROM dbo.[fn_split_string_to_column](@refEntityIds,',')

В этом случае вы снова увидите заметное улучшение производительности.

Вы также можете создать некластеризованный индекс для таблицы [Common].[EntityReference], в которой немного повышение производительности тоже. Но, пожалуйста, подумайте о создании индекса, прежде чем создавать его, это может оказать негативное влияние на ваши операции DML:

CREATE NONCLUSTERED INDEX [Index Name] ON [Common].[EntityReference]
(
    [IsDeleted] ASC
)
INCLUDE ([ReferencedEntityType],[ReferencedEntityId]) 

В случае, если у меня нет этогоindex (Предположим, что я заменил ваше раздельное решение на мое), стоимость поддерева: 6.19. Когда я добавляю вышеупомянутый индекс, стоимость поддерева снижается до 4.70, и, наконец, когда я изменяю индекс на следующий, стоимость поддерева составляет 5.16.

CREATE NONCLUSTERED INDEX [Index Name] ON [Common].[EntityReference]
(
    [ReferencedEntityType] ASC,
    [ReferencedEntityId] ASC
)
INCLUDE ([IsDeleted]) 

Благодаря @ PanagiotisKanavos следующий индекс будет работать даже лучше, чем вышеупомянутые (стоимость поддерева: 3,95):

CREATE NONCLUSTERED INDEX IX_EntityReference_ReferenceEntityID  
    ON Common.EntityReference (ReferencedEntityId)  
    INCLUDE(ReferencedEntityType)
    WHERE IsDeleted =0; 

Также обратите внимание, что,использование транзакции для локальной табличной переменной практически не имеет никакого эффекта, и, вероятно, вы можете просто проигнорировать ее.

2 голосов
/ 19 июня 2019

Если [p]. [ReferencedEntityId] будет целым числом, вам не нужно применять предложение COLLATE.Вы можете напрямую применить в состоянии.

  1. Вы можете перейти к простым значениям, разделенным запятыми, в список целых чисел, используя функции с табличными значениями.Есть много образцов .Сохраняйте тип данных идентификатора как целое, чтобы избежать применения параметров сортировки.
[p].[ReferencedEntityId] IN (SELECT ft.entityid AS FROM @fake_tbl ft))
2 голосов
/ 19 июня 2019
  1. Я не думаю, что вам нужен ТРАН. Вы просто «измельчаете» значения, разделенные запятыми, в таблицу @variable. и делать выбор. ТРАН здесь не нужен.

  2. попробуйте exists

SELECT [p].[ReferencedEntityId]  
    FROM [Common].[EntityReference] AS [p] 
    WHERE ([p].[IsDeleted] = 0) 
      AND (([p].[ReferencedEntityType] COLLATE Turkish_CI_AS  = @refEntityType COLLATE Turkish_CI_AS ) 
      AND EXISTS (SELECT 1 FROM @fake_tbl ft WHERE ft.entityid COLLATE Turkish_CI_AS =  [p].[ReferencedEntityId] COLLATE Turkish_CI_AS  )

3

См. https://www.sqlshack.com/efficient-creation-parsing-delimited-strings/

для различных способов анализа строки с разделителями.

цитата из статьи:

Встроенная функция Microsoft обеспечивает удобное решение и, кажется, хорошо работает. Это не быстрее, чем XML, но это явно был написан таким образом, чтобы обеспечить простой для оптимизации план выполнения. Логические чтения также выше. Пока мы не можем заглянуть под охватывает и точно, как Microsoft реализовала эту функцию, мы в хотя бы удобство функции разбивать строки, которые поставляется с SQL Server. Обратите внимание, что разделитель перешел в этот функция должна иметь размер 1. Другими словами, вы не можете использовать STRING_SPLIT с многосимвольным разделителем, таким как ‘”, ”’.

  1. опубликуйте скриншот вашего плана выполнения. если у вас нет правильного индекса (или у вас есть «подсказки», которые не позволяют использовать индексы) .. ваш запрос никогда не будет работать хорошо.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...