【问题标题】:WHEN run query on SQL Server, it is very slow. How to solve execution plan issues?在 SQL Server 上运行查询时,速度非常慢。如何解决执行计划问题?
【发布时间】:2022-01-05 18:24:13
【问题描述】:

我使用 SQL Server 并遇到查询非常慢的问题。

INSERT INTO [ExtractReports].[dbo].[TradeCodesDelete] (PartID,
                                                       Code,
                                                       CodeTypeID,
                                                       SourceTypeID,
                                                       RevisionID,
                                                       ZPLID,
                                                       PartLevel,
                                                       CreatedDate,
                                                       FlagDelete)

----select count(1) from [ExtractReports].[dbo].[TradeCodesDelete]

---update [ExtractReports].[dbo].[TradeCodesDelete] set FlagDelete=2 where FlagDelete=1

SELECT DISTINCT
       FT.PartId,
       TN.Code,
       FT.CodeTypeID,
       FT.SourceTypeID,
       FT.RevisionID,
       fm.[Value],
       FT.PartLevel,
       GETDATE(),
       1
FROM Parts.Nop_Part pt WITH (NOLOCK)
     INNER JOIN Parts.Nop_PartsFamilyAttribute fm WITH (NOLOCK) ON pt.PartsFamilyID = fm.PartFamilyID
                                                               AND fm.[Key] = 20281007
     INNER JOIN ExtractReports.dbo.TPls pl WITH (NOLOCK) ON pl.ZPLID = fm.Value
     INNER JOIN Parts.TradeCodes FT WITH (NOLOCK) ON pt.PartID = FT.PartID
                                                 AND FT.PartLevel = 0
     INNER JOIN ExtractReports.dbo.TPLNewData TN WITH (NOLOCK) ON TN.PartID = FT.PartID
                                                              AND TN.CodeTypeID = FT.CodeTypeID
     LEFT JOIN [ExtractReports].[dbo].[TradeCodesDelete] d ON d.partid = FT.partid
                                                          AND d.codetypeid = FT.codetypeid
                                                          AND d.partlevel = 0
WHERE TN.Code <> FT.Code
  AND FT.MappingDoneFlag = 0
  AND d.partid IS NULL;

由于查询性能缓慢,我检查了执行计划。

这是我的执行计划:https://www.brentozar.com/pastetheplan/?id=SJCzRrmht

  • 索引搜索高达 57% - 如何降低它?
  • 哈希匹配内连接高达 40% - 如何降低?
  • 计算标量为 1% - 如何降低它?

谁能帮帮我?

表格脚本如下:

CREATE TABLE [Parts].[Nop_PartsFamilyAttribute]
(
     [PartFamilyAttributeID] [int] IDENTITY(1,1) 
          NOT FOR REPLICATION NOT NULL,
     [PartFamilyID] [int] NOT NULL,
     [Key] [int] NOT NULL,
     [Value] [nvarchar](2200) NULL,
     [CreatedDate] [datetime] NULL,
     [CreatedBy] [int] NULL,
     [ModifiedDate] [datetime] NULL,
     [Modifiedby] [int] NULL,
     [DeletedDate] [datetime] NULL,
     [DeletedBy] [int] NULL,

     CONSTRAINT [PK_Nop_PartsFamilyAttribute30] 
         PRIMARY KEY CLUSTERED ([PartFamilyAttributeID] ASC)
                 WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
                       IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, 
                       ALLOW_PAGE_LOCKS = ON) ON [Customer],
     CONSTRAINT [UK_PartFamilyID_Key30] 
         UNIQUE NONCLUSTERED ([PartFamilyID] ASC, [Key] ASC)
                WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, 
                      IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, 
                      ALLOW_PAGE_LOCKS = ON) ON [Customer]
) ON [Customer]

我尝试了什么:

create index pf_idx on Parts.Nop_PartsFamilyAttribute([Key]) include ([Value])

但没有任何改变 仍然是索引查找 57

和哈希匹配 40

并计算标量成本 1

那么我该怎么解决执行计划的问题?

【问题讨论】:

  • 我会说从删除 NOLOCK 开始,然后看看计划如何变化。
  • 为什么[Value][nvarchar](2200)?在您询问的哈希连接之前,它会隐式转换为int。 (ON pl.ZPLID = fm.Value) - 可能你可以摆脱隐式转换并添加一个合适的索引以允许搜索它,
  • 您关心的估计只是估计,甚至更多,所以这是在您的估计执行计划中,而不是实际执行计划,因此它们甚至可能具有误导性或实际执行计划可能完全不同. 1. 查询运行需要多长时间/是否比可接受的速度慢? 2.您能否在您的问题中提供实际的执行计划(这将更有帮助建议您做什么)? 3. 不要再担心估计的百分比了。 4.不要到处使用NOLOCK提示,这很可能对你的表现没有帮助......
  • 我建议:删除DISTINCT,这是一种代码味道,表明连接的想法很糟糕,您应该找到重复的来源,而不是盲目地使用DISTINCT。将SourceTypeIDRevisionID 添加到TradeCodes 索引作为INCLUDE 列,避免键查找。并在UK_PartFamilyID_Key30 上交换两列,这样你就得到CONSTRAINT [UK_PartFamilyID_Key30] UNIQUE NONCLUSTERED ([PartFamilyID] ASC, [Key] ASC),或者至少添加一个二级索引。同时删除类型不匹配,并将LEFT JOIN IS NULL 转换为WHERE NOT EXISTS

标签: sql-server stored-procedures sql-server-2012 sql-server-2017


【解决方案1】:

您的查询中有一个常见的反模式,使用左连接查找不存在的行。

您的查询目前如下所示:

<snip>
     LEFT JOIN [ExtractReports].[dbo].[TradeCodesDelete] d ON d.partid = FT.partid
                                                          AND d.codetypeid = FT.codetypeid
                                                          AND d.partlevel = 0
WHERE 
<snip>
  AND d.partid IS NULL;

您没有从d. 中选择任何行,这是明智的,因为假设d.partid 是不可为空/键的列,所有结果列都将是NULL

这会导致查询计划中出现“后期过滤器”,这意味着所有行都完全连接在一起,然后稍后应用谓词:

useful rewrite 是将NOT EXISTS 语法用于其预期目的。

  SELECT DISTINCT
       FT.PartId,
       TN.Code,
       FT.CodeTypeID,
       FT.SourceTypeID,
       FT.RevisionID,
       fm.[Value],
       FT.PartLevel,
       GETDATE(),
       1
FROM Parts.Nop_Part pt WITH (NOLOCK)
     INNER JOIN Parts.Nop_PartsFamilyAttribute fm WITH (NOLOCK) ON pt.PartsFamilyID = fm.PartFamilyID
                                                               AND fm.[Key] = 20281007
     INNER JOIN ExtractReports.dbo.TPls pl WITH (NOLOCK) ON pl.ZPLID = fm.Value
     INNER JOIN Parts.TradeCodes FT WITH (NOLOCK) ON pt.PartID = FT.PartID
                                                 AND FT.PartLevel = 0
     INNER JOIN ExtractReports.dbo.TPLNewData TN WITH (NOLOCK) ON TN.PartID = FT.PartID
                                                              AND TN.CodeTypeID = FT.CodeTypeID
WHERE TN.Code <> FT.Code
  AND FT.MappingDoneFlag = 0
  AND NOT EXISTS
      (
          SELECT
              1/0
          FROM [ExtractReports].[dbo].[TradeCodesDelete] d 
          WHERE d.partid = FT.partid
          AND   d.codetypeid = FT.codetypeid
          AND   d.partlevel = 0
      );

一边

我看到您经常谈论成本并降低成本。请不要关注成本。它是一个无单位的估计指标,与查询的哪些部分缓慢且效率低下无关。

请注意,当您获得实际执行计划时,没有“实际”成本。即使在 SQL Server 测量并报告了许多其他运行时(实际)指标之后,它们仍然是估计值。

优化器使用成本来快速选择一个足够好的计划。减少它们不一定会让您获得更快的查询。您可以有快速运行的昂贵查询和运行缓慢的廉价查询。

由于您在此处标记了 SQL Server 2017,我建议您查看 operator times 以确定哪些部分效率最低。

【讨论】:

    【解决方案2】:

    创建以下索引:

    create index pf_idx 
        on Parts.Nop_PartsFamilyAttribute([Key],PartFamilyID) 
        include ([Value])
    

    目前的问题是,即使它在“寻找”,但它的效率并不高,而且它正在估计从这些寻找返回的 +4MM 行到散列连接中,这基本上是所有时间都在进行的地方。看来 Seek 只匹配 [Key] 值,然后依靠散列连接来进行 PartFamilyID 过滤,但是在 4MM 行时开销很大。我上面建议的索引应该可以通过使其能够更有效地查找(甚至可能扫描)它需要的行来缓解大部分问题,同时匹配 [Key] 和 PartFamilyID。

    ----

    正如@MartinSmith 指出的那样,您的第二次加入有一个更大的问题,我错过了:

     INNER JOIN ExtractReports.dbo.TPls pl WITH (NOLOCK) ON pl.ZPLID = fm.Value
    

    这里的问题是,虽然pl.ZIPID 是一个 INT,fm.Value 是一个 NVARCHAR,这使得整个 JOIN non-Sargeable

    解决方法如下:

    1. 假设您不能只将 Nop_PartsFamilyAttribute.Value 列更改为 INT,然后添加一个持久计算列:

    ->

    `ALTER Parts.Nop_PartsFamilyAttribute
      ADD IntValue AS (Try_Parse([Value] AS INT)) PERSISTED;`
    
    1. 创建以下索引(而不是我上面建议的那个)。

    ->

    create index pf_idx2 
        on Parts.Nop_PartsFamilyAttribute([Key],IntValue,PartFamilyID) 
    
    1. 将您的第二个加入更改为:

      INNER JOIN ExtractReports.dbo.TPls pl WITH (NOLOCK) ON pl.ZPLID = fm.IntValue

    2. 最后,将查询的列列表更改为:

    ->

    SELECT DISTINCT
           FT.PartId,
           TN.Code,
           FT.CodeTypeID,
           FT.SourceTypeID,
           FT.RevisionID,
           CAST(fm.IntValue AS Nvarchar(2000)) AS [Value],
           FT.PartLevel,
           GETDATE(),
           1
    

    【讨论】:

    • 问题仍然存在索引搜索 57 和哈希匹配 40 和计算标量成本 1
    • @abeershlby 这是实际的还是估计的执行计划?如果你有一个实际的执行计划,你能发布吗?另外,Nop_PartsFamilyAttribute 表的总行数是多少?
    • 昂贵的哈希连接是ExtractReports.dbo.TPls pl HASH JOIN (Parts.Nop_PartsFamilyAttribute WHERE [Key] = 20281007) fm ON pl.ZPLID = CAST(fm.Value AS INT),因此它需要在Key, Value 上建立一个索引并删除隐式强制转换才能在那里进行搜索
    • @MartinSmith 哇,我完全错过了。谢谢
    • 你能告诉我你的意思吗?昂贵的哈希连接是 ExtractReports.dbo.TPls pl
    猜你喜欢
    • 1970-01-01
    • 2020-04-30
    • 1970-01-01
    • 2016-02-10
    • 1970-01-01
    • 2019-12-16
    • 2022-12-13
    • 2013-02-07
    • 2010-11-24
    相关资源
    最近更新 更多