【问题标题】:Need help improving query performance需要帮助提高查询性能
【发布时间】:2011-09-22 00:52:25
【问题描述】:

我需要帮助来提高以下 SQL 查询的性能。此应用程序的数据库设计基于 OLD 大型机实体设计。查询所做的只是根据一些搜索条件返回一个客户列表:

  • @Advisers:只返回被该顾问捕获的客户。
  • @outlets:忽略这个
  • @searchtext: (firstname, surname, suburb, policy number) 任意组合

我正在做的是创建一个临时表,然后查询所有涉及的表,创建我自己的数据集,然后将该数据集插入到一个易于理解的表中 (@clients)

此查询需要 20 秒 执行,目前只返回 7 行!

所有表计数的屏幕截图可以在这里找到:Table Record Count

我可以从哪里开始优化此查询?

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

【问题讨论】:

  • 万岁!截图。不幸的是,这是错误的:实际的执行计划是什么样的? SQL Management Studio 提供了哪些提示?
  • 我认为这种性能只是这种数据模型的现实,您使用元数据来创建关联而不是真实的关系。
  • SQL执行计划PDF可以在这里下载:docs.google.com/…
  • 如果您只是立即从中选择所有内容,为什么还要使用临时表?
  • JohnFX:我没有粘贴整个查询,我删除了查询的其余部分以简化查找问题。其余的查询只需要 1s 执行,而上面的查询则需要 20s。临时表的原因很简单,有另一个查询将记录插入临时表,来自另一个数据库。所以它基本上是从两个数据库中收集客户。

标签: sql-server performance


【解决方案1】:

澄清问题

您要查询的主要数据是什么 - 顾问、搜索文本、网点?

感觉您的条件允许用户以多种不同方式进行搜索。对于您提出的每个问题,存储程序将始终使用完全相同的计划。通过使用多个存储过程,您可以获得更好的性能 - 每个存储过程都针对特定的搜索场景进行了调整(即我敢打赌,您可以编写一些非常快的东西来仅通过策略编号进行查询)。

如果您可以将搜索文本分成单独的参数,那么您可以:

  • 搜索与您提供的列表匹配的顾问关系 - 存储在临时表(或表变量)中。
  • 如果指定了任何姓氏,则从 temp 中删除所有不属于您提供的姓名的人的记录。
  • 重复其他条件列表 - 一直在减少临时记录。
  • THEN 加入外连接内容并返回结果。

在您的笔记中,您说可以忽略出口。如果这是真的,那么将它们取出将简化您的查询。您示例中的“或”子句意味着 SQL-Server 需要找到所有投资组合的所有关系,然后才能真正着手过滤您真正想要的结果。

分解查询

大多数查询由不参与过滤的外连接组成。尝试将这些连接移动到单独的选择中(即在您应用所有条件之后)。当 SQL-Server 看到很多表时,它会关闭一些可能的优化。所以你的第一步(假设你总是指定顾问)只是:

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 列与 SourceEntityId 的日期类型相同,并且 SourceEntityId 已编入索引。如果该列具有不同的数据类型,则 SQL-Server 将不想使用索引(因此您需要更改 @AdvisersListing 上的数据类型)。

tbOP_EntityRelationship 表听起来应该有一个类似的索引:

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

如果存在,那么 SQL-Server 应该能够通过仅转到索引页(而不是表页)来获得所需的一切。这称为“覆盖”索引。

tbOP_Data 上应该有一个稍微不同的索引(假设它在 DataId 上有一个聚集的主键):

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

SQL-Server 将存储来自表的聚集索引(我假设是 DataId)的键以及索引叶页中的“dateCreated”值。所以我们又有一个“覆盖”索引。

大多数其他表(tbOP__Client 等)应该在 DataId 上有索引。

查询计划

不幸的是,我看不到解释计划图片(我们的防火墙吃掉了它)。然而,一个有用的提示是将鼠标悬停在某些连接线上。它告诉你有多少记录被访问。

注意全表扫描。如果 SQL-Server 需要使用它们,那么它几乎放弃了您的索引。

数据库结构

它被设计成一个事务数据库。规范化水平(以及所有 EntityRelationship-this 和 Data-对于报告来说真的很痛苦)。您确实需要考虑拥有一个单独的报告数据库,将其中的一些信息分解为更有用的结构。

如果您直接针对生产数据库运行报告,那么我预计会出现一堆锁定问题和资源争用。

希望这很有用 - 这是我第一次在这里发帖。自从我上次在我现在的公司调整查询以来已经有很多年了(他们有一堆严肃的 DBA 来解决这类问题)。

【讨论】:

    【解决方案2】:

    查看您的执行计划... 97% 的查询成本用于处理 DISTINCT 子句。我不确定这是否有必要,因为无论如何您都在获取所有这些数据并对其进行分组。你可能想把它拿出来看看它对计划有何影响。

    【讨论】:

    • 试过了,没什么区别 :( 不过谢谢你的尝试。
    【解决方案3】:

    这种查询需要时间,有那么多连接和那么多临时表,没有什么简单或高效的。我一直在使用的一个技巧是使用局部变量。这可能不是一个全面的解决方案,如果它剃了几秒钟,那就值得了。

    DECLARE @Xadvisers varchar(1000)
    DECLARE @Xoutlets varchar(1000)
    DECLARE @XsearchText varchar(1000)
    
    SET @Xadvisers = @advisers
    SET @Xoutlets = @outlets
    SET @XsearchText = @searchText
    

    相信我,我已经对它进行了彻底的测试,它有助于处理复杂的脚本。关于 SQL Server 处理局部变量的方式。祝你好运!

    【讨论】:

    • 这完全没有什么不同。运行查询 10 次,无论是否更改。相同的执行时间。无论如何,谢谢。
    • 我已经对它进行了基准测试,它对我的​​许多复杂的 SP 产生了影响。这称为参数嗅探。不知道为什么人们对声誉如此负面。你们好残忍它以前对我有帮助,并且(当时)进行了大量研究。但如果人们是这样的,那就这样吧。对不起,它没有帮助。这是有关该主题的链接:sqlpointers.com/2006/11/…
    • 连接而不是子查询怎么样?而不是(advRel.SourceEntityId IN(从@AdvisersListing 选择顾问),左加入“顾问不为空”。与@OutletListing 相同。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多