英文原文:
https://www.mssqltips.com/sqlservertip/4046/sql-server-2016-query-store-example/
问题
SQL Server 2016的新特性之一是查询存储。在之前的文章中,我们回顾了如何在SQL Server Management Studio(SSMS)中配置和访问查询存储。我们也提供了SSMS中一个查询存储报表的详细回顾。本文我们将显示一个如何使用查询存储的示例,如何跟踪多个执行计划和如何强制一个有效执行计划。
解决方案
我们将对演示创建一个带有一个表和存储过程的数据库。这个表将会足够的数据用以显示执行计划的不同。
创建演示查询存储数据库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
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 )
GO
ALTER
DATABASE
[qstore_demo]
SET
AUTO_UPDATE_STATISTICS
OFF
GO
ALTER
DATABASE
[qstore_demo]
SET
AUTO_CREATE_STATISTICS
OFF
GO
ALTER
DATABASE
[qstore_demo]
SET
RECOVERY SIMPLE
GO
ALTER
DATABASE
[qstore_demo]
SET
QUERY_STORE =
OFF
GO
|
我们创建一个AUTO_UPDATE_STATISTICS为OFF和AUTO_CREATE_STATISTICS为OFF的数据库尽可能少的优化。这只是出于演示目的。
我们现在还没有启用查询存储。该表会生成数据并且我们不想要昂贵的INSERT查询在查询存储中。
现在我们将创建表和存储过程。我们将生成表并且在数据库上启用查询存储:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
USE qstore_demo
GO
-- create a table
CREATE
TABLE
dbo.db_store (c1
CHAR
(3)
NOT
NULL
, c2
CHAR
(3)
NOT
NULL
, c3
SMALLINT
NULL
)
GO
-- create a stored procedure
CREATE
PROC dbo.proc_1 @par1
SMALLINT
AS
SET
NOCOUNT
ON
SELECT
c1, c2
FROM
dbo.db_store
WHERE
c3 = @par1
GO
-- populate the table (this may take a couple of minutes)
SET
NOCOUNT
ON
INSERT
INTO
[dbo].db_store (c1,c2,c3)
SELECT
'18'
,
'2f'
,2
go 20000
INSERT
INTO
[dbo].db_store (c1,c2)
SELECT
'171'
,
'1ff'
go 4000
INSERT
INTO
[dbo].db_store (c1,c2,c3)
SELECT
'172'
,
'1ff'
,0
go 10
INSERT
INTO
[dbo].db_store (c1,c2,c3)
SELECT
'172'
,
'1ff'
,4
go 15000
-- enable Query Store on the database
ALTER
DATABASE
[qstore_demo]
SET
QUERY_STORE =
ON
GO
|
SQL Server 2016查询存储性能测试
测试1 – 表上没有索引
在我们的第一个测试中我们在没有任何索引的表上执行存储过程。然后我们会在SSMS查询存储报表中回顾结果。我们会运行存储过程20次只会为了生成足够的执行和I/O,以便更容易在查询存储的其他查询中找到:
1
2
|
EXEC
dbo.proc_1 0
GO 20
|
在SSMS中“Query Store”数据库的容器下打开“Top Resource Consuming Queries”面板:
修改纵坐标使用“exec count”并修改“Metric”下拉列表的“Duration”为“Logical Reads”:
这里是我们的查询(查询#1)带有一个单一表扫描计划(计划#1):
测试2 – 使用一个非聚集索引测试
在这个测试中我们将创建一个非聚集索引并再次执行存储过程:
1
2
3
4
5
|
CREATE
NONCLUSTERED
INDEX
NCI_1
ON
dbo.db_store (c3)
GO
EXEC
dbo.proc_1 0
GO 20
|
注意,我们会创建一些索引(重复索引)只用于演示目的为了生成多个执行计划。
在SSMS中回到查询存储面板并刷新结果:
注意,新的执行计划(计划#13)被创建和使用。这个计划现在有更少的逻辑读。
测试3 – 创建另一个非聚集索引
现在我们将在新增列上创建新的索引:
1
2
3
4
5
|
CREATE
NONCLUSTERED
INDEX
NCI_2
ON
dbo.db_store (c3, c1)
GO
EXEC
dbo.proc_1 0
GO 20
|
在SSMS中回顾结果并且注意到另一个执行计划(计划#20)被创建:
强制执行计划(Force Plan)
让我们运行一些表更新并再次执行存储过程:
1
2
3
4
5
6
7
|
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 = 3
INSERT
INTO
dbo.db_store (c1,c2,c3)
SELECT
'173'
,
'1fa'
,0
GO 5
EXEC
dbo.proc_1 0
GO 20
|
在SSMS中返回查询存储面板并点击“View plan summary in a grid format”按钮:
你现在可以看到带有详细统计信息的三个执行计划:
注意,最新的计划#20有比旧计划#13更多逻辑读。
在查询存储报表的“Plan Summary”或“Execution Plan”部分的下面,点击“Force Plan”按钮。你也可以右击计划#13并强制这个计划。确认对这个查询你想强制这个计划:
另一种强制计划的方式是使用sp_query_store_plan存储过程:
1
|
EXEC
sp_query_store_force_plan @query_id = 1, @plan_id = 13;
|
再次运行存储过程。然后在SSMS中返回查询存储。更新计划总结并注意到“plan forced”列值对于“plan id”#13现在被设为1。该计划的“exec count”也增加了,意味着强制计划#13这次被使用了而不是计划#20:
这里是以图表格式(打勾的泡)显示了强制计划的样子:
对于存储过程创建最后的,最优索引。
1
2
3
|
CREATE
NONCLUSTERED
INDEX
NCI_3
ON
dbo.db_store (c3)
INCLUDE (c1,c2)
|
如果你再次运行存储过程,你会注意到强制计划将会被再次使用而不是创建新的使用新索引的计划。
现在我们不强制计划#13并再次运行存储过程:
1
2
3
4
|
EXEC
sp_query_store_unforce_plan @query_id = 1, @plan_id = 13
GO
EXEC
dbo.proc_1 0
GO
|
这里是结果:
新的计划#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的其他文章。