SQL Server 如何在不影响性能的情况下将链接服务器添加到同一实例

Pav*_*tyn 5 sql-server performance linked-server

在我的公司,我们有多个带有 MS SQL 数据库服务器的环境(SQL 2008 R2、SQL 2014)。为了简单起见,我们只考虑一个 TEST 环境和一个 PROD 环境,每个环境都有两个 sql 服务器。让服务器名为srTest1srTest2srProd1srProd2,并且每个服务器都运行默认的 MS SQL Server 实例。我们使用多个数据库,例如 DataDb、ReportDb、DWHDb。

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

测试:

  • srTest1 - 数据数据库
  • srTest2 - DWHDb、ReportDb

产品:

  • srProd1 - 数据数据库、报告数据库
  • srProd2 -DWHDb

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

  • srTest1上的 lnkDataDb、lnkReportDb 和 lnkDWHDb ,
  • srTest2上的 lnkDataDb、lnkReportDb 和 lnkDWHDb ,
  • srProd1上的 lnkDataDb、lnkReportDb 和 lnkDWHDb ,
  • srProd2上的 lnkDataDb、lnkReportDb 和 lnkDWHDb 。

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

代替

SELECT * FROM DataDb.dbo.Contact
Run Code Online (Sandbox Code Playgroud)

我们会写

SELECT * FROM lnkDataDb.DataDb.dbo.Contact
Run Code Online (Sandbox Code Playgroud)

对于执行查询的数据库 (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
Run Code Online (Sandbox Code Playgroud)

即使您使用由四部分组成的名称方式引用 #2 中的表 Contact,前两个(查询 #1 和 #2)也具有相同的执行计划(尽可能快)。最后一个查询有不同的计划(远程查询,因此速度较慢)。

问题是:

您能否以某种方式创建一个链接服务器到 self(相同的 sql server 实例,实际上是默认实例)作为主机名(srProd1)的“别名”,以便迫使 SQL Server 将其理解为本地和不发出“远程执行”计划?

非常感谢您的任何提示

帕维尔

Pav*_*tyn 2

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

如果您使用多个 SQL 服务器上的多个数据库(例如制作报告),并且服务器上数据库的物理分布是一个挑战,因为它可能因环境而异(例如测试与生产),我建议:

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

例子:

SELECT * FROM DataDb.dbo.Contact
Run Code Online (Sandbox Code Playgroud)

如果您碰巧从不同的 SQL Server 实例(例如,驻留在不同的物理计算机上,但这不一定,其他 SQL Server 实例甚至可以安装在同一台计算机上)运行上述查询,请简单地说,如果您我们将使用一个由四部分组成的名称:

SELECT * FROM lnkDataDb.DataDb.dbo.Contact
Run Code Online (Sandbox Code Playgroud)

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

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

-- 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
Run Code Online (Sandbox Code Playgroud)

现在,如果您想从srTest2上的数据库DataDb中的表dbo.Contact中 SELECT 行,并且从srTest1执行查询,您将使用一个简单的三部分表名:

SELECT * FROM DataDb.dbo.Contact
Run Code Online (Sandbox Code Playgroud)

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

这种方法有一些缺点:

  1. 在本地服务器上,一开始就不能存在与远程同名的数据库。因为您将使用该名称创建一个“假”数据库来反映远程数据库对象的名称。
  2. 您正在创建一个几乎为空的数据库,从而增加了驻留在本地 SQL 服务器上的各种数据库的混乱程度。如果数据库管理员希望拥有尽可能少的数据库,这可能会引起他们的不情愿。
  3. 例如,如果您在 SQL Server Management Studio 中开发 T-SQL 脚本,则使用同义词会使您无法享受 IntelliSense 功能的便利。

不过,优点大于上述缺点:

  1. 您的脚本可以在任何环境(DEV、TEST、PROD)中工作,无需更改源代码的任何部分。
  2. 如果您从中查询数据的其他数据库与您的脚本驻留在同一 SQL Server 实例上,您还可以使用三部分名称约定,并且您的 SQL Server 将执行计划中的查询评估为本地查询,这是可以的。(这就是这篇文章最初想要解决的问题。)
  3. 如果您查询数据的另一个数据库驻留在另一个 SQL Server 实例上,您仍然使用 SQL 查询的“本地语法方式”(带有同义词),该查询仅在运行时在远程执行计划中进行计算。这也很好,因为数据库对象实际上是远程的。

总结一下

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