SQL Server如何将链接服务器添加到同一实例而不会影响性能

时间:2016-09-23 10:14:25

标签: sql-server performance linked-server

在我的公司,我们有几个带有MS SQL数据库服务器的环境(SQL 2008 R2,SQL 2014)。为简单起见,让我们考虑一个TEST环境和一个PROD环境以及每个环境中的两个sql server。让服务器名为 srTest1 srTest2 srProd1 srProd2 ,每个服务器都运行默认的MS SQL Server实例。我们使用多个数据库,比如DataDb,ReportDb,DWHDb。

我们希望在TEST和PROD的T-SQL中保留相同的源代码,但问题是每个环境中上述数据库的体系结构或分布:

TEST:

  • srTest1 - DataDb
  • srTest2 - DWHDb,ReportDb

PROD:

  • srProd1 - DataDb,ReportDb
  • srProd2 - DWHDb

现在,比如说,在ReportDb中,我们编写存储过程,其中有许多SELECT引用DataDb和DWHDb中的表和其他对象。为了使源代码尽可能通用,我们决定为每个环境中的每个数据库服务器上的每个数据库创建链接服务器,并根据它们为其创建的数据库命名。因此,这些链接的服务器就是:

  • lnkDataDb,lnkReportDb和lnkDWHDb srTest1
  • lnkDataDb,lnkReportDb和lnkDWHDb srTest2
  • lnkDataDb,lnkReportDb和lnkDWHDb srProd1
  • lnkDataDb,lnkReportDb和lnkDWHDb srProd2

我们将相应地调整存储过程中的源。例如:

而不是

SELECT * FROM DataDb.dbo.Contact

我们会写

SELECT * FROM lnkDataDb.DataDb.dbo.Contact

上面的示例对于执行查询的数据库(ReportDb)位于与引用表(DataDb)不同的服务器上的情况是合理的。 TEST环境的情况如何。但在PROD中却不是这样。这是我在这里关注的表现。 SQL Server会将SELECT视为"远程查询"事实上,无论它是否是对本地对象的引用。

现在,它是最重要的部分:

如果您查看这3个查询的实际执行计划,您会看到一件有趣的事情:

(1) SELECT * FROM DataDb.dbo.Contact
(2) SELECT * FROM srProd1.DataDb.dbo.Contact
(3) SELECT * FROM lnkDataDb.DataDb.dbo.Contact

前两个(查询#1和#2)具有相同的执行计划(尽可能最快),即使您使用引用#2表联系人的四部分名称方式也是如此。 最后一个查询有一个不同的计划(远程查询,因此更慢)。

问题是:

你能以某种方式创建一个自己的链接服务器(相同的sql server实例,实际上是默认实例)作为"别名"到主机的名称( srProd1 ),以便强制SQL服务器将其理解为本地而不是问题"远程执行"计划?

非常感谢任何提示

的Pavel

1 个答案:

答案 0 :(得分:1)

最近我发现了一种解决方法,它似乎比使用自指向链接服务器的解决方案更有效,更优雅地解决了这类问题。

如果你在多个SQL服务器上使用多个数据库工作(例如报告),并且服务器上数据库的物理分布是一个挑战,因为它可能因环境而异(例如TEST与PROD),I建议:

尽可能使用三部分数据库对象名称。如果对象是本地的,那么执行计划也是本地的,因此是有效的。

示例:

SELECT * FROM DataDb.dbo.Contact

如果您碰巧在不同的SQL服务器实例中运行上述查询(例如,驻留在不同的物理机上,但这不一定,即使在同一台机器上也可以安装其他SQL服务器实例),如果您要使用由四部分组成的名称:

SELECT * FROM lnkDataDb.DataDb.dbo.Contact

然后你可以使用以下技巧来解决这个问题:

我们假设 lnkDataDb 指向 srTest2 ,并且您正在从 srTest1 执行查询。现在,您将在本地服务器上创建一个“虚假”数据库 DataDb srTest1 )。这个虚假的DataDb不包含真正的数据库对象(没有表,没有视图,没有存储过程,没有UDF等)。只应在其中定义同义词。 (并且在它中也应该有与srTest2上的真实DataDb中相同的模式)。这些同义词的名称应与 srTest2 中DataDb中的实际db-object对应项完全相同。例如:

-- To be executed on srTest1.

EXEC sp_addlinkedserver
  @server       = N'lnkDataDb',
  @srvproduct   = N'',
  @provider     = N'SQLNCLI',
  @datasrc      = N'srTest2'
;
GO

CREATE DATABASE [DataDb];
GO

USE [DataDb];
GO

CREATE SYNONYM dbo.Contact FOR lnkDataDb.DataDb.dbo.Contact;
GO

现在,如果您想从 srTest2 中驻留在数据库 DataDb 中的表 dbo.Contact 中选择行,并且您正在执行来自 srTest1 的查询,您将使用一个简单的由三部分组成的表名:

SELECT * FROM DataDb.dbo.Contact

当然,在 srTest1 上,这不是一个表,它只是引用 srTest2 上同名表的同义词。但是,这就是诀窍,您使用相同的查询语法,就像在真实数据库对象所在的 srTest2 上执行它一样。

这种方法有缺点:

  1. 在本地服务器上,开头不能有数据库 与远程名称相同。因为你即将创造 带有该名称的“假”数据库,以反映远程名称 数据库对象。
  2. 您正在创建一个几乎为空的数据库 增加驻留在本地的各种数据库的混乱 SQL服务器。这可能会引起数据库管理员的不情愿 如果他们希望拥有尽可能少的数据库。
  3. 如果您正在SQL Server Management中开发T-SQL脚本 例如,工作室使用同义词会使您免于方便 智能感知功能。
  4. 优点胜过上述缺点:

    1. 您的脚本可以在任何环境(DEV,TEST,PROD)中使用 需要更改源代码的任何部分。
    2. 如果您查询数据的其他数据库驻留在同一个数据库中 SQL Server实例作为您的脚本,您还使用三部分名称 约定和您的SQL服务器在执行计划中评估查询 作为本地哪个好。 (这就是原来的问题 帖子正在寻求解决。)
    3. 如果您查询数据的其他数据库驻留在另一个数据库上 在SQL服务器实例中,您仍然使用SQL的“本地语法方式” 查询(使用同义词),仅在运行时进行求值 远程执行计划。哪个也好因为db对象 实际上是遥远的。
    4. 总结

      如果引用的对象是本地的,则查询将作为本地执行,如果引用的对象是远程的,则查询将作为远程执行,但 T-SQL脚本始终是相同的 。您不必更改其中的字母。