通过非聚集索引让select count(*) from 的查询速度提高几十倍
通过非聚集索引,可以显著提升count(*)查询的性能。
有的人可能会说,这个count(*)能用上索引吗,这个count(*)应该是通过表扫描来一个一个的统计,索引有用吗?
不错,一般的查询,如果用索引查找,也就是用Index Seek了,查询就会很快,之所以快,是由于查询所需要访问的数据只占整个表的很小一部分,如果访问的数据多了,那反而不如通过表扫描来的更快,因为扫描用的是顺序IO,效率更高,比运用随机IO访问大量数据的效率高很多(相应的,如果只需要访问少量数据,那么索引查找的效率远高于表扫描,因为通过随机IO来访问少量数据的效率远高于通过顺序IO来访问少量数据,关键是扫描访问了很多不需要的数据)。
下面通过一个实验,来说明非聚集索引为什么能提高count(*)的查询速度。
1、建表,插入数据
那么如果表没有聚集索引,也没有非聚集索引,效率又会怎么样呢?
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,是可以查到,看下面的代码:
use mastergo--index_id为1表示聚集索引select index_id, index_type_desc, alloc_unit_type_desc, page_count --201650from sys.dm_db_index_physical_stats(db_id('wcc'),object_id('wcc.dbo.test'),1,null,'detailed')dwhere index_level = 0 --只取level为0的,也就是页子级别/*index_idindex_type_descalloc_unit_type_desc page_count1 CLUSTERED INDEX IN_ROW_DATA 201650*/--index_id为2的,表示非聚集索引select index_id, index_type_desc, alloc_unit_type_desc, page_count --4538from sys.dm_db_index_physical_stats(db_id('wcc'),object_id('wcc.dbo.test'),2,null,'detailed')dwhere index_level = 0/*index_idindex_type_descalloc_unit_type_descpage_count2NONCLUSTERED INDEXIN_ROW_DATA4538*/
聚集索引的叶子节点的页数是201650,而非聚集索引的 叶子节点的页数是4538,差了近50倍,而在没有索引的时候,采用表扫描时,叶子节点的页数是201650,与聚集索引一样。
效率的差异不仅在与逻辑读取次数,因为逻辑读取效率本身是很高的,是直接在内存中读取的,但SQL Server的代码需要扫描内存中的数据201650次,也就是循环201650次,可想而知,cpu的使用率会暴涨,会严重影响SQL Server处理正常的请求。
假设这些要读取的页面不在内存中,那问题就大了,需要把硬盘上的数据读到内存,关键是要读201650页,而通过索引只需要读取4538次,效率的差距就会更大。
另外,实验中只是200多万条数据,如果实际生产环境中有2亿条记录呢?到时候,效率的差距会从几十倍上升到几百倍、几千倍,甚至几万倍。