Нужна помощь в улучшении производительности запросов - PullRequest
1 голос
/ 22 сентября 2011

Мне нужна помощь с улучшением производительности следующего SQL-запроса. Проект базы данных этого приложения основан на старых объектах мэйнфреймов. Все, что делает запрос, это возвращает список клиентов на основе некоторых критериев поиска:

  • @Advisers: возвращает только клиентов, которые были захвачены этим советником.
  • @outlets: просто проигнорируйте это
  • @searchtext: (firstname, surname, suburb, policy number) любая комбинация этого

Я создаю временную таблицу, затем запрашиваю все соответствующие таблицы, создаю свой собственный набор данных, а затем вставляю этот набор данных в легко понятную таблицу (@clients)

Этот запрос выполняется 20 секунд и в настоящее время возвращает только 7 строк!

Скриншот всех таблиц можно найти здесь: Количество записей в таблице

Есть идеи, где я могу начать оптимизировать этот запрос?

ALTER PROCEDURE [dbo].[spOP_SearchDashboard] 
    @advisers varchar(1000),
    @outlets varchar(1000),
    @searchText varchar(1000)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;

-- Set the prefixes to search for (firstname, surname, suburb, policy number)

DECLARE @splitSearchText varchar(1000)
SET     @splitSearchText = REPLACE(@searchText, ' ', ',')

DECLARE @AdvisersListing TABLE
(
    adviser varchar(200)
) 

DECLARE @SearchParts TABLE
(
    prefix varchar(200)
) 

DECLARE @OutletListing TABLE
(
    outlet varchar(200)
) 

INSERT INTO @AdvisersListing(adviser)
SELECT part as adviser FROM SplitString (@advisers, ',')

INSERT INTO @SearchParts(prefix)
SELECT part as prefix FROM SplitString (@splitSearchText, ',')

INSERT INTO @OutletListing(outlet)
SELECT part as outlet FROM SplitString (@outlets, ',')

DECLARE @Clients TABLE
(
    source varchar(2),
    adviserId bigint, 
    integratedId varchar(50), 
    rfClientId bigint, 
    ifClientId uniqueidentifier, 
    title varchar(30), 
    firstname varchar(100), 
    surname varchar(100), 
    address1 varchar(500), 
    address2 varchar(500), 
    suburb varchar(100), 
    state varchar(100), 
    postcode varchar(100), 
    policyNumber varchar(100), 
    lastAccess datetime,
    deleted bit
)

    INSERT INTO @Clients
       SELECT 
          source, adviserId, integratedId, rfClientId, ifClientId, title, 
          firstname, surname, address1, address2, suburb, state, postcode, 
          policyNumber, max(lastAccess) as lastAccess, deleted
       FROM 
          (SELECT DISTINCT
              'RF' as Source,
              advRel.SourceEntityId as adviserId,
              cast(pe.entityId as varchar(50)) AS IntegratedID,
              pe.entityId AS rfClientId, 
              cast(ifClient.Id as uniqueidentifier) as ifClientID,
              ISNULL(p.title, '') AS title,
              ISNULL(p.firstname, '') AS firstname, 
              ISNULL(p.surname, '') AS surname, 
              ISNULL(ct.address1, '') AS address1, 
              ISNULL(ct.address2, '') AS address2, 
              ISNULL(ct.suburb, '') AS suburb, 
              ISNULL(ct.state, '') AS state, 
              ISNULL(ct.postcode, '') AS postcode,
              ISNULL(contract.policyNumber,'') AS policyNumber,
              coalesce(pp.LastAccess, d_portfolio.dateCreated, pd.dateCreated) AS lastAccess,
              ISNULL(client.deleted, 0) as deleted
           FROM     
               tbOP_Entity pe 
           INNER JOIN tbOP_EntityRelationship advRel ON pe.EntityId = advRel.TargetEntityId  
                                                     AND advRel.RelationshipId = 39 
           LEFT OUTER JOIN tbOP_Data pd ON pe.EntityId = pd.entityId 
           LEFT OUTER JOIN tbOP__Person p ON pd.DataId = p.DataId
           LEFT OUTER JOIN tbOP_EntityRelationship ctr ON pe.EntityId = ctr.SourceEntityId 
                                                       AND ctr.RelationshipId = 79 
           LEFT OUTER JOIN tbOP_Data ctd ON ctr.TargetEntityId = ctd.entityId 
           LEFT OUTER JOIN tbOP__Contact ct ON ctd.DataId = ct.DataId 
           LEFT OUTER JOIN tbOP_EntityRelationship  ppr ON pe.EntityId = ppr.SourceEntityId 
                                                        AND ppr.RelationshipID = 113 
           LEFT OUTER JOIN tbOP_Data ppd ON ppr.TargetEntityId = ppd.EntityId 
           LEFT OUTER JOIN tbOP__Portfolio pp ON ppd.DataId = pp.DataId 
           LEFT OUTER JOIN tbOP_EntityRelationship er_policy ON ppd.EntityId = er_policy.SourceEntityId 
                                                             AND er_policy.RelationshipId = 3
           LEFT OUTER JOIN tbOP_EntityRelationship er_contract ON er_policy.TargetEntityId = er_contract.SourceEntityId AND er_contract.RelationshipId = 119
           LEFT OUTER JOIN tbOP_Data d_contract ON er_contract.TargetEntityId = d_contract.EntityId
           LEFT OUTER JOIN tbOP__Contract contract ON d_contract.DataId = contract.DataId
           LEFT JOIN tbOP_Data d_portfolio ON ppd.EntityId = d_portfolio.EntityId
           LEFT JOIN tbOP__Portfolio pt ON d_portfolio.DataId = pt.DataId
           LEFT JOIN tbIF_Clients ifClient on pe.entityId = ifClient.RFClientId
           LEFT JOIN tbOP__Client client on client.DataId = pd.DataId
        where 
           p.surname <> '' 
           AND (advRel.SourceEntityId IN (select adviser from @AdvisersListing)
                OR 
                pp.outlet COLLATE SQL_Latin1_General_CP1_CI_AS in (select outlet from @OutletListing)
               ) 
       ) as RFClients
group by 
        source, adviserId, integratedId, rfClientId, ifClientId, title, 
        firstname, surname, address1, address2, suburb, state, postcode, 
        policyNumber, deleted

SELECT * FROM @Clients --THIS ONLY RETURNS 10 RECORDS WITH MY CURRENT DATASET

END

Ответы [ 3 ]

3 голосов
/ 31 декабря 2011

Уточняющие вопросы

Какова ОСНОВНАЯ часть данных, к которой вы обращаетесь - консультанты, поисковый текст, торговые точки?

Такое ощущение, что ваши критерии позволяютдля пользователей, чтобы искать по-разному.Sproc всегда будет использовать точно такой же план для каждого вашего вопроса.Вы получаете более высокую производительность, используя несколько sprocs - каждый из которых настроен на определенный сценарий поиска (то есть, я уверен, что вы могли бы написать что-то невероятно быстрое для запросов только по номеру политики).

Если вы можете разделить свой текст поиска наИНДИВИДУАЛЬНЫЕ параметры, тогда вы сможете:

  • Поиск связей советника, соответствующих вашему предоставленному списку - сохранить во временной таблице (или табличной переменной).
  • ЕСЛИ ЛЮБЫЕ фамилии были указаны тогдаудалите все записи из temp, которые не предназначены для людей с указанными вами именами.
  • Повторите эти действия для других списков критериев - все время сокращая ваши временные записи.
  • ТО присоединитесь к внешнему соединениюи верните результаты.

В своих заметках вы говорите, что торговые точки можно игнорировать.Если это так, то удаление их упростит ваш запрос.Предложение «или» в вашем примере означает, что SQL-серверу необходимо найти ВСЕ отношения для ВСЕХ портфелей, прежде чем он сможет реально приступить к фильтрации тех результатов, которые вам действительно нужны.

Прерываниезапрос вверх

Большинство запросов состоит из внешних объединений, которые не участвуют в фильтрации.Попробуйте переместить эти объединения в отдельный выбор (т. Е. После того, как вы применили все свои критерии).Когда SQL-сервер видит много таблиц, он отключает некоторые из возможных оптимизаций.Таким образом, ваш первый шаг (при условии, что вы всегда указываете советники) просто:

SELECT advRel.SourceEntityId as adviserId, 
    advRel.TargetEntityId AS rfClientId
INTO #temp1
FROM @AdvisersListing advisers
INNER JOIN tbOP_EntityRelationship advRel
ON advRel.SourceEntityId = advisers.adviser
AND advRel.RelationshipId = 39;

Ссылка на tbOP_Entity (псевдоним "pe") не выглядит так, как будто она необходима для его данных.Таким образом, вы должны иметь возможность заменить все ссылки на «pe.EntityId» на «advRel.TargetEntityId».

Предложение DISTINCT и GROUP-BY, вероятно, пытаются достичь одного и того же - и оба онидействительно дорогоОбычно вы найдете ОДИН из них, когда предыдущий разработчик не смог получить правильные результаты.Избавьтесь от них - проверьте свои результаты - если вы получаете дубликаты, попробуйте отфильтровать дубликаты.Вам может понадобиться ОДНА из них, если у вас есть временные данные - вам определенно не нужны оба.

Индексы

Убедитесь, что столбец @ AdvisersListing.adviser одинаковDateType как SourceEntityId и этот SourceEntityId индексируется.Если столбец имеет другой тип данных, то SQL-сервер не захочет использовать индекс (поэтому вы должны изменить тип данных в @AdvisersListing).

Таблицы tbOP_EntityRelationship звучат так, как будто они должны иметьиндексировать что-то вроде:

CREATE UNIQUE INDEX advRel_idx1 ON tbOP_EntityRelationship (SourceEntityId,
    RelationshipId, TargetEntityId);

Если это существует, то SQL-сервер должен иметь возможность получать все, что ему нужно, ТОЛЬКО переходя на страницы индекса (а не на страницы таблиц).Это известно как индекс «покрытия».

Должен быть немного другой индекс для tbOP_Data (при условии, что он имеет кластеризованный первичный ключ в DataId):

CREATE INDEX tbOP_Data_idx1 ON tbOP_Data (entityId) INCLUDE (dateCreated);

SQL-сервер будетхранить ключи из кластерного индекса таблицы (который я предполагаю, будет DataId) вместе со значением «dateCreated» на листах индекса.Итак, снова у нас есть «покрывающий» индекс.

Большинство других таблиц (tbOP__Client и т. Д.) Должны иметь индексы для DataId.

План запроса

К сожалению, я не смог увидеть картину плана объяснения (наш брандмауэр съел ее).Однако один полезный совет - навести указатель мыши на некоторые линии соединения.Он сообщает вам, сколько записей будет доступно.

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

Структура базы данных

Он был разработан как база данных транзакций.Уровень нормализации (и все EntityRelationship - this и Data - которые действительно болезненны для отчетности).Вам действительно нужно подумать о том, чтобы иметь отдельную базу данных отчетов, которая распределяет часть этой информации в более удобную структуру.

Если вы запускаете отчеты непосредственно для своей производственной базы данных, я бы ожидал кучу проблем с блокировками и конфликт ресурсов..

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

2 голосов
/ 22 сентября 2011

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

0 голосов
/ 22 сентября 2011

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

DECLARE @Xadvisers varchar(1000)
DECLARE @Xoutlets varchar(1000)
DECLARE @XsearchText varchar(1000)

SET @Xadvisers = @advisers
SET @Xoutlets = @outlets
SET @XsearchText = @searchText

Поверьте, я тщательно его протестировал, и он помогает со сложными сценариями.Что-то о том, как SQL Server обрабатывает локальные переменные.Удачи!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...