为啥我重构的 SQL 比原来的版本效率低?

Posted

技术标签:

【中文标题】为啥我重构的 SQL 比原来的版本效率低?【英文标题】:Why is my refactored SQL less efficient than the original version?为什么我重构的 SQL 比原来的版本效率低? 【发布时间】:2012-07-06 16:46:22 【问题描述】:

我正在尝试优化存储过程,在查看了执行计划和执行时间后,我对结果感到惊讶。谁能解释一下。

我写的原始 SQL 有 2 个几乎相同的选择,然后我将它们放在一个 CTE 中,我尝试重构它,所以主要工作只完成一次,填充一个表变量,这样我就可以有 2 个较小的选择以我需要的方式过滤数据。 2 个 select 语句之间的唯一区别是在第一个 select 中传递给 TVF_GetChildGroups 的值 @AreaID 表示当前上下文的报告区域,而在第二个 select 中 @RootReportLevelID 只是所有区域。所以它是用户上下文的同龄人,目的是让你的分数与其他人的平均值进行比较。

DECLARE @Scores TABLE
(
  ShortName VARCHAR(50) ,
  PCTMax INT ,
  PCTAvg INT ,
  PCTMin INT ,
  ALLAvg INT ,
  AllMax INT ,
  AllMin INT
)
INSERT  INTO @Scores
    SELECT  T2.ShortName ,
            MAX(T1.MeridianScore) AS PCTMax ,
            CAST(( CAST(SUM(T1.contribution) AS DECIMAL) / CAST(SUM(T1.maxvalue) AS DECIMAL) * 100 ) AS DECIMAL(5, 2)) AS PCTAvg ,
            MIN(T1.MeridianScore) AS PCTMin ,
            CAST(( CAST(SUM(T2.contribution) AS DECIMAL) / CAST(SUM(T2.maxvalue) AS DECIMAL) * 100 ) AS DECIMAL(5, 2)) AS AllAvg ,
            MAX(T2.MeridianScore) AS AllUpperScore ,
            MIN(T2.MeridianScore) AS AllLowerScore
    FROM    ( SELECT    US.PKID ,
                        PT.ShortName ,
                        CAST(( CAST(SUM(RES.contribution) AS DECIMAL) / CAST(SUM(RES.maxvalue) AS DECIMAL) * 100 ) AS DECIMAL(5, 2)) AS MeridianScore ,
                        SUM(Contribution) AS Contribution ,
                        SUM(MaxValue) AS MaxValue
              FROM      tblUploadedScorecards AS US
                        INNER JOIN tblUploadedScoreCardResults AS RES ON US.PKID = RES.FKUploadedScoreCardID
                        INNER JOIN tblUploadedScorecardHeaders AS USH ON USH.FKUploadedScorecardID = US.PKID
                        INNER JOIN @ProviderTable AS PT ON USH.HeaderValue = PT.FullName
                        INNER JOIN TVF_GetChildGroups(@AreaID) AS TGCG ON TGCG.GroupName = US.Branch
              WHERE     US.FKScoreCardID = 185
                        AND reviewed = 1
                        AND ShopDate BETWEEN @StartDate AND @EndDate
                        AND RES.Rating <> 'I'
                        AND USH.FKScorecardHeaderID = 71
              GROUP BY  US.PKID ,
                        PT.ShortName ) AS T1
            RIGHT JOIN ( SELECT US.PKID ,
                                PT.ShortName ,
                                CAST(( CAST(SUM(RES.contribution) AS DECIMAL) / CAST(SUM(RES.maxvalue) AS DECIMAL) * 100 ) AS DECIMAL(5, 2)) AS MeridianScore ,
                                SUM(Contribution) AS Contribution ,
                                SUM(MaxValue) AS MaxValue
                         FROM   tblUploadedScorecards AS US
                                INNER JOIN tblUploadedScoreCardResults AS RES ON US.PKID = RES.FKUploadedScoreCardID
                                INNER JOIN tblUploadedScorecardHeaders AS USH ON USH.FKUploadedScorecardID = US.PKID
                                INNER JOIN @ProviderTable AS PT ON USH.HeaderValue = PT.FullName
                                INNER JOIN TVF_GetChildGroups(@RootReportLevelID) AS TGCG ON TGCG.GroupName = US.Branch
                         WHERE  US.FKScoreCardID = 185
                                AND reviewed = 1
                                AND ShopDate BETWEEN @StartDate AND @EndDate
                                AND RES.Rating <> 'I'
                                AND USH.FKScorecardHeaderID = 71
                         GROUP BY US.PKID ,
                                PT.ShortName ) AS T2 ON T1.ShortName = T2.ShortName
    GROUP BY T2.ShortName

因此,我将通用性重构为一个表变量,稍后我可以根据该变量将连接应用于 TVF,希望不需要过多地访问表并最终得到以下结果:

DECLARE @PreHierarchyResults TABLE 
(
PKID INT ,
Branch VARCHAR(150) ,
ShortName VARCHAR(50) ,
Contribution DECIMAL(20,3),
MaxValue DECIMAL(20,3)
)

INSERT INTO @PreHierarchyResults
    ( PKID ,
      Branch ,
      ShortName ,
      Contribution ,
      MaxValue )
SELECT  US.PKID ,
    US.Branch ,
    PT.ShortName ,
    Contribution AS Contribution ,
    MaxValue AS MaxValue
FROM    tblUploadedScorecards AS US
    INNER JOIN tblUploadedScoreCardResults AS RES ON US.PKID = RES.FKUploadedScoreCardID
    INNER JOIN tblUploadedScorecardHeaders AS USH ON USH.FKUploadedScorecardID = US.PKID
    INNER JOIN @ProviderTable AS PT ON USH.HeaderValue = PT.FullName
WHERE   US.FKScoreCardID = 185
    AND reviewed = 1
    AND ShopDate BETWEEN @StartDate AND @EndDate
    AND RES.Rating <> 'I'
    AND USH.FKScorecardHeaderID = 71


INSERT  INTO @Scores
    SELECT  T2.ShortName ,
            MAX(T1.MeridianScore) AS PCTMax ,
            CAST(( CAST(SUM(T1.contribution) AS DECIMAL) / CAST(SUM(T1.maxvalue) AS DECIMAL) * 100 ) AS DECIMAL(5, 2)) AS PCTAvg ,
            MIN(T1.MeridianScore) AS PCTMin ,
            CAST(( CAST(SUM(T2.contribution) AS DECIMAL) / CAST(SUM(T2.maxvalue) AS DECIMAL) * 100 ) AS DECIMAL(5, 2)) AS AllAvg ,
            MAX(T2.MeridianScore) AS AllUpperScore ,
            MIN(T2.MeridianScore) AS AllLowerScore
    FROM    ( SELECT    PHR.PKID ,
                        ShortName ,
                        CAST(( CAST(SUM(contribution) AS DECIMAL) / CAST(SUM(maxvalue) AS DECIMAL) * 100 ) AS DECIMAL(5, 2)) AS MeridianScore ,
                        SUM(Contribution) AS Contribution ,
                        SUM(MaxValue) AS MaxValue
              FROM      @PreHierarchyResults AS PHR 
                        INNER JOIN TVF_GetChildGroups(@AreaID) AS TGCG ON TGCG.GroupName = PHR.Branch 
              GROUP BY PHR.PKID , ShortName) AS T1
            RIGHT JOIN ( SELECT PHR2.PKID ,
                                ShortName ,
                                CAST(( CAST(SUM(Contribution) AS DECIMAL) / CAST(SUM(Maxvalue) AS DECIMAL) * 100 ) AS DECIMAL(5, 2)) AS MeridianScore ,
                                SUM(Contribution) AS Contribution ,
                                SUM(MaxValue) AS MaxValue
                         FROM   @PreHierarchyResults AS PHR2
                                INNER JOIN TVF_GetChildGroups(@RootReportLevelID) AS TGCG ON TGCG.GroupName = PHR2.Branch 
                         GROUP BY PHR2.PKID , ShortName) AS T2 ON T1.ShortName = T2.ShortName
    GROUP BY T2.ShortName

对于原始 SQL,我得到了执行时间:

SQL Server 执行时间: CPU 时间 = 16 毫秒,运行时间 = 11 毫秒。 表“#012F94F2”。扫描计数 0,逻辑读取 3,物理读取 0,预读读取 0,lob 逻辑读取 0,lob 物理读取 0,lob 预读读取 0。 表 'tblUploadedScoreCardResults'。扫描计数 4456,逻辑读取 13943,物理读取 40,预读读取 0,lob 逻辑读取 0,lob 物理读取 0,lob 预读读取 0。 表“工作台”。扫描计数 6,逻辑读取 29800,物理读取 0,预读读取 0,lob 逻辑读取 0,lob 物理读取 0,lob 预读读取 0。 表 'tblUserGroups'。扫描计数 2,逻辑读取 118,物理读取 1,预读读取 0,lob 逻辑读取 0,lob 物理读取 0,lob 预读读取 0。 表“tblUploadedScorecards”。扫描计数 0,逻辑读取 29496,物理读取 8,预读读取 0,lob 逻辑读取 0,lob 物理读取 0,lob 预读读取 0。 表 'tblUploadedScorecardHeaders'。扫描计数 192,逻辑读取 746,物理读取 5,预读读取 0,lob 逻辑读取 0,lob 物理读取 0,lob 预读读取 0。 表“#7D5F040E”。扫描计数 186,逻辑读取 372,物理读取 0,预读读取 0,lob 逻辑读取 0,lob 物理读取 0,lob 预读读取 0。

重构后我得到:

SQL Server 执行时间: CPU 时间 = 0 毫秒,经过的时间 = 10 毫秒。 表“#48563EF2”。扫描计数 0,逻辑读取 5106,物理读取 0,预读读取 0,lob 逻辑读取 0,lob 物理读取 0,lob 预读读取 0。 表 'tblUploadedScoreCardResults'。扫描计数 185,逻辑读取 614,物理读取 41,预读读取 0,lob 逻辑读取 0,lob 物理读取 0,lob 预读读取 0。 表“tblUploadedScorecards”。扫描计数 0,逻辑读取 370,物理读取 8,预读读取 0,lob 逻辑读取 0,lob 物理读取 0,lob 预读读取 0。 表 'tblUploadedScorecardHeaders'。扫描计数 7,逻辑读取 23,物理读取 5,预读读取 0,lob 逻辑读取 0,lob 物理读取 0,lob 预读读取 0。 表“#439189D5”。扫描计数 1,逻辑读取 2,物理读取 0,预读读取 0,lob 逻辑读取 0,lob 物理读取 0,lob 预读读取 0。 (5059 行受影响) (1 行受影响)

SQL Server 执行时间: CPU 时间 = 16 毫秒,运行时间 = 199 毫秒。 表“#47621AB9”。扫描计数 0,逻辑读取 3,物理读取 0,预读读取 0,lob 逻辑读取 0,lob 物理读取 0,lob 预读读取 0。 表“工作台”。扫描计数 114,逻辑读取 1770,物理读取 0,预读读取 0,lob 逻辑读取 0,lob 物理读取 0,lob 预读读取 0。 表 'tblUserGroups'。扫描计数 112,逻辑读取 998,物理读取 1,预读读取 0,lob 逻辑读取 0,lob 物理读取 0,lob 预读读取 0。 表“#48563EF2”。扫描计数 112,逻辑读取 5376,物理读取 0,预读读取 0,lob 逻辑读取 0,lob 物理读取 0,lob 预读读取 0。

查看输出我认为很明显第一个版本更有效,但我只是不明白为什么会这样,因为它似乎击中了 4 个表两次,而在第二个版本中,这些表只被击中一次。

【问题讨论】:

【参考方案1】:

我还会比较您的原始查询和修改后的查询之间的实际执行计划。

还要检查tblUploadedScorecardstblUploadedScoreCardResultstblUploadedScorecardHeaders 上的索引。将这些表中的数据缓存到 @PreHierarchyResults 后,您可能会失去第二个查询中连接列上某些覆盖索引的好处。

【讨论】:

以上是关于为啥我重构的 SQL 比原来的版本效率低?的主要内容,如果未能解决你的问题,请参考以下文章

为啥国内程序员都很少进行代码重构?

为啥我的数据库项目升级脚本包括从引用的数据库项目中重构?

JeecgBoot 3.4.0 版本发布,微服务重构版本

JeecgBoot 3.4.0 版本发布,微服务重构版本

JeecgBoot 3.4.0 版本发布,微服务重构版本

图像超分辨率重构实战