我需要帮助来改进以下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
答案 0 :(得分:3)
澄清问题
您查询的主要数据是什么 - 顾问,搜索文本,网点?
感觉您的标准允许用户以多种不同的方式进行搜索。对于你提出的每一个问题,一个程序员都会使用完全相同的SAME计划。通过使用几个sprocs可以获得更好的性能 - 每个都针对特定的搜索场景进行调整(即我打赌你可以通过策略号查询一些非常快速的查询)。
如果您可以将搜索文本分成INDIVIDUAL参数,那么您可以:
在你的笔记中,你说可以忽略出口。如果这是真的,那么将它们取出将简化您的查询。您的示例中的“or”子句意味着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”)的链接看起来并不像它的数据所需。所以你应该能够用“advRel.TargetEntityId”替换对“pe.EntityId”的所有引用。
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-这和数据 - 对报告来说真的很痛苦)。您确实需要考虑使用单独的报告数据库,将某些信息拆分为更有用的结构。
如果您直接针对生产数据库运行报告,那么我会发现一堆锁定问题和资源争用。
希望这有用 - 这是我第一次在这里发布。自从我上次调查当前公司的查询以来已经很久了(他们有一堆严厉的DBA来排序这类事情)。
答案 1 :(得分:2)
查看您的执行计划...查询成本的97%是在处理DISTINCT子句时。我不确定它是否是必要的,因为无论如何你都要获取所有数据并组建一个组。您可能想要将其删除,看看它是如何影响该计划的。
答案 2 :(得分:-2)
这种查询只需要花费时间,有很多连接和许多临时表,没有什么简单或有效的。我一直在使用的一个技巧是使用局部变量。它可能不是一个全面的解决方案,如果它刮了几秒钟,它是值得的。
DECLARE @Xadvisers varchar(1000)
DECLARE @Xoutlets varchar(1000)
DECLARE @XsearchText varchar(1000)
SET @Xadvisers = @advisers
SET @Xoutlets = @outlets
SET @XsearchText = @searchText
相信我,我已经彻底测试了它,它有助于复杂的脚本。关于SQL Server处理局部变量的方式。祝你好运!