SQL Server 2016 查询存储(Query Store)示例

 

英文原文:

 

问题

SQL Server 2016的新特性之一是查询存储。在之前的文章中,我们回顾了如何在SQL Server Management Studio(SSMS)中配置和访问查询存储。我们也提供了SSMS中一个查询存储报表的详细回顾。本文我们将显示一个如何使用查询存储的示例,如何跟踪多个执行计划和如何强制一个有效执行计划。

 

解决方案

我们将对演示创建一个带有一个表和存储过程的数据库。这个表将会足够的数据用以显示执行计划的不同。

创建演示查询存储数据库

CREATE DATABASE [qstore_demo]ON PRIMARY( NAME = N'qs_demo', FILENAME = N'C:\DATA\qs_demo.mdf' , SIZE = 102400KB ,MAXSIZE = 1024000KB , FILEGROWTH = 20480KB )LOG ON( NAME = N'qs_demo_log', FILENAME = N'D:\DATA\qs_demo_log.ldf' , SIZE = 20480KB ,MAXSIZE = 1024000KB , FILEGROWTH = 20480KB )GOALTER DATABASE [qstore_demo] SET AUTO_UPDATE_STATISTICS OFFGOALTER DATABASE [qstore_demo] SET AUTO_CREATE_STATISTICS OFFGOALTER DATABASE [qstore_demo] SET RECOVERY SIMPLEGOALTER DATABASE [qstore_demo] SET QUERY_STORE = OFFGO

我们创建一个AUTO_UPDATE_STATISTICS为OFF和AUTO_CREATE_STATISTICS为OFF的数据库尽可能少的优化。这只是出于演示目的。

我们现在还没有启用查询存储。该表会生成数据并且我们不想要昂贵的INSERT查询在查询存储中。

现在我们将创建表和存储过程。我们将生成表并且在数据库上启用查询存储:

USE qstore_demoGO-- create a tableCREATE TABLE dbo.db_store (c1 CHAR(3) NOT NULL, c2 CHAR(3) NOT NULL, c3 SMALLINT NULL)GO-- create a stored procedureCREATE PROC dbo.proc_1 @par1 SMALLINTASSET NOCOUNT ONSELECT c1, c2 FROM dbo.db_storeWHERE c3 = @par1GO-- populate the table (this may take a couple of minutes)SET NOCOUNT ONINSERT INTO [dbo].db_store (c1,c2,c3) SELECT '18','2f',2go 20000INSERT INTO [dbo].db_store (c1,c2) SELECT '171','1ff'go 4000INSERT INTO [dbo].db_store (c1,c2,c3) SELECT '172','1ff',0go 10INSERT INTO [dbo].db_store (c1,c2,c3) SELECT '172','1ff',4go 15000-- enable Query Store on the databaseALTER DATABASE [qstore_demo] SET QUERY_STORE = ONGO

SQL Server 2016查询存储性能测试

测试1 – 表上没有索引

在我们的第一个测试中我们在没有任何索引的表上执行存储过程。然后我们会在SSMS查询存储报表中回顾结果。我们会运行存储过程20次只会为了生成足够的执行和I/O,以便更容易在查询存储的其他查询中找到:

EXEC dbo.proc_1 0GO 20

在SSMS中“Query Store”数据库的容器下打开“Top Resource Consuming Queries”面板:

修改纵坐标使用“exec count”并修改“Metric”下拉列表的“Duration”为“Logical Reads”:

这里是我们的查询(查询#1)带有一个单一表扫描计划(计划#1):

测试2 – 使用一个非聚集索引测试

在这个测试中我们将创建一个非聚集索引并再次执行存储过程:

CREATE NONCLUSTERED INDEX NCI_1ON dbo.db_store (c3)GOEXEC dbo.proc_1 0GO 20

注意,我们会创建一些索引(重复索引)只用于演示目的为了生成多个执行计划。

在SSMS中回到查询存储面板并刷新结果:

注意,新的执行计划(计划#13)被创建和使用。这个计划现在有更少的逻辑读。

测试3 – 创建另一个非聚集索引

现在我们将在新增列上创建新的索引:

CREATE NONCLUSTERED INDEX NCI_2ON dbo.db_store (c3, c1)GOEXEC dbo.proc_1 0GO 20

在SSMS中回顾结果并且注意到另一个执行计划(计划#20)被创建:

强制执行计划(Force Plan)

让我们运行一些表更新并再次执行存储过程:

UPDATE dbo.db_store SET c1 ='1' WHERE c3 = '0'UPDATE dbo.db_store SET c2 ='3ff' WHERE c3 = '1'DELETE FROM dbo.db_store WHERE c3 = 3INSERT INTO dbo.db_store (c1,c2,c3) SELECT '173','1fa',0GO 5EXEC dbo.proc_1 0GO 20

在SSMS中返回查询存储面板并点击“View plan summary in a grid format”按钮:

你现在可以看到带有详细统计信息的三个执行计划:

注意,最新的计划#20有比旧计划#13更多逻辑读。

在查询存储报表的“Plan Summary”或“Execution Plan”部分的下面,点击“Force Plan”按钮。你也可以右击计划#13并强制这个计划。确认对这个查询你想强制这个计划:

另一种强制计划的方式是使用存储过程:

EXEC sp_query_store_force_plan @query_id = 1, @plan_id = 13;

再次运行存储过程。然后在SSMS中返回查询存储。更新计划总结并注意到“plan forced”列值对于“plan id”#13现在被设为1。该计划的“exec count”也增加了,意味着强制计划#13这次被使用了而不是计划#20:

这里是以图表格式(打勾的泡)显示了强制计划的样子:

对于存储过程创建最后的,最优索引。

CREATE NONCLUSTERED INDEX NCI_3ON dbo.db_store (c3)INCLUDE (c1,c2)

如果你再次运行存储过程,你会注意到强制计划将会被再次使用而不是创建新的使用新索引的计划。

现在我们不强制计划#13并再次运行存储过程:

EXEC sp_query_store_unforce_plan @query_id = 1, @plan_id = 13GOEXEC dbo.proc_1 0GO

这里是结果:

新的计划#23已经被创建和使用。它有最少的逻辑读。

注意:本文基于SQL Server 2016 CTP 2.2版本所写。一些截屏和行为在SQL Server 2016的最终版中可能会改变。

在接下来的文章中我们会提供大量针对查询存储运行的有用查询。

接下来

l 阅读关于查询存储的下一篇文章。

l 在阅读更多关于维护查询性能稳定性。

l 最新的SQL Server 2016评估版。

l 阅读SQL Server 2016。

l 阅读。

l 阅读关于SQL Server 2016的。