使用变量vs literal时远程查询很慢

时间:2015-07-03 17:49:40

标签: sql sql-server sql-server-2005 sql-update openquery

我到处搜索这种情况,找不到解决方法除了动态SQL,我不想使用

以下是我想在服务器2上更新的表:

(Stuff Id UNIQUEIDENTIFIER
, stuffname NVARCHAR(64))

我需要从服务器1更新它。

所以我一直在尝试这个:

DECLARE @newstuff nvarchar(64)

SELECT @newstuff = 'new stuff'

UPDATE [server2].database2.dbo.Stuff
SET stuffname=@newstuff
WHERE stuffId='4893CD93-08B3-4981-851B-5DC972288290'

这需要11秒。下一个使用文字在1秒内运行

UPDATE [server2].database2.dbo.Stuff
SET stuffname='new stuff'
WHERE stuffId='4893CD93-08B3-4981-851B-5DC972288290'

我已经比较了实际的执行计划。速度慢的是进行远程扫描,需要100%的成本,另外还有5个其他步骤(过滤器,表假脱机,计算标量,远程更新,更新)。快速的只是执行UPDATE和远程查询步骤。我需要使用变量,所以我需要一种方法来强制它远程执行整个查询。

我尝试过使用OPTION(RECOMPILE),但是server1正在使用SQL Server 2005.server2正在使用SQL Server 2012.我无法在服务器2上更改数据库结构而不会出现严重问题。我没有任何身份验证问题。我在更新时尝试对表进行别名。

我也尝试过使用Openquery。当我将id过滤器放在查询字符串中时,它会回落到1秒以下:

UPDATE OPENQUERY([server2], 'select stuffname, stuffid from database2.dbo.stufftable where contactid=''4CA1D489-9221-E511-A441-005056C00008''')
SET stuffname = @newstuff

但是我需要将id作为变量,并且该开放查询不接受变量(https://msdn.microsoft.com/en-CA/library/ms188427.aspx)。我尝试使用查询外部的id过滤器运行Openquery,但是在4秒内运行。它比11好,但不是很好:

UPDATE OPENQUERY([server2],'select stuffname, stuffid from database2.dbo.stufftable')
set stuffname=@newstuff
where contactid='4CA1D489-9221-E511-A441-005056C00008'

当然,我使用exec(@sql)运行openquery,但我真的不想那样。我可以使用文字来完成整个更新语句,甚至不使用OPENQUERY并获得相同类型的结果。

有没有办法让我在不使用exec(@sql)的情况下修复此性能?

6 个答案:

答案 0 :(得分:3)

您可以在远程端使用sp_executesql将动态SQL与参数一起使用。

declare @SQL nvarchar(max);

set @SQL = 'UPDATE database2.dbo.Stuff
            SET stuffname=@newstuff
            WHERE stuffId=''4893CD93-08B3-4981-851B-5DC972288290'''

exec [server2].master.dbo.sp_executesql @SQL, N'@newstuff nvarchar(64)', @newstuff

答案 1 :(得分:3)

我认为您的问题与您运行与LINKED服务器的连接的权限有关。

有解释此案例的链接,我也有类似的经历。 这里有几个链接:

OPENQUERY when executing linked server queries in SQL Server

TOP 3 PERFORMANCE KILLERS FOR LINKED SERVER QUERIES

我将在下面发布我的解决方案。

我已经设置了一个测试解决方案的环境。 我的server2是sql server 2005

我的server1是sql server 2012。

在server2上,我用以下方式创建并填充了stuff表:

我使用一个名为tablebackups的数据库,该数据库具有特定的命名约定,但我相信你可以理解: 结果是在标识字段上具有聚簇主键的表,以及用于更新的另一个字段。我的例子中的这个表有100,000条记录。

select @@version
--Microsoft SQL Server 2005 - 9.00.5000.00 (Intel X86) 
    --Dec 10 2010 10:56:29 
    --Copyright (c) 1988-2005 Microsoft Corporation
    --Standard Edition on Windows NT 5.2 (Build 3790: Service Pack 2)

use tablebackups
go

CREATE TABLE dbo._MM_201504710_stuff ( Id UNIQUEIDENTIFIER
                    , stuffname NVARCHAR(64)
                    )

ALTER TABLE dbo._MM_201504710_stuff ADD  CONSTRAINT [PK_Stuff] UNIQUE CLUSTERED ( ID );

-- add 100,000 records to the table so that we can have an idea of execution
SET NOCOUNT ON
insert into dbo._MM_201504710_stuff values (NewID(),'Radhe Radhe')
GO 100000 -- 100,000
SET NOCOUNT OFF
--this took 19:38


--just to test
SELECT TOP 100 * FROM dbo._MM_201504710_stuff
--18D4BDEA-6226-47E1-94DB-00402A29798F



DECLARE @newstuff nvarchar(64)
SELECT @newstuff = 'new stuff'
UPDATE dbo._MM_201504710_stuff
SET stuffname=@newstuff
WHERE Id='18D4BDEA-6226-47E1-94DB-00402A29798F'



UPDATE dbo._MM_201504710_stuff
SET stuffname='new stuff'
WHERE Id='18D4BDEA-6226-47E1-94DB-00402A29798F'

这些更新的执行计划非常相似,不是问题。 如下图所示。

enter image description here

在进入server1之前,我仔细检查我的stuff表的统计信息是否已更新,因为这会影响查询计划的生成。 只是为了确定。

enter image description here

然后我去了server1。

不,在我去server1之前,在server2上我有这个具有以下权限的sql登录:

我称之为“监视器” enter image description here

以及“监视器”的权限 我用这个选择:

SELECT p.[name], sp.permission_name, p.type_desc AS loginType FROM sys.server_principals p 
  JOIN sys.server_permissions Sp
   ON p.principal_id = sp.grantee_principal_id WHERE sp.class = 100 

这显示了我这些权限: enter image description here

现在在server1上我有一个到server2的链接服务器(sqlsalon1.dev.boden.local) 这个LINKED服务器使用“monitor”连接到server2。

如上所示,此监视器sql用户具有查看和更新​​统计信息所需的所有权限,因此即使运行远程事务,我们也可以使用最佳计划。

ON SERVER1: 我使用以下链接服务器连接到server2:

enter image description here

enter image description here

运行这些脚本(不到一秒)

-- just to test
select top 100 *
from [SQLSALON1.dev.boden.local].tablebackups.dbo._MM_201504710_stuff


--first update
UPDATE  [SQLSALON1.dev.boden.local].tablebackups.dbo._MM_201504710_stuff
SET stuffname='new stuff'
WHERE Id='18D4BDEA-6226-47E1-94DB-00402A29798F'



--second update
DECLARE @newstuff nvarchar(64)
SELECT @newstuff = 'new stuff'
UPDATE [SQLSALON1.dev.boden.local].tablebackups.dbo._MM_201504710_stuff
SET stuffname=@newstuff
WHERE Id='18D4BDEA-6226-47E1-94DB-00402A29798F'

我得到了这个查询计划: enter image description here

所以,仔细检查链接服务器帐户的权限,如果你复制我的,我相信你的问题会解决,因为这是在这里工作,除非有其他不同的东西,在这种情况下,请告诉我,我会尝试进一步解决它。

反过来说 从SQL 2005更新SQL 2012中的表

在sql 2012上 创建并填充表格

select @@version
--Microsoft SQL Server 2012 - 11.0.5058.0 (X64) 
--  May 14 2014 18:34:29 
--  Copyright (c) Microsoft Corporation
--  Standard Edition (64-bit) on Windows NT 6.3 <X64> (Build 9600: ) (Hypervisor)


use tablebackups
go

CREATE TABLE dbo._MM_201504710_stuff ( Id UNIQUEIDENTIFIER
                    , stuffname NVARCHAR(64)
                    )

ALTER TABLE dbo._MM_201504710_stuff ADD  CONSTRAINT [PK_Stuff] UNIQUE CLUSTERED ( ID );

-- add 100,000 records to the table so that we can have an idea of execution
SET NOCOUNT ON
insert into dbo._MM_201504710_stuff values (NewID(),'Radhe Radhe')
GO 100000 -- 100,000
SET NOCOUNT OFF
--this took 19:38


--just to test
SELECT TOP 100 * FROM dbo._MM_201504710_stuff
--3E29A8E5-BA57-4A9C-803E-003C13A80905

填充表后,我检查统计信息 enter image description here

事实证明统计数据未更新

所以我更新了统计数据:

--================================================
-- HAD TO UPDATE THE STATS BEFORE RUNNING THE UPDATES
--================================================
UPDATE STATISTICS dbo._MM_201504710_stuff

我再次检查,这次很好。

enter image description here

从sql 2005到sql 2012创建链接服务器:

USE [master]
GO

/****** Object:  LinkedServer [SQLMON1]    Script Date: 13/07/2015 17:09:08 ******/
EXEC master.dbo.sp_addlinkedserver @server = N'SQLMON1', @srvproduct=N'SQL Server'
 /* For security reasons the linked server remote logins password is changed with ######## */
EXEC master.dbo.sp_addlinkedsrvlogin @rmtsrvname=N'SQLMON1',@useself=N'False',@locallogin=NULL,@rmtuser=N'monitor',@rmtpassword='########'

GO

EXEC master.dbo.sp_serveroption @server=N'SQLMON1', @optname=N'collation compatible', @optvalue=N'false'
GO

EXEC master.dbo.sp_serveroption @server=N'SQLMON1', @optname=N'data access', @optvalue=N'true'
GO

EXEC master.dbo.sp_serveroption @server=N'SQLMON1', @optname=N'dist', @optvalue=N'false'
GO

EXEC master.dbo.sp_serveroption @server=N'SQLMON1', @optname=N'pub', @optvalue=N'false'
GO

EXEC master.dbo.sp_serveroption @server=N'SQLMON1', @optname=N'rpc', @optvalue=N'true'
GO

EXEC master.dbo.sp_serveroption @server=N'SQLMON1', @optname=N'rpc out', @optvalue=N'true'
GO

EXEC master.dbo.sp_serveroption @server=N'SQLMON1', @optname=N'sub', @optvalue=N'false'
GO

EXEC master.dbo.sp_serveroption @server=N'SQLMON1', @optname=N'connect timeout', @optvalue=N'0'
GO

EXEC master.dbo.sp_serveroption @server=N'SQLMON1', @optname=N'collation name', @optvalue=null
GO

EXEC master.dbo.sp_serveroption @server=N'SQLMON1', @optname=N'lazy schema validation', @optvalue=N'false'
GO

EXEC master.dbo.sp_serveroption @server=N'SQLMON1', @optname=N'query timeout', @optvalue=N'0'
GO

EXEC master.dbo.sp_serveroption @server=N'SQLMON1', @optname=N'use remote collation', @optvalue=N'true'
GO

我删除了以下服务器选项。 enter image description here

检查目标服务器上“monitor”的权限

SELECT p.[name] collate database_default, 
       sp.permission_name, 
       p.type_desc AS loginType 
   FROM sys.server_principals p 
  JOIN sys.server_permissions Sp
   ON p.principal_id = sp.grantee_principal_id
    WHERE sp.class = 100 
    and name = 'monitor'

enter image description here

然后我们可以从sql 2005服务器运行更新。

--first update
UPDATE  [SQLMON1].tablebackups.dbo._MM_201504710_stuff
SET stuffname='new stuff'
WHERE Id='3E29A8E5-BA57-4A9C-803E-003C13A80905'



--second update
DECLARE @newstuff nvarchar(64)
SELECT @newstuff = 'new stuff'
UPDATE [SQLMON1].tablebackups.dbo._MM_201504710_stuff
SET stuffname=@newstuff
WHERE Id='3E29A8E5-BA57-4A9C-803E-003C13A80905'

这将以相同的方式更新包含或不包含变量的行。 快速作为一个螺栓

enter image description here

答案 2 :(得分:0)

我会这样做。我不是将实际的UPDATE查询发送到server2,而是使用必要的参数在server2上创建一个存储过程,并从server1调用它。

在存储过程中,您可以根据需要使用server2的所有功能调整查询,以使其快速运行(例如OPTION(RECOMPILE))。

此外,拥有这样的显式存储过程定义了两个系统如何交互的接口,这本身就很好。

答案 3 :(得分:0)

解决方法应该是确保您使用的参数与列的长度和类型相匹配。例如,确保NVARCHAR(64)列的目标是“DECLARE @var AS NVARCHAR(64)”。

在你的样本中似乎是这种情况,但是在本地设置中测试时(使用SQLEXPRESS 2014链接的SQLEXPRESS 2005)我只在不匹配长度和类型时才进行“远程扫描”。

答案 4 :(得分:0)

这个问题可能与唯一标识符列有关。你有没有尝试过: 在server2上定义以下存储过程:

CREATE PROCEDURE updateStuff( @newstuff nvarchar(30), @stuffid    varchar(36))
AS 
 UPDATE Stuff
 SET stuffname=@newstuff
 WHERE stuffId=convert(uniqueidentifier, @stuffid))

来自服务器1调用: exec server2.database2.updatestuff N'New stuff','4893CD93-08B3-4981-851B-5DC972288290'

OLD建议:      声明@stuffid uniqueidentifier      设置@stuffid = convert(uniqueidentifier,='4893CD93-08B3-4981-851B-5DC972288290')      更新[server2] .database2.dbo.Stuff      SET stuffname = @newstuff      在哪里stuffId = @ stuffid

答案 5 :(得分:0)

您可以尝试在变量声明中设置值:

DECLARE @newstuff nvarchar(64) = 'new stuff'

UPDATE [server2].database2.dbo.Stuff
SET stuffname=@newstuff
WHERE stuffId='4893CD93-08B3-4981-851B-5DC972288290'

我认为删除select部分会有所帮助。