【问题标题】:SQL Server speed up querySQL Server 加速查询
【发布时间】:2011-10-20 14:06:51
【问题描述】:

我正在运行一个包含多个连接的存储过程,其中一个表包含超过 600,000 条记录。问题是该过程非常缓慢,可能需要几分钟才能执行。我们已经索引了相关的表列,但仍然没有运气。

我们可以做些什么来帮助提高性能?查询发布在下面。

谢谢

with CTE
as
(
select * from 
(
    select distinct c.ContactId, c.FirstName, c.LastName, 
    (select top 1 ce.Email from dbo.ContactEmails as ce where ce.ContactId =  c.ContactId and ce.IsPrimary = 1) as Email, 
    comp.CompanyName, j.JobName, c.MobileNumber, c.OfficeNumber, cse.DateSent, MAX(cse.DateSent) over(partition by ce.email) as maxdate 

    from dbo.ContactSentEmails as cse

    join dbo.ContactEmails as ce on cse.ContactId = ce.ContactId
    join dbo.Contacts as c on ce.ContactId = c.ContactId
    left join dbo.Jobs as j on c.JobId = j.JobId
    left join dbo.Companies as comp on c.CompanyId = comp.CompanyId

    join dbo.StaffProjects as sp on cse.StaffProjectId = sp.StaffProjectId
    join dbo.Staff as s on sp.StaffId = s.StaffId
    join dbo.Projects as p on sp.ProjectId = p.ProjectId

    where (@ContactSourceId = -1 or c.ContactSourceId = @ContactSourceId)
    and (@FirstName = '' OR c.FirstName LIKE '%' + @FirstName + '%')
    and (@LastName = '' OR c.LastName LIKE '%' + @LastName + '%')
    and (@EmailAddress = '' OR ce.Email LIKE '%' + @EmailAddress + '%')
    and (@StaffId = -1 or sp.StaffId = @StaffId)
    and (@ProjectId = -1 or sp.ProjectId = @ProjectId)
    and (@OfficeId = -1 or p.OfficeId = @OfficeId)
    and cse.DateSent between CONVERT(datetime, @startDate) and CONVERT(datetime, @endDate)

    group by c.ContactId, c.FirstName, c.LastName, Email,comp.CompanyName, j.JobName, c.MobileNumber, c.OfficeNumber, cse.DateSent
) as tbContacts
)

select ContactId, FirstName, LastName, Email, CompanyName, JobName, MobileNumber, OfficeNumber from CTE where cte.DateSent = CTE.maxdate order by CTE.Email

【问题讨论】:

  • 带有前导通配符的 LIKE 可能是在这里伤害您的原因。再多的索引也不会提高它们的性能。
  • 在左(外)连接之后还有(内)连接。那些左连接将成为基于表排序的内连接。看起来它们应该能够在所有内部连接之后移动而不会破坏您的查询。

标签: sql sql-server performance stored-procedures


【解决方案1】:

很多缺陷!

缺陷 1:不查看提供的值就无法选择计划。

(@ContactSourceId = -1 or c.ContactSourceId = @ContactSourceId) 

如果@ContactSourceId 为-1,则您有一个查询执行计划。如果 ContactSourceId 不是 -1,则您有另一个查询执行计划。如果您希望选择正确的执行计划,您需要提供一个查询,该查询知道它应该过滤什么,而无需查看变量的值。

由于您已使用此标准构造 7 次,因此有 2^7 个潜在计划,您获得正确计划的几率为 1/2^7 = 1/128

您需要将此查询文本分解为 128 个不同的查询 - 查询优化器只会为您做这件事。


缺陷 2:未能使用 SARGable 搜索条件

这 128 个查询中有一个是这样工作的:假设提供了 @FirstName 而没有提供其他变量。在这种情况下,@FirstName 是访问联系人表的主要条件。

c.FirstName LIKE '%' + @FirstName + '%'

如果您在 Contacts 表上仅使用此条件编写查询 - 将没有可以添加到将要使用的 Contacts 表的索引。你注定要进行表扫描。了解 SARGable 搜索条件。


缺陷 3:对每一行的操作,每行产生相同的结果。

cse.DateSent between CONVERT(datetime, @startDate) and CONVERT(datetime, @endDate)

为什么要将每行的变量转换为 DateTime?在运行查询之前进行这些转换。


缺陷 4:90% 的时间 Distinct 是一个拐杖

distinct
top 1
group by

查询中有这么多“只给我一个”运算符,这意味着查询作者只是在尝试一些东西,看看有什么能坚持下去。简化为实际意图。我的猜测是不需要不同的。如果您在不需要时添加 distinct - 您仍然需要付费!

【讨论】:

  • +1。通配符很慢,但 DISTINCT 对我来说就像拇指酸痛一样。获取MAX(DateSent) 的相关子查询在此处可能会比在可能很大的表上使用 DISTINCT 更好。
  • 关于查询计划的数量很好,但我不会准确地描述这是一个“缺陷”。但是您关于将其分解为单独的查询的观点可能是用于提高性能的一种很好的模式(尽管它当然会使维护成本更高)。这是使用动态 SQL 或动态生成 SQL 的好处之一。
【解决方案2】:

正如@Joe 在对您的问题的评论中所写,LIKE 运算符的成本很高,特别是因为您将通配符添加到每个搜索字符串的开头。众多的OR 运算符也是导致查询性能不佳的一个可能原因。

请参阅 Erland Sommarskog 的经典文章 Dynamic Search Conditions in T-SQL,了解如何编写性能更好的“搜索”查询。

[另外,SELECT TOP 1group by ...SELECT 子句中几乎每一列的关系是什么?]

【讨论】:

  • 我猜测 group by 是因为他错误地得到了未包含在 group by 中的列,所以只是将所有内容添加到 group by
  • 感谢您的回复,我现在去看看那篇文章。选择顶部是因为可以有多个电子邮件地址设置为主。我已经删除了 group 子句,因为它不需要。谢谢
【解决方案3】:

正如@Joe 和@Kenny 所说,LIKE 运算符可能是造成您问题的最大原因。解决这个问题可能会带来最大的性能提升。调查full text search,看看它是否适合您的需求。

但是,我还将重构您在 select 子句中选择电子邮件作为子选择的方式。这通常是在 SQL Server 中执行查询的一种非常昂贵的方式。一个联系人可以有多个IsPrimary = 1 的电子邮件地址吗?如果没有,那么只需加入主FROM 中的表即可。如果他们可以有多个,请考虑编写一个视图(可能是索引视图)以按联系人返回顶部的电子邮件地址。然后你就可以加入了。

【讨论】:

  • 是的,Primary = 1 的电子邮件可能不止一封。我会考虑通过返回联系人并加入该联系人来执行您的建议。感谢您的帖子
猜你喜欢
  • 1970-01-01
  • 2022-01-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-08
  • 1970-01-01
  • 2021-06-02
  • 1970-01-01
相关资源
最近更新 更多