通过非聚集索引,可以显著提升count(*)查询的性能。

有的人可能会说,这个count(*)能用上索引吗,这个count(*)应该是通过表扫描来一个一个的统计,索引有用吗?

不错,一般的查询,如果用索引查找,也就是用Index Seek了,查询就会很快。

之所以快,是由于查询所需要访问的数据只占整个表的很小一部分,如果访问的数据多了,那反而不如通过表扫描来的更快,因为扫描用的是顺序IO,效率更高,比运用随机IO访问大量数据的效率高很多。

相应的,如果只需要访问少量数据,那么索引查找的效率远高于表扫描,因为通过随机IO来访问少量数据的效率远高于通过顺序IO来访问少量数据,之所以扫描的效率较低是由于扫描访问了很多不需要的数据。

那么,通过非聚集索引,提升select count(*) from 的查询速度的本质在于,非聚集索引所占空间的大小往往,远小于聚集索引或堆表所占用的空间大小;

同样的,表中占用较少字节的字段的非聚集索引,对于速度的提升效果,也要远大于,占用较多字节的字段的非聚集索引,因为占用字节少,那么索引占用的空间也少,同样是扫描,只需要更少的时间,对硬盘的访问次数也更少,那么速度就会更快了。

下面通过一个实验,来说明非聚集索引为什么能提高count(*)的查询速度。

1、建表,插入数据

  1. if OBJECT_ID('test') is not null
  2. drop table test
  3. go
  4. create table test
  5. (
  6. id int identity(1,1),
  7. vid int ,
  8. v varchar(600),
  9. constraint pk_test_id primary key (id)
  10. )
  11. go
  12. insert into test(vid,v)
  13. select 1,REPLICATE('a',600) union all
  14. select 2,REPLICATE('b',600) union all
  15. select 3,REPLICATE('c',600) union all
  16. select 4,REPLICATE('d',600) union all
  17. select 5,REPLICATE('e',600) union all
  18. select 6,REPLICATE('f',600) union all
  19. select 7,REPLICATE('g',600) union all
  20. select 8,REPLICATE('h',600) union all
  21. select 9,REPLICATE('i',600) union all
  22. select 10,REPLICATE('j',600)
  23. go
  24. --select POWER(2,18) * 10
  25. --2621440条数据
  26. begin tran
  27. insert into test(vid,v)
  28. select vid,v
  29. from test
  30. commit
  31. go 18
  32. --建立非聚集索引
  33. create index idx_test_vid on test(vid)

2、查看采用聚集索引和非聚集索引后,查询的资源消耗

  1. --输出详细的IO和时间(cpu、流逝的时间)上的开销信息
  2. set statistics io on
  3. set statistics time on
  4. /* 采用聚集索引
  5. SQL Server 分析和编译时间:
  6. CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。
  7. (1 行受影响)
  8. 表 'test'。扫描计数 5,逻辑读取 206147 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
  9. SQL Server 执行时间:
  10. CPU 时间 = 921 毫秒,占用时间 = 277 毫秒。
  11. */
  12. select COUNT(*)
  13. from test with(index (pk_test_id))
  14. /*采用非聚集索引
  15. SQL Server 分析和编译时间:
  16. CPU 时间 = 0 毫秒,占用时间 = 1 毫秒。
  17. (1 行受影响)
  18. 表 'test'。扫描计数 5,逻辑读取 4608 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
  19. SQL Server 执行时间:
  20. CPU 时间 = 327 毫秒,占用时间 = 137 毫秒。
  21. */
  22. select count(*)
  23. from test with(index (idx_test_vid))

另外,下图的两个语句一起执行时的执行计划:

 
那么如果表没有聚集索引,也没有非聚集索引,效率又会怎么样呢?
  1. --删除主键,也就删除了聚集索引
  2. alter table test
  3. drop constraint pk_test_id
  4. --删除非聚集索引
  5. drop index idx_test_vid on test
  6. /* 表扫描
  7. SQL Server 分析和编译时间:
  8. CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。
  9. SQL Server 执行时间:
  10. CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。
  11. SQL Server 分析和编译时间:
  12. CPU 时间 = 0 毫秒,占用时间 = 1 毫秒。
  13. (1 行受影响)
  14. 表 'test'。扫描计数 5,逻辑读取 201650 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
  15. (1 行受影响)
  16. SQL Server 执行时间:
  17. CPU 时间 = 765 毫秒,占用时间 = 233 毫秒。
  18. SQL Server 分析和编译时间:
  19. CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。
  20. SQL Server 执行时间:
  21. CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。
  22. */
  23. select count(*)
  24. from test

 

3、从上面的开销可以看出:

a、通过聚集索引来查询count(*)时,逻辑读取次数206147次,执行时间和占用时间分别是921毫秒和277毫秒,从执行计划中看出,其查询开销是96%。

b、非聚集索引的逻辑读取次数是4608次,而执行时间和占用时间是327毫秒和137毫秒,查询开销是4%。

c、表扫描的逻辑读取次数是201650次,执行时间和占用时间是765毫秒和233毫秒。

这里需要注意的是,由于两个执行计划都采用了并行计划,导致了执行时间远大于占用时间,这主要是因为执行时间算的是多个cpu时间的总和,我的笔记本电脑有4个cpu,那么921/4 大概就是230毫秒左右,也就是每个cpu花在执行上的时间大概是230毫秒左右,和277毫秒就差不多了。

从这些开销信息可以看出,非聚集索引的逻辑读取次数是聚集索引的50分之一,执行时间是聚集索引的2-3分之一左右,查询开销上是聚集索引的24分之一。

很有意思的是,表扫描的逻辑读取次数要比聚集索引的要少4497次,这个逻辑读取次数201650,是可以查到,看下面的代码:

  1. use master
  2. go
  3. --下面的数据库名称是wcc,需要改成你自己的数据库名称
  4. select index_id,
  5. index_type_desc,
  6. alloc_unit_type_desc,
  7. page_count              --页数为:201650
  8. from sys.dm_db_index_physical_stats
  9. (
  10. db_id('wcc'),object_id('wcc.dbo.test'),0,null,'detailed'
  11. )d
  12. /*
  13. index_id    index_type_desc alloc_unit_type_desc    page_count
  14. 0           HEAP            IN_ROW_DATA             201650
  15. */

之所以能查到,是因为全表扫描,无非就是把表中所有的页,都扫描一遍,所以扫描的次数正好是表中的页数201650.

4、那为什么非聚集索引来查询count(*) 的效率是最高的呢?

其实上面分别提到了,通过聚集索引、非聚集索引、表扫描,3种方式来查询,从执行计划可以看出来,3种方式都是扫描,那为什么非聚集索引效率最高?

其实,很简单,谁扫描的次数少,也就是扫描的页数少,那谁的效率当然就高了。

看下面的代码,就明白了:

  1. use master
  2. go
  3. --index_id为1表示聚集索引
  4. select index_id,
  5. index_type_desc,
  6. alloc_unit_type_desc,
  7. page_count                --201650
  8. from sys.dm_db_index_physical_stats
  9. (
  10. db_id('wcc'),object_id('wcc.dbo.test'),1,null,'detailed'
  11. )d
  12. where index_level = 0  --只取level为0的,也就是页子级别
  13. /*
  14. index_id    index_type_desc     alloc_unit_type_desc   page_count
  15. 1           CLUSTERED INDEX     IN_ROW_DATA            201650
  16. */
  17. --index_id为2的,表示非聚集索引
  18. select index_id,
  19. index_type_desc,
  20. alloc_unit_type_desc,
  21. page_count               --4538
  22. from sys.dm_db_index_physical_stats
  23. (
  24. db_id('wcc'),object_id('wcc.dbo.test'),2,null,'detailed'
  25. )d
  26. where index_level = 0
  27. /*
  28. index_id    index_type_desc     alloc_unit_type_desc    page_count
  29. 2           NONCLUSTERED INDEX  IN_ROW_DATA             4538
  30. */

聚集索引的叶子节点的页数是201650,而非聚集索引的 叶子节点的页数是4538,差了近50倍,而在没有索引的时候,采用表扫描时,叶子节点的页数是201650,与聚集索引一样。

效率的差异不仅在与逻辑读取次数,因为逻辑读取效率本身是很高的,是直接在内存中读取的,但SQL Server的代码需要扫描内存中的数据201650次,也就是循环201650次,可想而知,cpu的使用率会暴涨,会严重影响SQL Server处理正常的请求。

假设这些要读取的页面不在内存中,那问题就大了,需要把硬盘上的数据读到内存,关键是要读201650页,而通过索引只需要读取4538次,效率的差距就会更大。

另外,实验中只是200多万条数据,如果实际生产环境中有2亿条记录呢?到时候,效率的差距会从几十倍上升到几百倍、几千倍。

5、那是不是只要是非聚集索引,都能提高select count(*) from查询的效率吗?

这个问题是由下面的网友提出的问题,而想到的一个问题。

如果按照v列来建索引,而v列的数据类型是varchar(600),所以这个新建的索引,占用的页数肯定是非常多的,应该仅次于聚集索引的201650页,那么完成索引扫描的开销肯定大于,按vid列建立的非聚集索引,而vid的数据类型是int。

所以,不是只要是非聚集索引,就能提高查询效率的。

总结一下:

执行select count(*) from查询的时候,要进行扫描,有人可能会说,扫描性能很差呀,还能提高性能?那么,难道用索引查找吗?这样性能只会更差。

这里想说的是,没有最好的技术,只有最适合的技术,要想提高这种select count(*) from查询的性能,那就只能用扫描。

这里,要提高效率的关键,就是减少要扫描的页数,而按照占用字节数少的字段,来建立非聚集索引,那么这个非聚集索引所占用的页数,远远少于聚集索引、按占用字节数较多的列建立的非聚集索引,所占用的页数,这样就能提高性能了。

最后,有两个关于索引的帖子,不错:

两个问题:1,(聚集或者非聚集的)索引页会不会出现也拆分;2,非聚集索引存储时又没排序:

http://bbs.csdn.NET/topics/390594730

继续:非聚集索引行在存储索引键时确实是排序了的,用事实说话,理论+实践:

http://bbs.csdn.net/topics/390595949

通过非聚集索引让select count(*) from 的查询速度提高几十倍、甚至千倍的更多相关文章

  1. 非聚集索引中的临界点(Tipping Point)

    什么是临界点? 注意,我要说的问题是非聚集索引的执行计划从Seek+Lookup变成Table/Clustered Index Scan的临界点.SQL Server的访问数据的IO最小单元是页. 我 ...

  2. SQL Server索引进阶:第二级,深入非聚集索引

    原文地址: Stairway to SQL Server Indexes: Level 2, Deeper into Nonclustered Indexes 本文是SQL Server索引进阶系列( ...

  3. SQL Server 索引和表体系结构(非聚集索引)

    非聚集索引 概述 对于非聚集索引,涉及的信息要比聚集索引更多一些,由于整个篇幅比较大涉及接下来的要写的“包含列的索引”,“索引碎片”等一些知识点,可能要结合起来阅读理解起来要更容易一些.非聚集索引和聚 ...

  4. SQL Server中的聚集索引(clustered index) 和 非聚集索引 (non-clustered index)

    本文转载自  http://blog.csdn.net/ak913/article/details/8026743 面试时经常问到的问题: 1. 什么是聚合索引(clustered index) / ...

  5. sql server临时删除/禁用非聚集索引并重新创建加回/启用的简便编程方法研究对比

    前言: 由于新型冠状病毒影响,博主(zhang502219048)在2020年1月份从广东广州工作地回到广东揭阳产业转移工业园磐东街道(镇里有阳美亚洲玉都.五金之乡,素以“金玉”闻名)老家后,还没过去 ...

  6. SQLSERVER聚集索引与非聚集索引的再次研究(下)

    SQLSERVER聚集索引与非聚集索引的再次研究(下) 上篇主要说了聚集索引和简单介绍了一下非聚集索引,相信大家一定对聚集索引和非聚集索引开始有一点了解了. 这篇文章只是作为参考,里面的观点不一定正确 ...

  7. SQL Server中的联合主键、聚集索引、非聚集索引、mysql 联合索引

    我们都知道在一个表中当需要2列以上才能确定记录的唯一性的时候,就需要用到联合主键,当建立联合主键以后,在查询数据的时候性能就会有很大的提升,不过并不是对联合主键的任何列单独查询的时候性能都会提升,但我 ...

  8. SQL Server - 索引详细教程 (聚集索引,非聚集索引)

    转载自:https://www.cnblogs.com/hyd1213126/p/5828937.html 作者:爱不绝迹 (一)必读:深入浅出理解索引结构 实际上,您可以把索引理解为一种特殊的目录. ...

  9. SQLServer------聚集索引和非聚集索引的区别

    转载: http://www.cnblogs.com/flashicp/archive/2007/05/08/739245.html 建立非聚集索引(vid不是主键) create index idx ...

随机推荐

  1. ArcGIS DataStore手册——入门篇

    第一章:ArcGIS DataStore入门 1.ArcGIS DataStore简介 ArcGIS DataStore是ArcGIS10.3推出来的一个全新的组件,它是一个关系型数据库管理系统,用于 ...

  2. Linux下OCI环境配置

    ORACLE调用接口(Oracle Call Interface简称OCI)提供了一组可对ORACLE数据库进行存取的接口子例程(函数),通过在第三代程序设计语言(如C语言)中进行调用可达到存取ORA ...

  3. 【阿里云产品公测】OpenSearch初体验

    OpenSearch是一个非常有意义的功能,对于很多数据量较大的站点, SF2<   如果搜索功能自己做的话,或者用数据库里的查询语句,首先效率低下,而且占用资源. ); <Le6   另 ...

  4. Distributed TensorFlow

    Distributed TensorFlow Todo list: Distributed TensorFlow简介 Distributed TensorFlow的部署与运行 对3个台主机做多卡GPU ...

  5. 制作rpm安装包

    1.安装rpmbuild软件 sudo apt-get install rpmbuild2.配置工作路径 在制作 rpm 包之前,首先要配置工作路径,也就是制作 rpm 包所在的目录.制作 rpm 包 ...

  6. mybatis ${}使用注意事项

    ${key}有值, ${key,jdbcType=VARCHAR}没有值 总结:${}使用时不要指定jdbcType等属性,指定这些反而取不到值,这也是跟#{}的一个区别.以上只是开发中遇到的情况,具 ...

  7. B+/-Tree原理(mysql索引数据结构)

    B+/-Tree原理 B-Tree介绍 B-Tree是一种多路搜索树(并不是二叉的):       1.定义任意非叶子结点最多只有M个儿子:且M>2:       2.根结点的儿子数为[2, M ...

  8. 我的前端页面开发js简易有效环境

    前端开发主要涉及到html, css(less/sass), javascript这几个方面的知识.真正的快速有效开发,必须实现所谓所见即所得.在构建生产时,可能需要使用gulp/grunt等task ...

  9. javascript 时间日期处理相加,减操作方法js

    javascript 时间日期处理相加,减操作方法js function dateAddDays(dataStr,dayCount){ var strdate = dataStr; // 2017年0 ...

  10. MQ测试

    2015年8月13日23:14:52 测试RabbitMq ====================== 千兆局域网:send ≍10000/s  receive ≍7000/s 百兆局域网:send ...