如何使用执行计划优化T-SQL查询


15

我有一个SQL查询,过去两天我一直在尝试使用试错法和执行计划进行优化,但无济于事。请原谅我这样做,但我将在此处发布整个执行计划。我已尽力使查询和执行计划中的表名和列名通用化,以简化和保护我公司的IP。可以使用SQL Sentry Plan Explorer打开执行计划。

我已经做了相当多的T-SQL,但是使用执行计划来优化查询对我来说是一个新领域,并且我确实试图理解如何做到这一点。因此,如果有人可以帮助我并解释如何解释该执行计划以在查询中找到优化它的方法,我将永远感激不已。我还有许多要优化的查询-我只需要一个跳板就可以帮助我解决第一个问题。

这是查询:

DECLARE @Param0 DATETIME     = '2013-07-29';
DECLARE @Param1 INT          = CONVERT(INT, CONVERT(VARCHAR, @Param0, 112))
DECLARE @Param2 VARCHAR(50)  = 'ABC';
DECLARE @Param3 VARCHAR(100) = 'DEF';
DECLARE @Param4 VARCHAR(50)  = 'XYZ';
DECLARE @Param5 VARCHAR(100) = NULL;
DECLARE @Param6 VARCHAR(50)  = 'Text3';

SET NOCOUNT ON

DECLARE @MyTableVar TABLE
(
    B_Var1_PK int,
    Job_Var1 varchar(512),
    Job_Var2 varchar(50)
)

INSERT INTO @MyTableVar (B_Var1_PK, Job_Var1, Job_Var2) 
SELECT B_Var1_PK, Job_Var1, Job_Var2 FROM [fn_GetJobs] (@Param1, @Param2, @Param3, @Param4, @Param6);

CREATE TABLE #TempTable
(
    TTVar1_PK INT PRIMARY KEY,
    TTVar2_LK VARCHAR(100),
    TTVar3_LK VARCHAR(50),
    TTVar4_LK INT,
    TTVar5 VARCHAR(20)
);

INSERT INTO #TempTable
SELECT DISTINCT
    T.T1_PK,
    T.T1_Var1_LK,
    T.T1_Var2_LK,
    MAX(T.T1_Var3_LK),
    T.T1_Var4_LK
FROM
    MyTable1 T
    INNER JOIN feeds.MyTable2 A ON A.T2_Var1 = T.T1_Var4_LK
    INNER JOIN @MyTableVar B ON B.Job_Var2 = A.T2_Var2 AND B.Job_Var1 = A.T2_Var3
GROUP BY T.T1_PK, T.T1_Var1_LK, T.T1_Var2_LK, T.T1_Var4_LK

-- This is the slow statement...
SELECT 
    CASE E.E_Var1_LK
        WHEN 'Text1' THEN T.TTVar2_LK + '_' + F.F_Var1
        WHEN 'Text2' THEN T.TTVar2_LK + '_' + F.F_Var2
        WHEN 'Text3' THEN T.TTVar2_LK
    END,
    T.TTVar4_LK,
    T.TTVar3_LK,
    CASE E.E_Var1_LK
        WHEN 'Text1' THEN F.F_Var1
        WHEN 'Text2' THEN F.F_Var2
        WHEN 'Text3' THEN T.TTVar5
    END,
    A.A_Var3_FK_LK,
    C.C_Var1_PK,
    SUM(CONVERT(DECIMAL(18,4), A.A_Var1) + CONVERT(DECIMAL(18,4), A.A_Var2))
FROM #TempTable T
    INNER JOIN TableA (NOLOCK) A ON A.A_Var4_FK_LK  = T.TTVar1_PK
    INNER JOIN @MyTableVar     B ON B.B_Var1_PK     = A.Job
    INNER JOIN TableC (NOLOCK) C ON C.C_Var2_PK     = A.A_Var5_FK_LK
    INNER JOIN TableD (NOLOCK) D ON D.D_Var1_PK     = A.A_Var6_FK_LK
    INNER JOIN TableE (NOLOCK) E ON E.E_Var1_PK     = A.A_Var7_FK_LK  
    LEFT OUTER JOIN feeds.TableF (NOLOCK) F ON F.F_Var1 = T.TTVar5
WHERE A.A_Var8_FK_LK = @Param1
GROUP BY
    CASE E.E_Var1_LK
        WHEN 'Text1' THEN T.TTVar2_LK + '_' + F.F_Var1
        WHEN 'Text2' THEN T.TTVar2_LK + '_' + F.F_Var2
        WHEN 'Text3' THEN T.TTVar2_LK
    END,
    T.TTVar4_LK,
    T.TTVar3_LK,
    CASE E.E_Var1_LK 
        WHEN 'Text1' THEN F.F_Var1
        WHEN 'Text2' THEN F.F_Var2
        WHEN 'Text3' THEN T.TTVar5
    END,
    A.A_Var3_FK_LK, 
    C.C_Var1_PK


IF OBJECT_ID(N'tempdb..#TempTable') IS NOT NULL
BEGIN
    DROP TABLE #TempTable
END
IF OBJECT_ID(N'tempdb..#TempTable') IS NOT NULL
BEGIN
    DROP TABLE #TempTable
END

我发现,第三条陈述(被评论为缓慢)是花费最多时间的部分。前两个语句几乎立即返回。

此链接中,执行计划可以XML格式提供。

最好右键单击并保存,然后在SQL Sentry Plan Explorer或其他查看软件中打开,而不是在浏览器中打开。

如果您需要我提供有关表格或数据的更多信息,请随时询问。


2
您的统计数据远非如此。您最后一次对索引进行碎片整理或更新统计信息是什么时候?另外,我会尝试使用临时表而不是表变量@MyTableVar,因为优化器实际上不能在表变量上使用统计信息。
亚当·海恩斯

感谢您的回复,亚当。将@MyTableVar更改为临时表没有任何效果,但是只有少量的行(可以从执行计划中看到)。执行计划中显示出我的统计数据有何不足?它是否指示应该重组或重建哪些索引,以及应该更新哪些表的统计信息?
Neo

3
右下角的哈希联接在构建输入中估计有24,000行,但实际为3,285,620行,因此很可能会溢出到tempdb。即对于导致从之间的连接行估计TableA@MyTableVar是路要走。同样,进入排序的行数比估计的要多得多,因此它们也很可能会溢出。
马丁·史密斯

Answers:


22

在获得主要答案之前,您需要更新两个软件。

所需的软件更新

首先是SQL Server。您正在运行SQL Server 2008 Service Pack 1(内部版本2531)。您应该至少修补到当前的Service Pack(SQL Server 2008 Service Pack 3-内部版本5500)。在撰写本文时,SQL Server 2008的最新版本是Service Pack 3,累积更新12(版本5844)。

第二个软件是SQL Sentry Plan Explorer。最新版本具有重要的新功能和修复,包括直接上传查询计划以进行专家分析的能力(无需将XML粘贴到任何地方!)

查询计划分析

由于使用了语句级的重新编译,因此表变量的基数估计是正确的:

表变量估计

不幸的是,表变量不能维护分布统计信息,因此优化器只知道有六行。它不知道那六行中的值。鉴于下一个操作是对另一个表的联接,因此此信息至关重要。该连接的基数估计是基于优化程序的疯狂猜测:

首次加入估算

从那时起,优化器选择的计划是基于错误的信息,因此也难怪性能如此之差。特别是,为排序和哈希表保留的用于排序的内存将太小。在执行时,溢出的排序和哈希操作将溢出到物理 tempdb磁盘上。

SQL Server 2008在执行计划中没有强调这一点。您可以使用“扩展事件”或“探查器排序警告”和“ 哈希警告”来监视泄漏。内存是在执行开始之前根据基数估算保留用于排序和散列的,并且无论SQL Server可能有多少备用内存,都无法在执行期间增加内存。因此,准确的行数估算对于任何涉及工作区内存消耗操作的执行计划都至关重要。

您的查询也已参数化。OPTION (RECOMPILE)如果不同的参数值影响查询计划,则应考虑添加到查询中。您可能仍然应该考虑使用它,以便优化程序可以@Param1在编译时看到的值。如果没有其他问题,考虑到表非常大且已分区,这可以帮助优化器为上面显示的索引查找产生更合理的估计。它还可以启用静态分区消除。

使用临时表而不是表变量 再次尝试查询OPTION (RECOMPILE)。您还应该尝试将第一次联接的结果具体化到另一个临时表中,并针对该临时表运行其余的查询。行数不是那么大(3,285,620),因此应该相当快。然后,优化器将具有关于联接结果的准确基数估计和分布统计信息。幸运的是,该计划的其余部分将很好地落实到位。

根据计划中显示的属性,实现查询将是:

SELECT
    A.A_Var7_FK_LK,
    A.A_Var4_FK_LK,
    A.A_Var6_FK_LK, 
    A.A_Var5_FK_LK,
    A.A_Var1,
    A.A_Var2,
    A.A_Var3_FK_LK
INTO #AnotherTempTable
FROM @MyTableVar AS B
JOIN TableA AS A
    ON A.Job = B.B_Var1_PK
WHERE
    A_Var8_FK_LK = @Param1;

您也可以INSERT进入预定义的临时表中(正确的数据类型未在计划中显示,因此我无法执行此操作)。新的临时表可能会或不会从聚集索引和非聚集索引中受益。


非常感谢您的深入回答。抱歉,我们花了一个星期的时间来回覆-我每天都在努力进行这项工作,同时还要进行其他工作。我已实施您的建议,以实现对TableA的联接#AnotherTempTable。这似乎产生了最好的影响-其他建议(使用临时表而不是@MyTableVar的表变量,并且使用OPTION (RECOMPILE)没有太大效果或根本没有效果。“匿名化”和“发布到SQLPerformance.com”在SQL哨兵计划Explorer选项是伟大的-我刚使用它们:answers.sqlperformance.com/questions/1087

-6

我注意到@MyTableVar上应该有一个PK,并同意#MyTableVar的性能通常更好(特别是行数更多)。

where子句中的条件

   WHERE A.A_Var8_FK_LK = @Param1

应该移到内部联接A AND'ed。根据我的经验,优化器不够聪明(抱歉,没有考虑计划),它可能会产生很大的变化。

如果这些更改没有显示出改善,那么我接下来将创建另一个A的临时表,并将所有它所连接的东西都由A.A_Var8_FK_LK = @ Param1约束(好吗?),如果该分组对您而言是合理的话。

然后在该临时表上(在创建之前或之后)为下一个连接条件创建聚簇索引。

然后将结果与剩下的几个表(F和T)结合起来。

Bam,当行估计值关闭并且有时不容易被改进时,他需要一个糟糕的查询计划)。我假设您有适当的索引,但这是我首先要在计划中检查的指标。

跟踪可以显示tempdb溢出,可能会或不会产生严重影响。

另一种替代方法-至少可以更快地进行尝试-是将表从最低行数(A)到最高行排序,然后开始将合并,哈希和循环添加到联接中。存在提示时,连接顺序将按照指定的顺序固定。其他用户明智地避免使用此方法,因为从长远来看,如果相对行数发生显着变化,它可能会受到损害。最少数量的提示是可取的。

如果您要执行许多操作,那么也许可以尝试(或试用)商业优化器,并且仍然是良好的学习体验。


是的。它确保A返回的行受到约束的限制。否则,优化器可以先加入,然后再应用约束。我每天处理一次。
crokusek

4
@crokusek你错了。当SQL-Server的INNER联接时,它们知道查询是等效的(无论条件位于WHERE还是ON子句),SQL Server的优化器非常擅长。
ypercubeᵀᴹ

6
您可能会在查询优化器上发现Paul White的系列很有用。
马丁·史密斯

这是一个可怕的习惯。也许对于这种特定情况(有一个约束),但是我来自多个开发人员,他们在where子句中使用AND条件。SQL Server的确实 一贯“移动”他们回到了联接你。
crokusek

同意不正确的外部连接(和正确的连接)。但是,当where子句中只有 AND'd表达式并且每个术语对应于特定的内部联接时,可以将该术语作为优化和最佳实践(imo)安全放心地移至“ on”位置。无论是“真正的”加入条件还是只是固定的约束条件,都是获得较大性能的第二要因。该链接仅适用于普通情况。现实生活中,convert()和数学条件多种多样,因此使它们成为最佳实践的最佳候选者。
crokusek
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.