是否可以从另一个数据库中的一个数据库的计划缓存中重用查询计划?

J.D*_*.D. 10 sql-server optimization execution-plan sql-server-2016

例如,如果我点击 sys 动态视图来选择一个特定的查询计划,我是否能够将该查询计划插入到另一个数据库的计划缓存中,运行相同的查询?(我知道查询经过哈希处理并进行比较以确定何时生成新计划,因此在我的示例中,我将确保查询确实是逐个字符完全相同的。)

AMt*_*two 11

好的,这就是您问题的答案,但您可能想知道为什么

好的,为什么不呢?

不同的数据库可能有不同的数据,因此需要不同的计划。

假设您将WideWorldImporters数据库恢复到同一台服务器两次(我们称之为WWI_1WWI_2)。您有两个相同的数据库。SQL Server 可能会创建一个计划并将其用于两个数据库的查询中。

但问题是,这两个数据库一上线,它们的“相同”就分叉了。它们可以独立更改。即使它们维护相同的模式,它们的数据也可以独立更改。因此,SQL Server在编译计划时必须独立考虑数据库。WWI_1可以有不同的统计WWI_2,这可能会导致不同的计划。为了让 SQL Server 对两个数据库使用相同的计划,SQL Server 需要在分叉后跟踪差异——这比仅仅编译/维护单独的计划更复杂和更昂贵。

让我们举一个真实世界的例子

让我们假设您是一家软件公司,并为您的客户托管软件。每个客户都有自己的数据库。在您的托管环境中,您可能在每台服务器上都有数百个数据库,其中每个数据库都具有相同的架构,但每个客户端的数据都是唯一的。

一位客户拥有95% 加州客户的客户群。查询加利福尼亚州所有客户的地址表将导致表扫描

-- For CustomerA this query returns 95% of the table, so it scans
SELECT CustomerID
FROM dbo.Addresses
WHERE StateCode = 'CA';
Run Code Online (Sandbox Code Playgroud)

不同的客户拥有均匀分布在美国每个州的客户群,并且还拥有重要的国际业务。相对较小比例的客户来自加利福尼亚。在这种情况下,查询加利福尼亚州所有客户的地址表将导致表seek

-- For CustomerB this query returns <1% of the table, so it seeks
SELECT CustomerID
FROM dbo.Addresses
WHERE StateCode = 'CA';
Run Code Online (Sandbox Code Playgroud)

相同的查询,在具有相同模式的数据库上,由于不同的统计数据而具有完全不同的计划。即使这两个数据库最初都是从同一个源备份中恢复的,SQL Server 也需要编译不同的计划。

两个查询 100% 相同并且架构 100% 相同是不够的。仅基于这些标准的重用计划可能是非常错误的——因此 SQL Server 不会这样做。

如果你真的愿意,你能做到吗?

呃……有点。

让我们使用相同的“您是一家软件公司,并为您的客户托管软件”示例。无论 SQL Server 想要做什么,您都希望在每个托管数据库中强制执行相同的计划。您可以使用计划指南并将相同的指南应用于服务器上的每个数据库。这与“将 [一个] 查询计划插入另一个数据库的计划缓存中”不太一样……但实际上是一回事。

一个完全微不足道的计划呢?

类似的东西SELECT COUNT(*) FROM dbo.SomeTable很简单,可以在两个数据库上使用相同的计划,对吗?不,即使那样!

让我们创建一个示例:

  • 创建示例数据库
  • 创建一个表并用一些数据填充它
    • 请注意,该表具有聚集 PK 和非聚集索引
  • 备份和恢复第二个副本到同一台服务器

这是一些代码来做到这一点:

CREATE DATABASE Sample1;
GO

USE Sample1
GO

CREATE TABLE dbo.SomeTable (
    SomeID int IDENTITY(1,1) PRIMARY KEY CLUSTERED,
    StuffType int,
    OtherStuff varchar(100),
    INDEX OtherStuff(OtherStuff)
);
GO

SET NOCOUNT ON;
INSERT INTO dbo.SomeTable (StuffType,OtherStuff)
SELECT  object_id%50,
        name 
FROM sys.objects;
GO 1000


BACKUP DATABASE Sample1 TO DISK = '/var/opt/mssql/data/Sample1.bak' WITH INIT;


RESTORE DATABASE Sample2 FROM DISK = '/var/opt/mssql/data/Sample1.bak'
    WITH MOVE 'Sample1' TO '/var/opt/mssql/data/Sample2.mdf',
         MOVE 'Sample1_log' TO '/var/opt/mssql/data/Sample2_log.ldf';
Run Code Online (Sandbox Code Playgroud)

现在,让我们做这个COUNT(*)查询。两者的计划相同吗?是的!在我的笔记本电脑上,它通过扫描集群 PK 来执行计数 查询计划扫描PK

SQL Server 选择扫描 PK,因为它更小。非聚集索引较大,因为它高度碎片化。

现在索引维护发生在Sample1数据库上(但不是Sample2)。有人重建索引dbo.SomeTable

ALTER INDEX ALL ON Sample1.dbo.SomeTable REBUILD;
Run Code Online (Sandbox Code Playgroud)

这如何影响查询计划? QueryPlansScanningDifferentIndexes

在 上Sample1,非聚集索引现在很好且紧凑。它比PK小。扫描非聚集索引将是`Sample1 的正确选择,因为它更小,IO 更少,速度更快。

在 上Sample2,扫描 PK 是正确的选择,因为它更小,IO 更少,因此速度更快。

具有相同数据的相同模式,第二个从第一个的最近备份中分叉出来。这两个查询都有不同的执行计划,但都有适合他们场景的查询计划。