SQL Server查询性能优化之创建合理的索引(上)

本文主要作为优化查询性能的一些知识储备,感觉知识点有些散,不知道起啥名字好,独立成文又没有达到把每个点都说透彻那样的高度,且就当做创建合理索引的一个楔子把。本文对实际应用没有太大的指导意义,但可以加深我们对SQL Server理解,夯实我们的基本功,就像小说里面的武功一样,没有足够的内功基础,给你再好的秘籍你也成不了武林高手。

  序言

  写这篇文章时表示鸭梨很大,主要是对SQL Server的认识很有限,远远不足把这个话题说清楚,不过还是鼓起勇气写出来,也算作自己对索引认识的一个总结。索引这潭水太深了,应用场景不同,所建立的索引在有些情况下运行良好,有些情况下可能完全无效。而对于索引理解、认识层次不同,怎样建才比较合理的也是众说分云,用万金油的说法就是没有最合理的只有最合适的。一般来说最懂数据库的当属DBA,不过DBA却不懂业务,一般除了一些核心业务模块表建立及索引维护有DBA完成外,大多数情况下索引、SQL维护都是有开发人员完成的,因此我们通常认为的索引建立、优化都是有开发人员完成的(并不是我无视DBA,事实就是这个情况),可能有人疑惑了,DBA不维护索引、不写SQL那他们都干嘛吃去了,这个俺还真不知道,不过DBA很忙、很累我是知道的,估计、可能、大概会做以下事情:数据库并发、分发复制、性能监控、数据迁移、备份、日常维护、索引、SQL调优等等,DBA童鞋别喷我,我是真的不知道,DBA童鞋们可以留言告诉我下哈。

  下面简单说一些暂时想到的对使用索引影响较大的几个注意点

  页是SQL Server存储数据的基本单位,大小为8KB。

  请一定要记住,页是SQL Server进行数据读写的最小I/O单位,而不是行。SQL Server中一个页大小为8KB,每8个页形成一个区,每页8KB,其中页头占用96个字节,页尾的行指示器占用2个字节,还有几个保留字节,也就是一个页8192个字节,能用了存储数据的实际约8000个字节,8000个字节一般可以存储很多行数据。即便SQL Server只访问一行数据,它也要把整个页加载到缓存并从缓存读取数据,我们通常所说的开销主要就是I/O开销,这点不少人都没有清醒的认知。

  认识窄索引

  很多书籍和文章都写过索引要使用窄索引,窄索引是个什么东东呢,大白话就是把索引建在字节长度比较小的列上,比如int占用4个字节,bigint占用8个字节,char(20)占用20个字节nchar(20)最占用40个字节,那么int 相对于bigint来说就是窄了(占用字节数更少),bigint比char(20)也要窄,char(20)和nchar(20)相比要窄(nchar(20)每个字符占用2个字节)。

  明白了啥是窄索引我们来说下为什么要使用窄索引,我们知道数据存储和读取的最小单位是页,一个页8K大小,当使用比较窄的列做索引列时,每个页能存储的数据就更多,以int和bigint为例,一个8K的页大约能存储8*1024/4(int 4个字节)=2048(实际值要比这个数字小)条数据,使用bigint大约能存储8*1024/8(bigint为8个字节)=1024(实际值要比这个数字小)条数据,也就是说索引列的长度也小,每个页能存储的数据也就越多,反过来说就是存储索引所需要的页数也就越少,页数少了进行索引查找时需要检索的页自然也就少了,检索页数少了IO开销也就随之减少,查询效率自然也就高了。

  认识索引的二叉树级数

  SQL Server中所有的索引都是平衡二叉树结构,平衡树的意思是所有叶子节点到根节点的距离都相同,SQL Server进行索引查找时总是从索引的根节点开始,并从根跳到下一级的相应页,并继续从一个级别跳到下一个级别,直到达把可以查找键的叶子页。所有叶级节点到底跟的距离都是相同的,这意味着一次查找操作在叶读取上的成本正好是页的级数。索引级数大多为2级到4级,2级索引大约包含几千行,3级索引大约4 000 000行,4级索引能容纳约4 000 000 000 行,这点上聚集索引和非聚集索引上是一样的。一般来说对于小表来说索引通常只有2级,对于大表通常包含3级或4级。

  索引级数也就意味着一次索引查找的最小开销,比如我们建立一个User表


CREATE     TABLE Users
(
    UserID INT IDENTITY,
    UserName nvarchar(50),
Age INT,
Gender BIT,
    CreateTime DateTime
)

--在UserID列上创建聚集索引 IX_UserID
CREATE UNIQUE CLUSTERED INDEX IX_UserID ON dbo.Users(UserID)
--插入示例数据
insert into Users(UserName,Age,Gender,CreateTime)
select N'Bob',20,1,'2012-5-1'
union all
select N'Jack',23,0,'2012-5-2'
union all
select N'Robert',28,1,'2012-5-3'
union all
select N'Janet',40,0,'2012-5-9'
union all
select N'Michael',22,1,'2012-5-2'
union all
select N'Laura',16,1,'2012-5-1'
union all
select N'Anne',36,1,'2012-5-7'

  我们执行查询

SELECT * FROM dbo.Users WHERE UserID=1

  可以看到输出信息为

(1 行受影响)
表 'Users'。扫描计数 0,逻辑读取 2 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。

  当表数据量增多至几千条时,执行上述查询逻辑读依然为2,当数据量到达百万级别时逻辑读会变成3,当到达千万、亿级别时,逻辑读就会变成4,有兴趣的童鞋可以亲自去试验下。

  认识书签查找的开销

  当使用非聚集索引时,若查询条件没有实现索引覆盖就会形成书签查找,那么一次书签查找的开销是多少呢?答案是不一定,一般为1到4次逻辑读,对于堆表(无聚集索引的表)来说,一次书签查找只需要一次逻辑读,对于非堆表(有聚集索引的表)来说一次书签查找的逻辑读次数为聚集索引的级数,在索引结构我们简单说了下大多数聚集索引的级数为2-4级,也就是说对于非堆表来说一次逻辑读的开销为2-4次逻辑读,下面我们做具体测试

  创建非聚集索引 IX_UserName

CREATE INDEX IX_UserName ON dbo.Users(UserName)

  执行查询

SELECT UserID,UserName FROM dbo.Users WITH(INDEX(IX_UserName))WHERE UserName =('Bob')

  由于我们的查询实现了索引覆盖,没有书签查找,获取一条数据需要2次逻辑读


(1 行受影响)
表 'Users'。扫描计数 1,逻辑读取 2 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。

(1 行受影响)

  修改查询,返回列中增加CreateTime,这样就无法实现索引覆盖

SELECT UserID,UserName,CreateTime FROM dbo.Users WITH(INDEX(IX_UserName))WHERE UserName =('Bob')

  可以看到这时发生了书签查找,同样返回一条数据,逻辑读达到了4次(索引查找2次,书签查找2次),由于我们的表Users为非堆表,故一次书签查找需要2次(聚集索引IS_UserID的级数为2)逻辑读


(1 行受影响)
表 'Users'。扫描计数 1,逻辑读取 4 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。

(1 行受影响)

  然后我们继续测试堆表的书签查找开销,删除表Users的聚集索引IX_UserID,使其变为堆表

DROP INDEX IX_UserID ON dbo.Users

  再次执行我们的查询,可以看到逻辑读变成了3次(索引查找2次,书签查找1次),想想为什么堆表的书签查找次数少呢?

SELECT UserID,UserName,CreateTime FROM dbo.Users WITH(INDEX(IX_UserName))WHERE UserName =('Bob')


(1 行受影响)
表 'Users'。扫描计数 1,逻辑读取 3 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。

(1 行受影响)

  使用索引时的随机读

  嗯,关于随机读我现在也有些迷糊,只是大概的知道发生随即读时即便需要的数据就存在同一页上,也会发生多次读取,而不是一次拿到整页数据后进行筛选。当进行where in查找或发生书签查找时,一定会使用随机读

  首先我们看看聚集索引的随即读表现

--创建聚集索引IX_UserID
CREATE UNIQUE CLUSTERED INDEX IX_UserID ON dbo.Users(UserID)

  执行如下查询,可以发现在聚集索引上面使用where in时不管有没有找到记录都会进行聚集索引查找,而且查找次数固定为where in里面的条件数*索引级数,不知道为什么明明索引扫描有着更高的效率,查询优化器还是选择聚集索引查找,有知道的朋友还请告知下哈

--这个聚集索引扫描,返回记录7条,逻辑读2次
SELECT * FROM dbo.Users
--这个聚集索引查找,返回记录3条,逻辑读2次
SELECT * FROM dbo.Users WHERE UserID<=2
--这个聚集索引查找,返回记录3条,逻辑读6次
SELECT * FROM dbo.Users WHERE UserID IN(1,2)
--这个聚集索引查找,返回记录0条,逻辑读6次
SELECT * FROM dbo.Users WHERE UserID IN(10,20,22)


(7 行受影响)
表 'Users'。扫描计数 1,逻辑读取 2 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。

(1 行受影响)

(3 行受影响)
表 'Users'。扫描计数 1,逻辑读取 2 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。

(1 行受影响)

(3 行受影响)
表 'Users'。扫描计数 3,逻辑读取 6 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。

(1 行受影响)

(0 行受影响)
表 'Users'。扫描计数 3,逻辑读取 6 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。

(1 行受影响)

  我们再来看下非聚集索引的随机读,当然我这里为了演示特意使用了索引提示,实际应用中没事千万别加索引提示,当使用非聚集索引时查询优化器发现使用索引后效率更低时会放弃索引转为使用表扫描,我们这个例子中若去掉索引提示的话使用表扫描仅需要2次逻辑读就可以完成查询,这里仅仅是为了演示


--非聚集索引查找,返回记录2,逻辑读2
SELECT UserID,UserName FROM dbo.Users WITH(INDEX(IX_UserName))WHERE UserName  LIKE 'J%'
--非聚集索引查找,返回记录2,逻辑读4
SELECT UserID,UserName FROM dbo.Users WITH(INDEX(IX_UserName))WHERE UserName IN('Bob','Jack')

--非聚集索引查找+书签查找,返回记录2,逻辑读6
SELECT UserID,UserName,CreateTime FROM dbo.Users WITH(INDEX(IX_UserName))WHERE UserName  LIKE 'J%'

--非聚集索引查找+书签查找,返回记录0,逻辑读2(索引查找2*1+书签查找2*0)
SELECT UserID,UserName,CreateTime FROM dbo.Users WITH(INDEX(IX_UserName))WHERE UserName IN('Rabbit')
--非聚集索引查找+书签查找,返回记录1,逻辑读4(索引查找2*1+书签查找2*1)
SELECT UserID,UserName,CreateTime FROM dbo.Users WITH(INDEX(IX_UserName))WHERE UserName IN('Bob')
--非聚集索引查找+书签查找,返回记录2,逻辑读8(索引查找2*2+书签查找2*2)
SELECT UserID,UserName,CreateTime FROM dbo.Users WITH(INDEX(IX_UserName))WHERE UserName IN('Bob','Jack')


(2 行受影响)
表 'Users'。扫描计数 1,逻辑读取 2 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。

(1 行受影响)

(2 行受影响)
表 'Users'。扫描计数 2,逻辑读取 4 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。

(1 行受影响)

(2 行受影响)
表 'Users'。扫描计数 1,逻辑读取 6 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。

(1 行受影响)

(0 行受影响)
表 'Worktable'。扫描计数 0,逻辑读取 0 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
表 'Users'。扫描计数 1,逻辑读取 2 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。

(1 行受影响)

(1 行受影响)
表 'Users'。扫描计数 1,逻辑读取 4 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。

(1 行受影响)

(2 行受影响)
表 'Users'。扫描计数 2,逻辑读取 8 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。

(1 行受影响)

  总结:

  认识书索引的查找开销、书签查找的开销、随机读的影响对我们具体创建索引和编写SQL有着积极的影响,毕竟对查询的的认识更加清楚了,我们在写索引和SQL时候才有更有针对性,最起码我们又知道了一个尽量不适用where in的理由,为什么要尽量规避书签查找,聚集索引查找不见得就一定高效,顺便留个问题:聚集索引扫描和非聚集索引扫描哪个有着更高的效率,什么情况下会发生非聚集索引扫描?

  嗯,暂且写到这里,还是脑子里的墨水太少了,写点东西就感觉脑子里空荡荡的......

  

====================================分割线================================

最新内容请见作者的GitHub页:http://qaseven.github.io/

时间: 2024-09-17 04:43:30

SQL Server查询性能优化之创建合理的索引(上)的相关文章

SQL Server查询性能优化之创建合理的索引(下)

续上一篇SQLServer查询性能优化之创建合理的索引(上) 数据库索引分为聚集索引和非聚集索引,聚集索引就是物理索引,也就是数据的物理的存储顺序,聚集索引的叶子节点就是数据行本身:非聚集索引是逻辑索引,也可以简单的认为是对聚集索引建立的索引,一般来说聚集索引的键就是非聚集索引的叶子节点(在不使用include时). 关于索引的选择 对于索引类型来说没什么好选的,一般来说聚集索引是必须的(有特殊需要的另说),非聚集索引看实际需要灵活建立.因此对于索引来说主要是决定在那些列上建立索引,尤其是对于聚

Sql Server 查询性能优化之走出索引的误区分析_MsSql

据了解绝大多数开发人员对于索引的理解都是一知半解,局限于大多数日常工作没有机会.也什么没有必要去关心.了解索引,实在哪天某个查询太慢了找到查询条件建个索引就ok,哪天又有个查询慢了,再建立个索引就是,或者干脆把整个查询SQL直接发给DBA,让DBA直接帮忙优化了,所以造成的状况就是开发人员对于索引的理解.认识很局限,以下就把我个人对于索引的理解及浅薄认识和大家分享下,希望能解除一些大家的疑惑,一起走出索引的误区 误区1.在表上建立了索引,在查询时用到了索引的列,索引就一定会生效 首先明确下这样的

分析Sql Server查询性能优化之走出索引的误区

误区1.在表上建立了索引,在查询时用到了索引的列,索引就一定会生效 首先明确下这样的观点是错误的,SQL Server查询优化器是基于开销进行选择的优化器,通过一系列复杂判断来决定是否使用索引.使用什么类型索引.使用那个索引.SQL Server内部维护着索引列上的数据的统计,统计信息会随着索引列内容的变化而变化,索引的有效期完全取决于索引列上的统计信息,随着数据的变化关于索引的检索机制也随之变化.对于查询优化器来说始终保持查询开销最低始终是其的不二选择,如果一个非聚集索引的列上有大量的重复值,

Sql Server 查询性能优化之走出索引的误区分析

据了解绝大多数开发人员对于索引的理解都是一知半解,局限于大多数日常工作没有机会.也什么没有必要去关心.了解索引,实在哪天某个查询太慢了找到查询条件建个索引就ok,哪天又有个查询慢了,再建立个索引就是,或者干脆把整个查询SQL直接发给DBA,让DBA直接帮忙优化了,所以造成的状况就是开发人员对于索引的理解.认识很局限,以下就把我个人对于索引的理解及浅薄认识和大家分享下,希望能解除一些大家的疑惑,一起走出索引的误区 误区1.在表上建立了索引,在查询时用到了索引的列,索引就一定会生效 首先明确下这样的

SQL Server 查询性能优化 相关文章

来自: SQL Server 查询性能优化--堆表.碎片与索引(一) SQL Server 查询性能优化--堆表.碎片与索引(二) SQL Server 查询性能优化--覆盖索引(一) SQL Server 查询性能优化--覆盖索引(二) SQL Server 查询性能优化--创建索引原则(一) SQL Server 查询性能优化--创建索引原则(二) SQL Server 查询性能优化--索引与SARG(一) SQL Server 查询性能优化--索引与SARG(二) SQL Server 查

SET STATISTICS IO和SET STATISTICS TIME 在SQL Server查询性能优化中的作用

原文:SET STATISTICS IO和SET STATISTICS TIME 在SQL Server查询性能优化中的作用 近段时间以来,一直在探究SQL Server查询性能的问题,当然也漫无目的的查找了很多资料,也从网上的大神们的文章中学到了很多,在这里,向各位大神致敬.正是受大神们无私奉献精神的影响,所以小弟也作为回报,分享一下关于SET STATISTICS IO和SET STATISTICS TIME这两条T_SQL命令,在查询优化性能中的作用.       首先我想说明一下这篇文章

Sql Server查询性能优化之不可小觑的书签查找介绍_MsSql

小小程序猿SQL Server认知的成长 1.没毕业或工作没多久,只知道有数据库.SQL这么个东东,浑然分不清SQL和Sql Server Oracle.MySql的关系,通常认为SQL就是SQL Server 2.工作好几年了,也写过不少SQL,却浑然不知道索引为何物,只知道数据库有索引这么个东西,分不清聚集索引和非聚集索引,只知道查询慢了建个索引查询就快了,到头来索引也建了不少,查询也确实快了,偶然问之:汝建之索引为何类型?答曰:... 3.终于受到刺激开始奋发图强,买书,gg查资料终于知道

Sql Server查询性能优化之不可小觑的书签查找介绍

小小程序猿SQL Server认知的成长 1.没毕业或工作没多久,只知道有数据库.SQL这么个东东,浑然分不清SQL和Sql Server Oracle.MySql的关系,通常认为SQL就是SQL Server 2.工作好几年了,也写过不少SQL,却浑然不知道索引为何物,只知道数据库有索引这么个东西,分不清聚集索引和非聚集索引,只知道查询慢了建个索引查询就快了,到头来索引也建了不少,查询也确实快了,偶然问之:汝建之索引为何类型?答曰:... 3.终于受到刺激开始奋发图强,买书,gg查资料终于知道

在SQL server的性能优化过程中的常见技巧

在SQL server 的http://www.aliyun.com/zixun/aggregation/14109.html">性能优化过程中,TSQL的语句优化是很重要的一环.当您使用各种手段找出系统最需要优化的语句后,应该如何对该语句进行优化呢?下面列出一些TSQL 语句优化的常见技巧. 1. 语句的执行计划分析 首先要对该语句的执行计划(execution plan)进行分析,找出语句运行慢的原因.比如说, <>在检查执行计划是否包含table scan /index