我到处搜索这种情况,找不到解决方法除了动态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)的情况下修复此性能?
答案 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'
这些更新的执行计划非常相似,不是问题。 如下图所示。
在进入server1之前,我仔细检查我的stuff表的统计信息是否已更新,因为这会影响查询计划的生成。 只是为了确定。
然后我去了server1。
不,在我去server1之前,在server2上我有这个具有以下权限的sql登录:
我称之为“监视器”
以及“监视器”的权限 我用这个选择:
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
这显示了我这些权限:
现在在server1上我有一个到server2的链接服务器(sqlsalon1.dev.boden.local) 这个LINKED服务器使用“monitor”连接到server2。
如上所示,此监视器sql用户具有查看和更新统计信息所需的所有权限,因此即使运行远程事务,我们也可以使用最佳计划。
ON SERVER1: 我使用以下链接服务器连接到server2:
运行这些脚本(不到一秒)
-- 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'
我得到了这个查询计划:
所以,仔细检查链接服务器帐户的权限,如果你复制我的,我相信你的问题会解决,因为这是在这里工作,除非有其他不同的东西,在这种情况下,请告诉我,我会尝试进一步解决它。
反过来说 从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
填充表后,我检查统计信息
事实证明统计数据未更新
所以我更新了统计数据:
--================================================
-- HAD TO UPDATE THE STATS BEFORE RUNNING THE UPDATES
--================================================
UPDATE STATISTICS dbo._MM_201504710_stuff
我再次检查,这次很好。
从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
我删除了以下服务器选项。
检查目标服务器上“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'
然后我们可以从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'
这将以相同的方式更新包含或不包含变量的行。 快速作为一个螺栓
答案 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
部分会有所帮助。