【问题标题】:High Plan Count in SQL Server 2008 Activity MonitorSQL Server 2008 活动监视器中的高计划计数
【发布时间】:2010-09-08 21:22:52
【问题描述】:

我有一个MERGE 查询,用于在我的数据库上创建一个 upsert 操作,其中包含通过我的应用程序输入的数据。当我调用它来保存我的大型交易 (>5000) 数据时,它需要 非常 很长的时间(~20-40 秒)。

这是我的MERGE 声明

MERGE TestTable AS target USING (SELECT @Guid) AS source (target.Guid = source.Guid)
WHEN MATCHED THEN
  UPDATE TestTable SET Column1 = @Column1, Column2 = @Column2 WHERE Guid = @Guid
WHEN NOT MATCHED THEN
  INSERT INTO TestTable (Column1, Column2) VALUES (@Column1, @Column2)
OUTPUT $action

我在我的 .NET 代码中一次调用一个对象。

我在 SQL Express 2008 Activity Monitor 的 Activity Monitor 中注意到,由于调用查询时使用的所有不同参数排列,Plan Count 达到了 900 左右。我还注意到,如果我在不久之后使用稍微不同的参数重复相同的保存,它会更快地保存 更多(~2 秒)。

这是一个潜在的性能问题以及保存时间长的原因吗?

我使用的是 SQL Express 2008 R2。

编辑:计划如下:

|--Compute Scalar(DEFINE:([Expr1044]=CASE WHEN [Action1004]=(1) THEN N'UPDATE' ELSE CASE WHEN [Action1004]=(4) THEN N'INSERT' ELSE N'DELETE' END END))
     |--Assert(WHERE:(CASE WHEN NOT [Pass1238] AND [Expr1237] IS NULL THEN (0) ELSE NULL END))
          |--Nested Loops(Left Semi Join, PASSTHRU:([Action1004]=(3) OR [C:\DATABASE.MDF].[dbo].[DoorTable].[CarTable_Guid] as [target].[CarTable_Guid] IS NULL), OUTER REFERENCES:([C:\DATABASE.MDF].[dbo].[DoorTable].[CarTable_Guid]), DEFINE:([Expr1237] = [PROBE VALUE]))
               |--Clustered Index Merge(OBJECT:([C:\DATABASE.MDF].[dbo].[DoorTable].[DoorTable_PK] AS [target]), OBJECT:([C:\DATABASE.MDF].[dbo].[DoorTable].[DoorTable_FI01] AS [target]), SET:(Insert, [C:\DATABASE.MDF].[dbo].[DoorTable].[Column1] as [target].[Column1] = [Expr1005],[C:\DATABASE.MDF].[dbo].[DoorTable].[Column2] as [target].[Column2] = [Expr1006],[C:\DATABASE.MDF].[dbo].[DoorTable].[Column3] as [target].[Column3] = [Expr1007],[C:\DATABASE.MDF].[dbo].[DoorTable].[Column4] as [target].[Column4] = [Expr1008],[C:\DATABASE.MDF].[dbo].[DoorTable].[Column5] as [target].[Column5] = [Expr1009],[C:\DATABASE.MDF].[dbo].[DoorTable].[Column6] as [target].[Column6] = [Expr1010],[C:\DATABASE.MDF].[dbo].[DoorTable].[Column7] as [target].[Column7] = [Expr1011],[C:\DATABASE.MDF].[dbo].[DoorTable].[Column8] as [target].[Column8] = [Expr1012],...
               |    |--Compute Scalar(DEFINE:([Action1004]=[Action1004], [Expr1198]=[Expr1198]))
               |         |--Top(TOP EXPRESSION:((1)))
               |              |--Compute Scalar(DEFINE:([Expr1198]=CASE WHEN [Action1004] = (1) THEN CASE WHEN [Expr1099] THEN (0) ELSE (1) END ELSE [Action1004] END))
               |                   |--Compute Scalar(DEFINE:([Expr1005]=CONVERT_IMPLICIT(nvarchar(64),[@Column1],0), [Expr1006]=CONVERT_IMPLICIT(nvarchar(64),[@Column2],0), [Expr1007]=CONVERT_IMPLICIT(nvarchar(64),[@Column3],0), [Expr1008]=[@Column4], [Expr1009]=CONVERT_IMPLICIT(nvarchar(64),[@Column5],0), [Expr1010]=[@Column6], [Expr1011]=[@Column7], [Expr1012]=CONVERT_IMPLICIT(float(53),[@Column8],0),[Expr1099]=[Action1004]=(1) AND CASE WHEN [C:\DATABASE.MDF].[dbo].[DoorTable].[CarTable_Guid] as [target].[CarTable_Guid] = CONVERT_IMPLICIT(nvarchar(32),[@CarTable_Guid],0) THEN (1) ELSE (0) END))
               |                        |--Compute Scalar(DEFINE:([Action1004]=ForceOrder(CASE WHEN [TrgPrb1002] IS NOT NULL THEN (1) ELSE (4) END)))
               |                             |--Nested Loops(Left Outer Join)
               |                                  |--Constant Scan
               |                                  |--Compute Scalar(DEFINE:([TrgPrb1002]=(1)))
               |                                       |--Clustered Index Seek(OBJECT:([C:\DATABASE.MDF].[dbo].[DoorTable].[DoorTable_PK] AS [target]), SEEK:([target].[Guid]=CONVERT_IMPLICIT(nvarchar(1),[@Guid],0)) ORDERED FORWARD)
               |--Clustered Index Seek(OBJECT:([C:\DATABASE.MDF].[dbo].[CarTable].[CarTable_PK]), SEEK:([C:\DATABASE.MDF].[dbo].[CarTable].[Guid]=[C:\DATABASE.MDF].[dbo].[DoorTable].[CarTable_Guid] as [target].[CarTable_Guid]) ORDERED FORWARD)

【问题讨论】:

  • 另一件需要注意的事情是,我们从INSERTUPDATE 改为使用MERGE,并且之前没有出现此性能问题。
  • 您能在此处发布您的计划吗?只需发出SET SHOWPLAN_TEXT ON GO,然后您的查询并在此处发布输出。

标签: sql sql-server sql-server-2008 sql-server-2008-r2


【解决方案1】:

与其在5000 次循环中执行此操作,不如将其包装到一个存储过程中,该过程将值TABLE 作为输入,并执行批量更新:

CREATE TYPE paramTable AS TABLE
        (
        guid UNIQUEIDENTIFIER NOT NULL PRIMARY KEY,
        column1 INT,
        column2 VARCHAR(100)
        )

CREATE PROCEDURE prcMergeInput(@mytable paramTable)
AS 
        MERGE   TestTable AS target
        USING   (
                SELECT  column1, column2, guid
                FROM    @mytable
                ) AS source
        ON      (target.Guid = source.Guid)
        WHEN MATCHED THEN
                UPDATE  TestTable
                SET     Column1 = source.Column1,
                        Column2 = source.Column2
        WHEN NOT MATCHED THEN
                INSERT
                INTO    TestTable (Column1, Column2)
                VALUES  (source.Column1, source.Column2)
        OUTPUT  INSERTED.guid

还要确保您在TestTable (guid) 上有一个索引,或者它被声明为PRIMARY KEY

【讨论】:

  • 我确实将 Guid 设置为 PRIMARY KEY。
  • 这可能是一个很好的解决方案。我想看看是否有什么我可以在不更改代码的情况下做的事情。以某种方式调整我的 SQL 实例?
【解决方案2】:

要验证缓存计划的来源,可以查询包含所有缓存计划的动态管理视图:

SELECT  text
FROM    sys.dm_exec_cached_plans
CROSS APPLY 
        sys.dm_exec_sql_text(plan_handle)
WHERE   text LIKE ‘%SnippetFromYourQuery%’

虽然看起来您正确地对查询进行了参数化,但您可以通过打开 forced parameterization 来测试它:

alter database YourDb forced

如果这会减少运行时间,您应该调查查询的哪一部分包含硬编码值而不是参数。 SQL Profiler 应该会让这一切变得简单。

【讨论】:

  • 也许你能告诉我我的理解哪里出了问题:我的查询中唯一没有参数化的地方是ON段。如果我查看 sys.dm_exec_query_stats 表,它的 query_hash 和 query_plan_hash 具有相同的值,但是,它正在为我试图合并到我的数据库中的值的不同排列创建不同的 plan_handle。这对 SQL Server 来说是否合适?看来应该把参数和计划分开保存。
  • @petejamd:我猜 SQL Server 可以决定为不同的参数值创建不同的执行计划。您可以尝试使用optimize for (unknown) 查询提示,请参阅msdn.microsoft.com/en-us/library/ms181714.aspx
  • 我试过了,但我仍然得到相同数量的查询计划:(
  • @petejamd:你有没有运行分析器来查看究竟执行了什么?
【解决方案3】:

我已经解决了我遇到的问题。让我弄清楚的是看看这些人遇到的一个问题:nHibernate Recompiles and Execution Plans

基本上,在 .NET 代码中,它没有定义 DbParameter.Size 属性。由于不同的参数大小会导致创建不同的执行计划,因此我所有参数的每次排列都会导致创建和缓存不同的计划。

我所要做的就是将 DbParameter.Size 设置为我的 DDL 脚本中每一列的大小!哇。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-08
    相关资源
    最近更新 更多