SQL Server 2016 查询存储性能优化小结

SQL Server 2016已经发布了有半年多,相信还有不少小伙伴尚未开始使用,今天咱们来谈谈SQL Server 2016 查询存储性能优化,但愿你们可以喜欢html

做为一个DBA,排除SQL Server问题是咱们的职责之一,每月都有不少人给咱们带来各类不能解释却要解决的性能问题。sql

我就屡次听到,之前的SQL Server的性能问题都还好且在正常范围内,但如今一切已经改变,SQL Server开始糟糕, 疯狂的事情不能解释。在这个状况下我介入,分析下整个SQL Server的安装,最后用一些神奇的调查方法找出性能问题的根源。数据库

但不少时候问题的根源是同样的:所谓的计划回归(Plan Regression),即特定查询的执行计划已经改变。昨天SQL Server已经缓存了在计划缓存里缓存了一个好的执行计划,今天就生成、缓存最后重用了一个糟糕的执行计划——不断重复。缓存

进入SQL Server 2016后,我就变得有点多余了,觉得微软引进了查询存储(Query Store)。这是这个版本最热门的功能!查询存储帮助你很容易找出你的性能问题是否是计划回归形成的。若是你找到了计划回归,这很容易强制一个特定计划不使用计划向导。听起来颇有意思?让咱们经过一个特定的场景,向你展现下在SQL Server 2016里,如何使用查询存储来找出并最终修正计划回归。性能优化

查询存储(Query Store)——个人对手性能

在SQL Server 2016里,在你使用查询存储功能前,你要对这个数据库启用它。这是经过ALTER DATABASE语句实现,如你所见的下列代码优化

CREATE DATABASE QueryStoreDemo
GO
 
USE QueryStoreDemo
GO
 
-- Enable the Query Store for our database
ALTER DATABASE QueryStoreDemo
SET QUERY_STORE = ON
GO
 
-- Configure the Query Store
ALTER DATABASE QueryStoreDemo SET QUERY_STORE
(
 OPERATION_MODE = READ_WRITE, 
 CLEANUP_POLICY = (STALE_QUERY_THRESHOLD_DAYS = 367), 
 DATA_FLUSH_INTERVAL_SECONDS = 900, 
 INTERVAL_LENGTH_MINUTES = 1, 
 MAX_STORAGE_SIZE_MB = 100, 
 QUERY_CAPTURE_MODE = ALL, 
 SIZE_BASED_CLEANUP_MODE = OFF
)
GO

 

在线帮助为你提供了各个选项的详细信息。接下来我建立一个简单的表,建立一个非汇集索引,最后插入80000条记录。spa

 

-- Create a new table
CREATE TABLE Customers
(
 CustomerID INT NOT NULL PRIMARY KEY CLUSTERED,
 CustomerName CHAR(10) NOT NULL,
 CustomerAddress CHAR(10) NOT NULL,
 Comments CHAR(5) NOT NULL,
 Value INT NOT NULL
)
GO
 
-- Create a supporting new Non-Clustered Index.
CREATE UNIQUE NONCLUSTERED INDEX idx_Test ON Customers(Value)
GO
 
-- Insert 80000 records
DECLARE @i INT = 1
WHILE (@i <= 80000)
BEGIN
 INSERT INTO Customers VALUES
 (
  @i,
  CAST(@i AS CHAR(10)),
  CAST(@i AS CHAR(10)),
  CAST(@i AS CHAR(5)),
  @i
 )
  
 SET @i += 1
END
GO

 

为了访问咱们的表,我额建立了一个简单的存储过程,传入value值做为过滤谓语。.net

-- Create a simple stored procedure to retrieve the data
CREATE PROCEDURE RetrieveCustomers
(
 @Value INT
)
AS
BEGIN
 SELECT * FROM Customers
 WHERE Value < @Value
END
GO

如今我用80000的参数值来执行存储过程。设计

-- Execute the stored procedure.
 -- This generates an execution plan with a Key Lookup (Clustered).
 EXEC RetrieveCustomers 80000
 GO

 

如今当你查看实际的执行计划时,你会看到查询优化器已经选择了有419个逻辑读的汇集索引扫描运算符。SQL Server并无使用非汇集索引,由于这样没有意义,因为临界点。这个查询结果并无选择性。

如今假设SQL Server发生了些事情(例如重启,故障转移),SQL Server忽略已经缓存的计划,这里我经过执行DBCC FREEPROCCACHE从计划缓存里抹掉每一个缓存的计划来模拟SQL Server重启(不要在生产环境里使用!)。 

-- Get rid of the cached execution plan...
DBCC FREEPROCCACHE
GO

 

如今有人再次调用你的存储过程,此次输入参数值是1。此次执行计划不同,由于如今在执行计划里你会有书签查找。SQL Server估计行数是1,在非汇集索引里没有找到任何行。所以与非汇集索引查找结合的书签查找才有意义,由于这个查询是有选择性的。

如今我再执行用80000参数值的查询。

 
-- Execute the stored procedure
EXEC RetrieveCustomers 1
GO
 
-- Execute the stored procedure again
-- This introduces now a plan regression, because now we get a Clustered Index Scan
-- instead of the Key Lookup (Clustered).
EXEC RetrieveCustomers 80000
GO

 

当你再次看STATISTICS IO的输出,你会看到这个查询如今产生了160139个逻辑读——刚才的查询只有419个逻辑读。这个时候DBA的手机就会响起,性能问题。但今天咱们要不一样的方式解决——使用刚才启用的查询存储。

当你再次看实际的执行计划,在你面前你会看到有一个计划回归,由于SQL Server刚重用了书签查找的的计划缓存。刚才你有汇集索引扫描运算符的执行计划。这是SQL Server里参数嗅探的反作用。

让咱们经过查询存储来详细了解这个问题。在SSMS里的对象资源管理器里,SQL Server 2016提供了一个新的结点叫查询存储,这里你会看到一些报表。

【前几个资源使用查询】向你展现了最昂贵的查询,基于你选择的维度。这里切换到【逻辑读取次数】。

这里在你面前有一些查询。最昂贵的查询生成了近500000个逻辑读。这是咱们的初始语句。这已是第一个WOW效果的的查询存储:SQL Server重启后,查询存储的数据仍是存在的!第2个是你存储过程里的SELECT语句。在查询存储里每一个捕获的查询都有一个标示号——这里是7。最后当你看报告的右边,你会看这个查询的不一样执行计划。

如你所见,查询存储捕获了2个不一样的执行计划,一个ID是7,一个ID是8。当你点击计划ID时,SQL Server会在报表的最下面为你显示估计的执行计划。

计划8是汇集索引扫描,计划7是书签查找。如你所见,使用查询存储分析计划回归很是简单。但你如今还没结束。你如今能够对指定的查询强制执行计划。 如今你知道包含汇集索引扫描的执行计划有更好的性能。所以如今你能够经过点击【强制执行计划】强制查询7使用执行计划。

搞定,咱们已经解决问题了!

如今当你执行存储过程(用80000的输入参数值),在执行计划里你能够看到汇集索引扫描,执行计划只生成419个逻辑读——很简单,是否是?绝对不是!!!!

微软告诉咱们只给修正SQL Server性能相关的“新方式”。你只是强制了特定的计划,一切都还好。这个方法有个大的问题,由于性能问题的根源并无解决!这个问题的关键是由于书签查找计划没有稳定性。取决于首次执行计划默认的输入值,执行计划所以就被不断重用。

一般我会建议调整下你的索引设计,建立一个覆盖索引来保证计划的稳定性。但强制特定执行计划只是临时解决问题——你仍是要修正你问题的根源。

小结

不要误解我:SQL Server 2016里的查询存储功能很棒,能够帮你更容易理解计划回归。它也会帮你“临时”强制特定的执行计划。但性能调优的目标仍是同样:你要找到问题根源,尝试解决问题——不要在外面晃荡!

相关文章
相关标签/搜索