前言

索引是关系数据库中最重要的对象之中的一个,他能显著降低磁盘I/O及逻辑读取的消耗,并以此来提升 SELECT 语句的查找性能。但它是一把双刃剑。使用不当反而会影响性能:他须要额外的空间来存放这些索引信息。而且当数据更新时须要一些额外开销来保持索引的同步。

形象的来说索引就像字典里的文件夹,你要查找某一个字的时候能够依据它的比划/拼音先在文件夹中找到相应的页码范围,然后在该范围中找到这个字。假设没有这个文件夹(索引),你可能须要翻遍整本字典来找到要找的字。

SQL Server 中的索引以 B-Tree 的形式存储,例如以下图:

建立聚集索引(clustered index)来改进性能

RDBMS 随着数据的增长都会面临查询性能的下降,索引就是专门设计来解决问题的。聚集索引是全部索引的基础。没有它数据表就是一个堆(heap)。聚集索引决定了数据的物理存储形态,所以一张表上仅仅能有一个聚集索引。

SQL Server 的 sys.partitions 系统视图中记录着全部聚集索引的信息(它们的 Index_ID为1)。

聚集索引能够包括多个字段(列),通常应挑选绝大多数查询语句中常常涉及到的筛选字段,而且事先了解下面几点:

  • 字段应当包括大量的非反复的值。比如:身份证号
  • 默认情况下主键字段将自己主动建立聚集索引。但这不是必须的,你能够手工改动为非聚集索引(non-clustered index)
  • 字段常常參与筛选,即:常常在 WHERE, JOIN, ORDER BY, GROUP BY 语句中使用
  • 字段常常參与比較。即:常常參与 >, <, >=, <=, BETWEEN, IN 运算
  • 字段长度越短越好

另外在可能的情况下建议对聚集索引实施下面规则:

  • 包括的字段都设为唯一(unique)且非空(NOT NULL)
  • 包括字段的长度越短越好,包括的字段越少越好
  • 每张表都有聚集索引,而且把 WHERE 中常常使用到的字段作为该聚集索引的字段
  • 尽量避免在 varchar 列上建立聚集索引

我们来做一次10w条数据的性能比較(測试数据的生成SQL请參照附录):

SELECT OrderDate,Amount,Refno FROM ordDemo WHERE Refno<3

索引建立前的运行计划:

CREATE CLUSTERED INDEX idx_refno ON ordDemo(refno)
GO
--再次运行同样的查询语句
SELECT OrderDate,Amount,Refno FROM ordDemo WHERE Refno<3
GO

建立索引后的运行计划:

通过对照我们可发现I/O 消耗从 0.379421 减少为 0.0571991,而且从 Table Scan 处理转变为 Index Seek。

建立非聚集索引(non-clustered index)来改善性能

上面提到了索引能有效改善查询性能,但因为一张表仅仅能有一个聚集索引。而一个聚集索引通常无法包括全部必要的列,所以 SQL Server 同意我们建立非聚集索引来实现这个需求。

【 SQL Server 2005 及之前的版本号同意建立249 个非聚集索引;SQL Server 2008 及 SQL Server 2012 同意999个非聚集索引】

通常当你在某一个字段上建立一个唯一键(unique key)的时候。SQL Server 会自己主动在该列上建立一个非聚集索引。

sys.partitions 系统表中存放着非聚集索引的相关信息(Index_ID>1)。

在为某张表建立非聚集索引之前请先确认两点:该表是否真的须要非聚集索引?该表是否有合适的字段来建立非聚集索引?

这是由于索引建得不好不但不能带来性能的提高,还会花费额外的空间来存放索引并产生额外的 I/O 操作!

建立非聚集索引选择字段时应遵循下面规则:

  • 字段应当包括大量的非反复的值。
  • 字段常常參与等值(=)运算
  • 字段常常參与筛选,即:常常在 JOIN, ORDER BY, GROUP BY 语句中使用

我们继续之前的測试。来看看非聚集索引带来的速度提升:

SELECT OrderDate FROM ordDemo
WHERE OrderDate='2011-11-28 20:29:00.000'
GO

运行计划例如以下图:

建立非聚集索引,并再次运行查询:

CREATE NONCLUSTERED INDEX idx_orderdate
on ordDemo(orderdate)
GO SELECT OrderDate FROM ordDemo
WHERE OrderDate='2011-11-28 20:29:00.000'
GO



比較结果很明显,非聚集索引建立之后 I/O Cost, CPU Cost, Operator Cost 等消耗大幅下降。

在我们的样例中因为OrderDate 字段并不在聚集索引中,所曾经一次的查询被解释成一个index scan。当我们在OrderDate 上建立一个非聚集索引后,查询将利用起该索引并解释成 index seek。

随着表的数据越来越多,用来存放非聚集索引的空间也会越来越大。并逐渐对性能造成影响。遇到这样的情况能够把非聚集索引建立在独立的数据库文件或文件组(filegroup)中,从而降低对同一个文件的 I/O 操作压力。

合理的索引覆盖来改善性能

运行以下的測试 SQL

SELECT OrderDate,OrderID FROM ordDemo
WHERE OrderDate='2011-11-28 20:29:00.000'
GO

观察运行计划后你会发现查询被解析为 index scan,而不是先前的 index seek?这是由于我们已建立的两个索引都没有包括 OrderId 字段。

把 non-clustered Index 删掉了,又一次建一下(把OrderId 字段也作为索引的字段)

CREATE NONCLUSTERED INDEX idx_orderdate_orderId
on ordDemo(orderdate DESC,OrderId ASC)
GO

再次运行查询,运行计划例如以下图

查询不出意料的再次被解析为 index seek。

注意:

一个索引中最多包括16个字段,而且这些字段的长度必须小于 900 byte。

下面类型不能作为索引的keyword段(text, ntext, image, nvarchar(max), varchar(max), varbinary(max))

调整索引的包括字段(including columns)来提高性能

索引的包括字段的概念起源自 SQL Server 2005,SQL Server 2008 及 2012 也具备该功能。它同意你在非聚集索引中包括非键值(non-key)字段,这些字段不会记入索引的大小(这样我们也就不太会促发上文提到的索引字段上限)。另外这些字段的类型能够是除 text, ntext, image 之外的不论什么类型。

在前文的測试案例中 OrderId 并非一个keyword段,由于他并没有在 WHERE 子句中进行筛选,所以把他作为索引的keyword段并不合适。如今我们用 INCLUDE 来把它建立为包括字段:

--删除前文的索引
DROP INDEX idx_orderdate_orderId ON ordDemo
GO --重建索引
CREATE NONCLUSTERED INDEX idx_orderdate_Included
on ordDemo(orderdate DESC)
INCLUDE(OrderID)
GO --再次查询
SELECT OrderDate,OrderID FROM ordDemo
WHERE OrderDate='2011-11-28 20:29:00.000'
GO

运行计划例如以下图:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc3FsY2hlbg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">

从性能上来说本节的优化结果与上一节的差点儿一致,但採用了包括字段索引(include column index) 后,你受到的限制更小,并伴随着索引keyword段的降低,索引的占用也变小查询起来更高效。

总结下区分索引keyword段及包括字段的基本原则:

  • WHERE, ORDER BY, GROUP BY, JOIN-ON 中的使用到的字段适用于keyword段
  • SELECT, HAVING 中的使用到的字段适用于包括字段

使用过滤索引(filtered index)来提高性能

过滤索引起源自 SQL Server 2008 ,SQL Server 2012 也具备该功能,你能够把它看成一个带着 WHERE 子句的非聚集索引。

适当地使用能降低索引的存储尺寸及维护消耗,同一时候提高查询性能。

常规的索引都是对整张表的每条数据进行索引,而过滤索引只对满足特定条件的记录进行索引,这个特定条件在建立过滤索引时通过 WHERE 子句来定义。

类似下面的场景你能够考虑採用过滤索引:

一张包括多年数据的巨型表。实际使用中仅查询当年数据。

一张记录产品类别的表,包括很多过期不再使用的类别。

一个订单表。包括OrderStartDate 及 OrderEndDate 字段。当订单完毕时更新OrderEndDate,其它情况为 null。你能够在 OrderEndDate 上建立过滤索引,这样当你须要查询哪些订单未完毕时能够利用。

在建立过滤索引时须要进行一些设定:

  • ARITHABORT = ON
  • CONCAT_NULL_YIELDS_NULL = ON
  • QUOTED_IDENTIFIER = ON
  • ANSI_WARNINGS = ON
  • ANSI_NULLS = ON
  • ANSI_PADDING = ON
  • NUMERIC_ROUNDABORT = OFF
来看一下演示样例:
SET ANSI_NULLS ON
SET ANSI_PADDING ON
SET ANSI_WARNINGS ON
SET ARITHABORT ON
SET CONCAT_NULL_YIELDS_NULL ON
SET QUOTED_IDENTIFIER ON
SET NUMERIC_ROUNDABORT OFF
GO CREATE NONCLUSTERED INDEX idx_orderdate_Filtered
on ordDemo(orderdate DESC)
INCLUDE(OrderId)
WHERE OrderDate = '2011-11-28 20:29:00.000'
GO SELECT OrderDate,OrderID FROM ordDemo WHERE OrderDate='2011-11-28 20:29:00.000'
GO

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc3FsY2hlbg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">

I/O 消耗从上一节的0.0078751 降低为 0.003125。优化效果很显著。

使用列存储索引(columnstore index)来提高性能

眼下为止我们讨论的都是行存储索引(rowstore index),SQL Server 2012 開始支持列存储索引。

行存储索引在数据页(data page)中保存数据行,列存储索引在数据页中保存数据列。

如果我们有一张表(tblEmployee)。包含 empId, FirstName, LastName 三列。行存储索引/列存储索引表现为下面存储形式:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc3FsY2hlbg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">

显然当你须要对某几列值进行查找筛选的时候,列存储索引须要訪问的数据页更少,从而减少了I/O开销,并因此提高了运行效率。

在你决定採用列存储索引之前建议你确认一下3点:

  • 你的数据表能否够设定为仅仅读(read-only)
  • 你的数据表是否很巨大(百万级以上)
  • 假设你的数据库是个OLTP。能否同意你切换(开/关)列存储索引

假设以上3点的答案都是OK的。那么你能够開始使用列存储索引了。只是你还会受到下面限制:

  • 你不能包括1024个以上字段
  • 字段类型仅仅能是下面几种:

int

big int

small int

tiny int

money

smallmoney

bit

float

real

char(n)

varchar(n)

nchar(n)

nvarchar(n)

date

datetime

datetime2

small datetime

time

datetimeoffset (precision <=2)

decimal 或 numeric (precision <=18)

好。我们来试验一下列存储索引:

运行下面的代码,依据输出的运行计划能够发现它已经利用了我们先前建立的聚集索引(行存储索引)。

SELECT
Refno
,sum(Amount) as SumAmt
,avg(Amount) as AvgAmt
FROM
ordDemo
WHERE
Refno>3
Group By
Refno
Order By
Refno
GO

接着我们把已经存在的行存储索引删除,建立列存储索引:

DROP INDEX idx_refno ON ordDemo

CREATE NONCLUSTERED COLUMNSTORE INDEX
idx_columnstore_refno
ON ordDemo (Amount,refno)

再次运行同样的查询语句,运行计划例如以下图:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc3FsY2hlbg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">

通过比較。我们能够发现I/O消耗显著下降:)

注意:因为建立了列存储索引,此时该表是仅仅读的,假设你要恢复成可写的状态必须删除这个列存储索引!

附录

生成測试数据的SQL代码:

--建表
CREATE TABLE ordDemo (OrderID INT IDENTITY, OrderDate DATETIME,Amount MONEY, Refno INT)
GO --插入 100000 条測试数据
INSERT INTO ordDemo (OrderDate, Amount, Refno)
SELECT TOP 100000
DATEADD(minute, ABS(a.object_id % 50000 ), CAST('2011-11-04' AS DATETIME)), ABS(a.object_id % 10), CAST(ABS(a.object_id % 13) AS VARCHAR)
FROM sys.all_objects a
CROSS JOIN sys.all_objects b
GO

SQL Server 性能调优2 之索引(Index)的建立的更多相关文章

  1. SQL Server 性能调优培训引言

    原文:SQL Server 性能调优培训引言 大家好,这是我在博客园写的第一篇博文,之所以要开这个博客,是我对MS SQL技术学习的一个兴趣记录. 作为计算机专业毕业的人,自己对技术的掌握总是觉得很肤 ...

  2. sql server 性能调优之 资源等待PAGELATCH

    一.概述 在前几章介绍过 sql server 性能调优资源等待之PAGEIOLATCH,PAGEIOLATCH是出现在sql server要和磁盘作交互的时候,所以加个IO两个字.这次来介绍PAGE ...

  3. sql server 性能调优之 资源等待 LCk

    一.  概述 这次介绍实例级别资源等待LCK类型锁的等待时间,关于LCK锁的介绍可参考 “sql server 锁与事务拨云见日”.下面还是使用sys.dm_os_wait_stats 来查看,并找出 ...

  4. sql server 性能调优之 CPU消耗最大资源分析1 (自sqlserver服务启动以后)

    一. 概述 上次在介绍性能调优中讲到了I/O的开销查看及维护,这次介绍CPU的开销及维护, 在调优方面是可以从多个维度去发现问题如I/O,CPU,  内存,锁等,不管从哪个维度去解决,都能达到调优的效 ...

  5. sql server性能调优

    转自:https://www.cnblogs.com/woodytu/tag/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E5%9F%B9%E8%AE%AD/defaul ...

  6. CPU开销sql server 性能调优

    sql server 性能调优 CPU开销分析 一. 概述 上次在介绍性能调优中讲到了I/O的开销查看及维护,这次介绍CPU的开销及维护, 在调优方面是可以从多个维度去发现问题如I/O,CPU, 内存 ...

  7. [转]SQL Server 性能调优(io)

      目录 诊断磁盘io问题 常见的磁盘问题 容量替代了性能 负载隔离配置有问题 分区对齐配置有问题 总结 关于io这一块,前面的东西如磁盘大小,磁盘带宽,随机读取写入,顺序读取写入,raid选择,DA ...

  8. sql server 性能调优 资源等待之网络I/O

    原文:sql server 性能调优 资源等待之网络I/O 一.概述 与网络I/O相关的等待的主要是ASYNC_NETWORK_IO,是指当sql server返回数据结果集给客户端的时候,会先将结果 ...

  9. sql server 性能调优 资源等待之内存瓶颈的三种等待类型

    原文:sql server 性能调优 资源等待之内存瓶颈的三种等待类型 一.概述 这篇介绍Stolen内存相关的主要三种等待类型以及对应的waittype编号,CMEMTHREAD(0x00B9),S ...

随机推荐

  1. ES学习——分析器和自定义分析器

    简介 es在对文档进行倒排索引的需要用分析器(Analyzer)对文档进行分析.建立索引.从文档中提取词元(Token)的算法称为分词器(Tokenizer),在分词前预处理的算法称为字符过滤器(Ch ...

  2. android开发面试题

    找了将近两个星期的工作,面试了5家公司,罗列一下笔试或者面试时的问题,祝大家好运 1,handler机制 答:handler执行机制:1).在主线程中创建handler 2).子线程中借助主线程的ha ...

  3. HDU 1709

    MB,一开始就想到是不是只要加上一个不选择砝码的情况,但一直没动手做,因为看了看网上了,觉得总有点复杂,认为自己想错了.... 相信自己 #include <iostream> #incl ...

  4. 《iOS Human Interface Guidelines》——System Button

    系统button 系统button运行一个app特定的动作. API NOTE 在iOS 7中,UIButtonTypeRoundedRect被又一次定义成UIButtonTypeSystem.查看U ...

  5. [Gatsby] Install Gatsby and Scaffold a Blog

    In this lesson, you’ll install Gatsby and the plugins that give the default starter the ability to t ...

  6. HBase基本数据操作具体解释

    引言 本文档參考最新(截止2014年7月16日)的官方Ref Guide.Developer API编写. 全部代码均基于"hbase 0.96.2-hadoop2"版本号编写.均 ...

  7. setsockopt 设置socket

    1.closesocket(一般不会立即关闭而经历TIME_WAIT的过程)后想继续重用该socket:BOOL bReuseaddr=TRUE;setsockopt(s,SOL_SOCKET ,SO ...

  8. 93.快速搭建Web环境 Angularjs + Express3 + Bootstrap3

    转自:https://www.cnblogs.com/wawahaha/p/3946023.html 前言 Angularjs越用越顺手,不仅代码量比jQuery少很多,而且实现思路特别清晰,构建大型 ...

  9. xBIM 实战01 在浏览器中加载IFC模型文件

    系列目录    [已更新最新开发文章,点击查看详细]  一.创建Web项目 打开VS,新建Web项目,选择 .NET Framework 4.5  选择一个空的项目 新建完成后,项目结构如下: 二.添 ...

  10. 前端模块化 | 解读JS模块化开发中的 require、import 和 export

    本篇分为两个部分 第一部分:总结了ES6出现之前,在当时现有的运行环境中,实现"模块"的方式: 第二部分:总结了ES6出现后,module成为ES6标准,客户端实现模块化的解决方案 ...