SQL Server调优系列基础篇(索引运算总结)

前言html

上几篇文章咱们介绍了如何查看查询计划、经常使用运算符的介绍、并行运算的方式,有兴趣的能够点击查看。性能优化

本篇将分析在SQL Server中,如何利用先有索引项进行查询性能优化,经过了解这些索引项的应用方式能够指导咱们如何创建索引、调整咱们的查询语句,达到性能优化的目的。oop

闲言少叙,进入本篇的正题。post

技术准备性能

基于SQL Server2008R2版本,利用微软的一个更简洁的案例库(Northwind)进行解析。学习

简介优化

所谓的索引应用就是在咱们平常写的T-SQL语句中,如何利用现有的索引项,再分析的话就是咱们所写的查询条件,其实大部分状况也无非如下几种:url

一、等于谓词:select ...where...column=@parameterspa

二、比较谓词:select ...where...column> or < or  <> or <= or >= @parameter3d

三、范围谓词:select ...where...column in or not in  or between and @parameter

四、逻辑谓词:select ...where...一个谓词 or、and 其它谓词 or、and 更多谓词....

咱们就依次分析上面几种状况下,如何利用索引进行查询优化的

1、动态索引查找

所谓的动态索引查找就是SQL Server在执行语句的时候,才格式化查询条件,而后根据查询条件的不一样自动的去匹配索引项,达到性能提高的目的。

来举个例子

SET SHOWPLAN_TEXT ON
GO
SELECT OrderID
FROM Orders
WHERE ShipPostalCode IN (N'05022',N'99362')

由于咱们在表Orders的列ShipPostalCode列中创建了非汇集索引列,因此这里查询的计划利用了索引查找的方式。这也是须要创建索引的地方。

咱们来利用文本的方式来查看该语句的详细的执行计划脚本,语句比较长,我用记事本换行,格式化查看

咱们知道这张表的该列里存在一个非汇集索引,因此在查询的时候要尽可能使用,若是经过索引扫描的方式消耗就比较大了,因此SQL Server尽可能想采起索引查找的方式,其实IN关键字和OR关键字逻辑是同样的。

因而上面的查询条件就转换成了: 

                       [Northwind].[dbo].[Orders].[ShipPostalCode]=N'05022'
                        OR 
                       [Northwind].[dbo].[Orders].[ShipPostalCode]=N'99362'

这样就能够采用索引查找了,先查找第一个结果,而后再查找第二个,而这个过程在SQL Server中就被称为:动态索引查找。

是否是有点智能的感受了....

因此有时候咱们写语句的时候,尽可能要使用SQL Server的这点智能了,让其能自动的查找到索引,提高性能。

有时候恰恰咱们写的语句让SQL Server的智能消失,举个例子:

--参数化查询条件
DECLARE @Parameter1 NVARCHAR(20),@Parameter2 NVARCHAR(20)
SELECT @Parameter1=N'05022',@Parameter2=N'99362'
SELECT OrderID
FROM Orders
WHERE ShipPostalCode IN (@Parameter1,@Parameter2)

咱们将这两个静态的筛序值改为参数,有时候咱们写的存储过程灰常喜欢这么作!咱们来看这种方式的生成的查询计划

原本很简单的一个非汇集索引查找搞定的执行计划,咱们只是将这两个数值没有直接写入IN关键字中,而是利用了两个变量来代替。

看看上面SQL Server生成的查询计划!尼玛...这都是些啥???还用起来嵌套循环,我就查询了一个Orders表...你嵌套循环个啥....上面动态索引查找的能力去哪了???

好吧,咱们用文本查询计划来查看下,这个简单的语句到底在干些啥...

 |--Nested Loops(Inner Join, OUTER REFERENCES:([Expr1009], [Expr1010], [Expr1011]))
       |--Merge Interval
       |    |--Sort(TOP 2, ORDER BY:([Expr1012] DESC, [Expr1013] ASC, [Expr1009] ASC, [Expr1014] DESC))
       |         |--Compute Scalar(DEFINE:([Expr1012]=((4)&[Expr1011]) = (4) AND NULL = [Expr1009], [Expr1013]=(4)&[Expr1011], [Expr1014]=(16)&[Expr1011]))
       |              |--Concatenation
       |                   |--Compute Scalar(DEFINE:([Expr1004]=[@Parameter2], [Expr1005]=[@Parameter2], [Expr1003]=(62)))
       |                   |    |--Constant Scan
       |                   |--Compute Scalar(DEFINE:([Expr1007]=[@Parameter1], [Expr1008]=[@Parameter1], [Expr1006]=(62)))
       |                        |--Constant Scan
       |--Index Seek(OBJECT:([Northwind].[dbo].[Orders].[ShipPostalCode]), SEEK:([Northwind].[dbo].[Orders].[ShipPostalCode] > [Expr1009] AND [Northwind].[dbo].[Orders].[ShipPostalCode] < [Expr1010]) ORDERED FORWARD)

挺复杂的是吧,其实我分析了一下脚本,关于为何会生成这个计划脚本的缘由,是为了解决以下几个问题:


一、前面咱们写的脚本在IN里面写的是两个常量值,而且是不一样的值,因此造成了两个索引值的查找经过OR关键字组合,

这种方式貌似没问题,可是咱们将这两个数值变成了参数,这就引来了新的问题,假如这两个参数咱们输入的是相等的,那么利用前面的执行计划就会生成以下

                       [Northwind].[dbo].[Orders].[ShipPostalCode]=N'05022'
                        OR
                       [Northwind].[dbo].[Orders].[ShipPostalCode]=N'05022'

这样执行产生的输出结果就是2条同样的输出值!...可是表里面确实只有1条数据...因此这样输出结果不正确!

因此变成参数后首先解决的问题就是去重问题,2个同样的变成1个。

二、上面变成参数,还引入了另一个问题,加入咱们两个值有一个传入的为Null值,或者两个都为Null值,一样输出结果面临着这样的问题。因此这里还要解决的去Null值的问题。

 

为了解决上面的问题,咱们来粗略的分析一下执行计划,看SQL Server如何解决这个问题的

简单点将就是经过扫描变量中的值,而后将内容进行汇总值,而后在进行排序,再将参数中的重复值去掉,这样获取的值就是一个正确的值,最后拿这些去重后的参数值参与到嵌套循环中,和表Orders进行索引查找。

可是分析的过程当中,有一个问题我也没看明白,就是最好的通过去重以后的常量汇总值,用来嵌套循环链接的时候,在下面的索引查找的时候的过滤条件变成了 and  查找

我将上面的最后的索引查找条件,整理以下:

       |--Index Seek(OBJECT:([Northwind].[dbo].[Orders].[ShipPostalCode]), SEEK:
                    (
                       [Northwind].[dbo].[Orders].[ShipPostalCode] > [Expr1009]
                       AND
                       [Northwind].[dbo].[Orders].[ShipPostalCode] < [Expr1010]

                    ) ORDERED FORWARD)

 

这个地方怎么搞的?我也没弄清楚,还望有看明白童鞋的稍加指导下....

 

好了,咱们继续

上面的执行计划中,提到了一个新的运算符:合并间隔(merge interval operator)

咱们来分析下这个运算符的做用,其实在上面咱们已经在执行计划的图中标示出该运算符的做用了,去掉重复值。

其实关于去重的操做有不少的,好比前面文章中咱们提到的各类去重操做。

这里怎么又冒出个合并间隔去重?其实缘由很简单,由于咱们在使用这个运算符以前已经对结果进行了排序操做,排序后的结果项重复值是牢牢靠在一块儿的,因此就引入了合并间隔的方式去处理,这样性能是最好的。

更重要的是合并间隔这种运算符应用场景不只仅局限于重复值的去除,更重要的是还应用于重复区间的去除。

来看下面的例子

--参数化查询条件
DECLARE @Parameter1 DATETIME,@Parameter2 DATETIME
SELECT @Parameter1='1998-01-01',@Parameter2='1998-01-04'
SELECT OrderID 
FROM ORDERS
WHERE OrderDate BETWEEN @Parameter1 AND DATEADD(DAY,6,@Parameter1)
OR OrderDate BETWEEN @Parameter2 AND DATEADD(DAY,6,@Parameter2)

咱们看看这个生成的查询计划项

能够看到,SQL Server为咱们生成的查询计划,和前面咱们写的语句是如出一辙的,固然咱们的语句也没作多少改动,改动的地方就是查询条件上。

咱们来分析下这个查询条件:

WHERE OrderDate BETWEEN @Parameter1 AND DATEADD(DAY,6,@Parameter1)
OR OrderDate BETWEEN @Parameter2 AND DATEADD(DAY,6,@Parameter2)

很简单的筛选条件,要获取订单日期在1998-01-01开始到1998-01-07内的值或者1998-01-04开始到1998-01-10内的值(不包含开始日期)

这里用的逻辑谓词为:OR...其实也就等同于咱们前面写的IN

可是咱们这里再分析一下,你会发现这两个时间段是重叠的

这个重复的区间值,若是用到前面的直接索引查找,在这段区间以内的搜索出来的范围值就是重复的,因此为了不这种问题,SQL Server又引入了“合并间隔”这个运算符。

 

其实,通过上面的分析,咱们已经分析出这种动态索引查找的优缺点了,有时候咱们为了不这种复杂的执行计划生成,使用最简单的方式就是直接传值进入语句中(固然这里须要重编译),固然大部分的状况咱们写的程序都是只定义的参数,而后进行的运算。可能带来的麻烦就是上面的问题,固然有时候参数多了,为了合并间隔所应用的排序就消耗的内存就会增加。怎么使用,根据场景本身酌情分析。

 

、索引联合

所谓的索引联合,就是根据就是根据筛选条件的不一样,拆分红不一样的条件,去匹配不一样的索引项。

举个例子

SELECT OrderID 
FROM ORDERS
WHERE OrderDate BETWEEN '1998-01-01' AND '1998-01-07'
OR ShippedDate BETWEEN '1998-01-01' AND '1998-01-07'

这段代码是查询出订单中的订单日期在1998年1月1日到1998年1月7日的或者发货日期一样在1998年1月1日到1998年1月7日的。

逻辑很简单,咱们知道在这种表里面这两个字段都有索引项。因此这个查询在SQL Server中就有了两个选择:

 

一、一次性的来个索引扫描根据匹配结果项输出,这样简单有效,可是若是订单表数据量比较大的话,性能就会不好,由于大部分数据就根本不是咱们想要的,还要浪费时间去扫描。

二、就是经过两列的索引字段直接查找获取这部分数据,这样能够直接减小数据表的扫描量,可是带来的问题就是,若是分开扫描,有一部分数据就是重复的:那些同时在1998年1月1日到1998年1月7日的订单,发货日期也在这段时间内,由于两个扫描项都包含,因此再输出的时候须要将这部分重复数据去掉。

 

咱们来看SQL Server如何选择

看来SQL Server通过评估选择了第2中方法。可是上面的方法也不尽完美,采用去重操做耗费了64%的资源。

 

其实,上面的方法,咱们根据生成的查询计划能够变通的使用如下逻辑,其效果和上面的语句是同样的,而且生成的查询计划也同样

SELECT OrderID 
FROM ORDERS
WHERE OrderDate BETWEEN '1998-01-01' AND '1998-01-07'
UNION 
SELECT OrderID 
FROM ORDERS
WHERE  ShippedDate BETWEEN '1998-01-01' AND '1998-01-07'

 

咱们再来看一个索引联合的例子

SELECT OrderID 
FROM ORDERS
WHERE OrderDate = '1998-01-01' 
OR ShippedDate = '1998-01-01'

咱们将上面的Between and不等式筛选条件改为等式筛选条件,咱们来看一下这样造成的执行计划

基本相同的语句,只是咱们改变了不一样的查询条件,可是生成的查询计划仍是变化蛮大的,有几点不一样之处:

 

一、前面的用between...and  的筛选条件,经过索引查找返回的值进行组合是用的串联的方式,所谓的串联就是两个数据集拼凑在一块儿就行,无所谓顺序链接什么的。

二、前面的用between...and  的筛选条件,经过串联拼凑的结果集去重的方式,是排序去重(Sort Distinct)...而且耗费了大量的资源。这里采用了流聚合来干这个事,基本不消耗

咱们来分析如下产生着两点不一样的缘由有哪些:

首先、这里改变了筛选条件为等式链接,所经过索引查找所产生的结果项是排序的,而且按照咱们所要查询的OrderID列排序,所以在两个数据集进行汇总的时候,正适合合并链接的条件!须要提早排序。因此这里最优的方式就是采用合并链接!

那么前面咱们用between...and  的筛选条件经过索引查找获取的结果项也是排序的,可是这里它没有按照OrderID排序,它是按照OrderDate或者ShippedDate列排序的,而咱们的结果是要OrderID列,因此这里的排序是没用的......因此SQL Server只能选择一个串联操做,将结果汇聚到一块儿,而后在排序了......我但愿这里我已经讲明白了...

其次、关于去重操做,毫无疑问采用流聚合(Aggregate)这种方式最好,消耗内存少,速度又快...可是前提是要提早排序...前面选用的排序去重(Sort Distinct)纯属无奈之举...

 

总结下:咱们在写语句的时候能肯定为等式链接,最好采用等式链接。还有就是若是能肯定输出条件的最好能写入,避免多余的书签查找,还有万恶的SELEECT *....

若是写了万恶的SELECT *...那么你所写的语句基本上就能够和非汇集索引查找告别了....顶多就是汇集索引扫描或者RID查找...

瞅瞅如下语句

SELECT * 
FROM ORDERS
WHERE OrderDate = '1998-01-01' 
OR ShippedDate = '1998-01-01'

 

 

最后,奉上一个AND的一个链接谓词的操做方式,这个方式被称为:索引交叉,意思就是说若是两个或多个筛选条件若是采用的索引是交叉进行的,那么使用一个就能够进行查询。

来看个语句就明白了

SELECT OrderID 
FROM ORDERS
WHERE OrderDate = '1998-01-01' 
AND ShippedDate = '1998-03-05'

这里咱们采用了的谓词链接方式为AND,因此在实际执行的时候,虽然两列都存在非汇集索引,理论均可以使用,可是咱们只要选一个最优的索引进行查找,另一个直接使用书签查找出来就能够。省去了前面介绍的各类神马排序去重....流聚合去重....等等不人性的操做。

看来AND链接符是一个很帅的运算符...因此不少时候咱们在尝试写OR的状况下,不如换个思路改用AND更高效。

 

参考文献

结语

此篇文章主要介绍了索引运算的一些方式,主要是描述了咱们日常在写语句的时候所应用的方式,而且举了几个例子,算做抛砖引玉吧,其实咱们日常所写的语句中无非也就本篇文章中介绍的各类方式的更改,拼凑。并且根据此,咱们该怎样创建索引也做为一个指导项。

下一篇咱们介绍子查询一系列的内容,有兴趣可提早关注,关于SQL Server性能调优的内容涉及面很广,后续文章中依次展开分析。

有问题能够留言或者私信,随时恭候有兴趣的童鞋加入SQL SERVER的深刻研究。共同窗习,一块儿进步。

 

文章最后给出上几篇的链接,看来有必要整理一篇目录了.....

SQL Server调优系列基础篇

SQL Server调优系列基础篇(经常使用运算符总结)

SQL Server调优系列基础篇(联合运算符总结)

SQL Server调优系列基础篇(并行运算总结)

SQL Server调优系列基础篇(并行运算总结篇二)

 

若是您看了本篇博客,以为对您有所收获,请不要吝啬您的“推荐”。

相关文章
相关标签/搜索