SQL Server 2008活动监视器中的高计划计数
我有一个SQL Server 2008活动监视器中的高计划计数,sql,sql-server,sql-server-2008,sql-server-2008-r2,Sql,Sql Server,Sql Server 2008,Sql Server 2008 R2,我有一个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, Colum
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代码中一次调用一个对象
我在SQLExpress 2008活动监视器的活动监视器中注意到,由于调用查询时使用了所有不同的参数排列,计划计数高达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)
与其在循环
5000次
中执行此操作,不如将其包装到一个存储过程中,该存储过程将值的表
作为输入,并执行批量更新:
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)
上有索引,或者将其声明为主键
要验证缓存计划的来源,可以查询包含所有缓存计划的动态管理视图:
SELECT text
FROM sys.dm_exec_cached_plans
CROSS APPLY
sys.dm_exec_sql_text(plan_handle)
WHERE text LIKE ‘%SnippetFromYourQuery%’
尽管看起来您正在正确地对查询进行参数化,但您可以通过启用以下选项来测试:
如果这减少了运行时间,您应该调查查询的哪个部分包含硬编码值而不是参数。SQL事件探查器应该使这变得容易。我已经解决了我遇到的问题。让我明白的是,这些人遇到了一个问题: 基本上,在.NET代码中,它没有定义DbParameter.Size属性。由于不同的参数大小会导致创建不同的执行计划,所以我所有参数的每个排列都会导致创建和缓存不同的计划
我所要做的就是将DbParameter.Size设置为DDL脚本中各个列的大小!哇。另一件需要注意的事情是,我们从
INSERT
或UPDATE
更改为使用MERGE
,并且以前没有这个性能问题。您能在这里发布您的计划吗?只需发出SET SHOWPLAN\u TEXT ON GO
,然后查询并在此处发布输出。我确实将Guid设置为主键。这可能是一个很好的解决方案。我想看看在不改变代码的情况下我能做些什么。不知怎的优化了我的SQL实例?也许你可以告诉我我的理解哪里出了问题:我的查询中唯一没有参数化的地方是ON
段。如果我查看sys.dm_exec_query_stats表,它的query_散列和query_plan_散列具有相同的值,但是,它为我试图合并到数据库中的不同排列的值创建了不同的plan_句柄。SQL Server是否适合这样做?看起来应该将参数和计划分开保存。@petejamd:我想SQL Server可以决定为不同的参数值创建不同的执行计划。您可以尝试使用optimizefor(unknown)
query提示,看到了吗?我尝试了这个,但仍然得到了相同数量的查询计划:(@petejamd:您是否运行了探查器来查看执行的具体内容?
alter database YourDb forced