在视图中使用JOIN和在sprocs中编写它们的SQL Server性能?

时间:2012-02-16 07:05:44

标签: sql-server-2008 tsql join left-join

在数据库中,表中的记录少于200,000条,在存储过程中,我引用了几个执行多个LEFT JOIN和其他连接的视图。

其中一个视图包含四个LEFT OUTER JOIN,另外两个包含几个INNER JOIN,只链接表/数据。

查看sproc执行计划时,我看到一个查询占用了39%的执行时间。绿色的建议是在主表的两个字段上创建一个非聚集索引(我已经包含了聚簇索引,因为它包含一个自动递增的PK)。

添加后,执行时间没有那么多下降,目前正在徘徊2.5秒左右。

这是预期的吗?

我偏向于在大型主表中保留(尽管可能没有规范化),因此无需视图/连接。

此时是否会以这种方式重构数据库?

更新

此sproc运行大约14个不同的规则来查找匹配项。如果找到匹配项,则将内容附加到全局参数。因此,要检查每个规则,都有一个单独的查询。

我没有在一个sproc中有14个左右的查询,而是创建了单独的sprocs并使用EXEC调用它们,传入(连同其他参数)并返回该全局参数。

我使用SET SHOWPLAN_ALL ON执行了执行计划。

第一个罪魁祸首(显示总子树成本为4.408248)

   SELECT @ExternalTagName = etbs.ExternalTagName, @ExternalTagID = etbs.ExternalTagID, @ExternalPixelValue = etbs.ExternalPixelValue, @TriggerAlpha = ISNULL(SUM(dbo.FindInString(etbs.TriggerValue, @DocumentUrl)), '')   FROM vw_ETBS etbs   WHERE etbs.SystemBehaviouralSegmentID = 9  -- page url contains   AND etbs.AccountContainerID = @AccountContainerID   AND etbs.IsEnabled = 1    AND etbs.TriggerValue = @TriggerAlpha   GROUP BY ExternalPixelValue, etbs.ExternalTagID, etbs.ExternalTagName     --INSERT INTO DebugTable (DebugKey, DebugValue)   --VALUES ('after sql', 'test')  79  259 1   NULL    NULL    67  NULL    63.26242    NULL    NULL    NULL    4.408248    NULL    NULL    SELECT  0   NULL
       |--Compute Scalar(DEFINE:([Expr1016]=CONVERT_IMPLICIT(varchar(6000),[ROTags-Shopify-Alpha].[dbo].[ExternalTagList].[ExternalPixelValue],0), [Expr1017]=CONVERT_IMPLICIT(varchar(512),isnull([Expr1015],(0)),0))) 79  260 259 Compute Scalar  Compute Scalar  DEFINE:([Expr1016]=CONVERT_IMPLICIT(varchar(6000),[ROTags-Shopify-Alpha].[dbo].[ExternalTagList].[ExternalPixelValue],0), [Expr1017]=CONVERT_IMPLICIT(varchar(512),isnull([Expr1015],(0)),0))   [Expr1016]=CONVERT_IMPLICIT(varchar(6000),[ROTags-Shopify-Alpha].[dbo].[ExternalTagList].[ExternalPixelValue],0), [Expr1017]=CONVERT_IMPLICIT(varchar(512),isnull([Expr1015],(0)),0)    63.26242    0   6.326241E-06    3293    4.408248    [ROTags-Shopify-Alpha].[dbo].[VisitorTriggeredExternalTag].[ExternalTagName], [ROTags-Shopify-Alpha].[dbo].[VisitorTriggeredExternalTag].[ExternalTagID], [Expr1016], [Expr1017]    NULL    PLAN_ROW    0   1
            |--Compute Scalar(DEFINE:([Expr1015]=CASE WHEN [Expr1029]=(0) THEN NULL ELSE [Expr1030] END))   79  261 260 Compute Scalar  Compute Scalar  DEFINE:([Expr1015]=CASE WHEN [Expr1029]=(0) THEN NULL ELSE [Expr1030] END)  [Expr1015]=CASE WHEN [Expr1029]=(0) THEN NULL ELSE [Expr1030] END   63.26242    0   0.004639106 4063    4.408242    [ROTags-Shopify-Alpha].[dbo].[VisitorTriggeredExternalTag].[ExternalTagName], [ROTags-Shopify-Alpha].[dbo].[VisitorTriggeredExternalTag].[ExternalTagID], [ROTags-Shopify-Alpha].[dbo].[ExternalTagList].[ExternalPixelValue], [Expr1015]   NULL    PLAN_ROW    0   1
                 |--Stream Aggregate(GROUP BY:([ROTags-Shopify-Alpha].[dbo].[VisitorTriggeredExternalTag].[ExternalTagName], [ROTags-Shopify-Alpha].[dbo].[VisitorTriggeredExternalTag].[ExternalTagID]) DEFINE:([Expr1029]=COUNT_BIG([ROTags-Shopify-Alpha].[dbo].[FindInString]([Expr1019],CONVERT_IMPLICIT(varchar(max),[@DocumentUrl],0))), [Expr1030]=SUM([ROTags-Shopify-Alpha].[dbo].[FindInString]([Expr1019],CONVERT_IMPLICIT(varchar(max),[@DocumentUrl],0))), [ROTags-Shopify-Alpha].[dbo].[ExternalTagList].[ExternalPixelValue]=ANY([ROTags-Shopify-Alpha].[dbo].[ExternalTagList].[ExternalPixelValue]))) 79  262 261 Stream Aggregate    Aggregate   GROUP BY:([ROTags-Shopify-Alpha].[dbo].[VisitorTriggeredExternalTag].[ExternalTagName], [ROTags-Shopify-Alpha].[dbo].[VisitorTriggeredExternalTag].[ExternalTagID]) [Expr1029]=COUNT_BIG([ROTags-Shopify-Alpha].[dbo].[FindInString]([Expr1019],CONVERT_IMPLICIT(varchar(max),[@DocumentUrl],0))), [Expr1030]=SUM([ROTags-Shopify-Alpha].[dbo].[FindInString]([Expr1019],CONVERT_IMPLICIT(varchar(max),[@DocumentUrl],0))), [ROTags-Shopify-Alpha].[dbo].[ExternalTagList].[ExternalPixelValue]=ANY([ROTags-Shopify-Alpha].[dbo].[ExternalTagList].[ExternalPixelValue])    63.26242    0   0.004639106 4063    4.408242    [ROTags-Shopify-Alpha].[dbo].[VisitorTriggeredExternalTag].[ExternalTagName], [ROTags-Shopify-Alpha].[dbo].[VisitorTriggeredExternalTag].[ExternalTagID], [ROTags-Shopify-Alpha].[dbo].[ExternalTagList].[ExternalPixelValue], [Expr1029], [Expr1030]   NULL    PLAN_ROW    0   1
                      |--Sort(ORDER BY:([ROTags-Shopify-Alpha].[dbo].[VisitorTriggeredExternalTag].[ExternalTagName] ASC, [ROTags-Shopify-Alpha].[dbo].[VisitorTriggeredExternalTag].[ExternalTagID] ASC))  79  263 262 Sort    Sort    ORDER BY:([ROTags-Shopify-Alpha].[dbo].[VisitorTriggeredExternalTag].[ExternalTagName] ASC, [ROTags-Shopify-Alpha].[dbo].[VisitorTriggeredExternalTag].[ExternalTagID] ASC) NULL    7679.125    0.01126126  0.311861    4125    4.403603    [ROTags-Shopify-Alpha].[dbo].[VisitorTriggeredExternalTag].[ExternalTagName], [ROTags-Shopify-Alpha].[dbo].[VisitorTriggeredExternalTag].[ExternalTagID], [ROTags-Shopify-Alpha].[dbo].[ExternalTagList].[ExternalPixelValue], [Expr1019]   NULL    PLAN_ROW    0   1
                           |--Nested Loops(Inner Join, OUTER REFERENCES:([ROTags-Shopify-Alpha].[dbo].[VisitorTriggeredExternalTag].[VisitorTriggeredTagID], [Expr1028]) WITH UNORDERED PREFETCH)   79  264 263 Nested Loops    Inner Join  OUTER REFERENCES:([ROTags-Shopify-Alpha].[dbo].[VisitorTriggeredExternalTag].[VisitorTriggeredTagID], [Expr1028]) WITH UNORDERED PREFETCH   NULL    7679.125    0   0.03209874  4133    4.076795    [ROTags-Shopify-Alpha].[dbo].[VisitorTriggeredExternalTag].[ExternalTagName], [ROTags-Shopify-Alpha].[dbo].[VisitorTriggeredExternalTag].[ExternalTagID], [ROTags-Shopify-Alpha].[dbo].[ExternalTagList].[ExternalPixelValue], [Expr1019]   NULL    PLAN_ROW    0   1

SELECT @ExternalTagName = ExternalTagName, @ExternalTagID = ExternalTagID, @ExternalPixelValue = ExternalPixelValue, @TriggerNumeric = COUNT(*)
FROM vw_ETBS
WHERE SystemBehaviouralSegmentID = 10       -- direct traffic
AND AccountContainerID = @AccountContainerID    
AND vw_ETBS.IsEnabled = 1
GROUP BY ExternalPixelValue, ExternalTagID, ExternalTagName

vw_ETBS:

SELECT [lots of individual fields]
FROM    dbo.VisitorTriggeredExternalTag LEFT OUTER JOIN
        dbo.PageVisitEvents ON dbo.VisitorTriggeredExternalTag.PageVisitEventID = dbo.PageVisitEvents.PageVisitEventID LEFT OUTER JOIN
        dbo.ExternalTagBehaviouralSegments ON dbo.VisitorTriggeredExternalTag.ExternalTagID = dbo.ExternalTagBehaviouralSegments.ExternalTagID LEFT OUTER JOIN
        dbo.ExternalTagList ON dbo.ExternalTagBehaviouralSegments.ExternalTagID = dbo.ExternalTagList.ExternalTagID LEFT OUTER JOIN
        dbo.AccountContainers ON dbo.ExternalTagList.AccountContainerID = dbo.AccountContainers.AccountContainerID

5 个答案:

答案 0 :(得分:2)

视图,除非它们是索引视图(你必须遵循许多定义和使用的规则)类似于其他编程语言中的宏 - 它们被有效地扩展到查询中使用它们,然后在那时优化整个查询。

因此,视图不以任何方式“关于”性能 - 它们通常提供的只是引用查询的简写(它们可能隐藏了复杂的查询,或者也实现了专门的规则,例如安全性)。

我会继续检查执行计划,并继续处理查询中最昂贵的部分,直到执行时间对你来说是可接受的 - 我不知道当前情况下2.5秒是否“足够快” ,需要在进行优化之前定义可接受的性能。

答案 1 :(得分:0)

包含SET NOCOUNT ON语句:对于每个SELECT和DML语句,SQL Server都会返回一条消息,指示该语句的受影响行数。这些信息主要用于调试代码,但之后没用。通过设置SET NOCOUNT ON,我们可以禁用返回此额外信息的功能。对于包含多个语句或包含Transact-SQL循环的存储过程,将SET NOCOUNT设置为ON可以显着提高性能,因为网络流量大大减少。

CREATE PROC dbo.ProcName
AS
 SET NOCOUNT ON;
--Procedure code here
SELECT column1 FROM dbo.TblTable1
-- Reset SET NOCOUNT to OFF
SET NOCOUNT OFF;
GO

使用带有对象名称的模式名称:如果与模式名称一起使用,则对象名称是限定的。模式名称应与存储过程名称一起使用,并与存储过程中引用的所有对象一起使用。这有助于直接查找已编译的计划,而不是在最终决定使用缓存计划(如果可用)之前搜索其他可能模式中的对象。搜索和决定对象的模式的过程导致对存储过程的COMPILE锁定并降低存储过程的性能。因此,始终在存储过程中引用具有限定名称的对象,如

SELECT * FROM dbo.MyTable -- Preferred method
 -- Instead of
SELECT * FROM MyTable -- Avoid this method
 --And finally call the stored procedure with qualified name like:
EXEC dbo.MyProc -- Preferred method
 --Instead of
EXEC MyProc -- Avoid this method

不要在存储过程名称中使用前缀“sp_”:如果存储过程名称以“SP_”开头,则SQL Server首先在master数据库中搜索,然后在当前会话中搜索数据库。如果在master数据库中找到具有相同名称的另一个存储过程,则在master数据库中搜索会导致额外的开销甚至是错误的结果。 使用IF EXISTS(SELECT 1)代替(SELECT *):要检查另一个表中是否存在记录,我们使用IF EXISTS子句。如果从内部语句返回任何值(单个值“1”)或记录或完整记录集的所有列,则IF EXISTS子句返回True。不使用内部语句的输出。因此,为了最小化处理和网络传输的数据,我们应该在内部语句的SELECT子句中使用“1”,如下所示:

IF EXISTS (SELECT 1 FROM sysobjects
WHERE name = 'MyTable' AND type = 'U')

使用sp_executesql存储过程而不是EXECUTE语句。  sp_executesql存储过程支持参数。因此,使用sp_executesql存储过程而不是EXECUTE语句可以提高代码的可重用性。只有当两个语句的每个字符(包括大小写,空格,注释和参数)相同时,才能重用动态语句的执行计划。例如,如果我们执行以下批次:

DECLARE @Query VARCHAR(100)
DECLARE @Age INT
 SET @Age = 25
SET @Query = 'SELECT * FROM dbo.tblPerson WHERE Age = ' + CONVERT(VARCHAR(3),@Age)
EXEC (@Query)

如果我们再次使用不同的@Age值执行上述批处理,则不会重用为@Age = 25创建的SELECT语句的执行计划。但是,如果我们按以下方式编写上述批次,

DECLARE @Query NVARCHAR(100)
SET @Query = N'SELECT * FROM dbo.tblPerson WHERE Age = @Age'
EXECUTE sp_executesql @Query, N'@Age int', @Age = 25

此SELECT语句的已编译计划将重用于@Age参数的不同值。重用现有的合规计划将提高性能。 尽可能避免使用SQL Server游标:Cursor使用大量资源进行开销处理,以维护记录集中的当前记录位置,这会降低性能。如果我们需要在循环中逐个处理记录,那么我们应该使用WHILE子句。在可能的情况下,我们应该使用基于SET的方法替换基于游标的方法。因为SQL Server引擎经过精心设计和优化,可以非常快速地执行基于SET的操作。请注意,光标也是一种WHILE循环。

保持事务尽可能短:事务的长度会影响阻塞和死锁。在交易结束前不会发布独占锁定。在较高的隔离级别中,共享锁也会随事务而老化。因此,冗长的事务意味着锁定更长的时间,锁定更长的时间变成阻塞。在某些情况下,阻塞也会转换为死锁。因此,为了加快执行速度并减少阻塞,事务应尽可能短。 使用TRY-Catch进行错误处理:在用于错误处理的SQL Server 2005版本代码之前,实际代码中有很大一部分,因为在每个t-sql语句之后都会写入错误检查语句。更多代码总是消耗更多资源和时间。在SQL Server 2005中,为了相同的目的引入了一种新的简单方法。语法如下:

BEGIN TRY
--Your t-sql code goes here
END TRY
BEGIN CATCH
--Your error handling code goes here
END CATCH

如需更多信息,请参阅此Link您将会有更好的想法。

答案 2 :(得分:0)

在一个完美的世界中,将一个视图加入另一个视图根本不会影响您的表现。不幸的是,在我们生活的这个世界里,这是一个问题。基本上,您在优化器上抛出的对象和连接越多,在有限的时间内完成的工作就越多,您就越有可能获得超时,从而导致计划次优。因此,通常情况下,不要将一个视图连接到另一个视图,最好只重写查询,这样您只需引用所需的信息,从而使优化器的工作更轻松。

至于将所有内容放在大表中而不是规范化数据,这取决于您的情况,这可以表现得更好。但是,它也可能表现更差。而不是在单个页面上分散存储大量信息的小数据集,而是将非常大的数据集塞进页面,这样每页的行数非常少。无论如何,你必须阅读页面,所以如果你要获取单行信息,你可以通过消除连接看到相同甚至更低的性能。但是,当您看到多次搜索或扫描时,性能会比标准化数据严重降低。

如果您使用的是关系数据库管理系统,我强烈建议您放弃关系数据管理的概念。规范化结构,强制执行主键,强制执行外键,按照设计使用系统,您将获益。大多数问题出现在人们停止对待RDBMS时,而是试图将其视为文件存储或NoSQL系统之一(它们真正擅长于他们所做的事情,但你必须要他们为他们所做的工作)好吧,我也看到人们使用像RDBMS这样的NoSQL数据库然后抱怨它们无法正常工作......)。

答案 3 :(得分:0)

只加入索引列,重建查询中所有表的索引(或索引碎片整理它们),尤其是在您加入的表上并获得这种糟糕的性能。

维护索引后,运行“UPDATE STATISTICS tableName”,最好是“WITH FULLSCAN”以获得最佳样本,但指定“WITH SAMPLE 20 PERCENT”或任何大样本大小可以获得比使用默认值更好的统计数据。此外,如果您选择重建所有索引,那么您还可以通过添加“COLUMNS”来节省时间,以便仅重新生成列统计信息,因为重建索引已经为您提供了索引统计信息的完美采样。 所以最佳语句是“UPDATE STATISTICS tableName WITH FULLSCAN,COLUMNS”

此外,我已经说过仅使用实际表而不是视图编写整个查询的内容,除非视图以您自己的方式编制索引。此外,您应该只指定您将在SELECT语句中实际使用的列,而不是使用SELECT *。这使得优化器有机会减少工作量,甚至可能跳过读取某些表中的聚簇索引,如果你碰巧有一个索引覆盖了该表所需的全部内容。

如果我做出的修改并没有给你带来更好的性能,而且你没有受到阻塞,那么我认为我们需要查看你的查询计划,以便更好地了解正在发生的事情。

答案 4 :(得分:0)

视图是一个虚拟表,由一个或多个表中的列组成。您无法实际过滤此技术中的记录。所有匹配/不匹配的记录将出现在结果集中,具体取决于您的加入(内/左)。

因此,基本上在使用视图时,您必须等待执行,直到所有记录都存储在RAM中...

另一方面,存储过程为您提供输入参数选项以过滤查询..

有两种方法可以快速从存储过程中获取结果集,具体取决于特定表中存在的记录数...

案例1

Select Column1, Column2 From LargeTable T1
Inner Join SmallTable T2 on T2.Id = T1.Id
Where T1.ID = 1;

案例2

Select Column1, Column2 From
(
    Select Column1, Column2, ID From LargeTable Where ID = 1
)K
Inner Join SmallTable T2 on T2.ID = K.ID

检查差异......希望这有助于您优化它......