日期:2014-05-16  浏览次数:20523 次

通过非聚集索引让select count(*) from 的查询速度提高几十倍

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

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


不错,一般的查询,如果用索引查找,也就是用Index Seek了,查询就会很快,之所以快,是由于查询所需要访问的数据只占整个表的很小一部分,如果访问的数据多了,那反而不如通过表扫描来的更快,因为扫描用的是顺序IO,效率更高,比运用随机IO访问大量数据的效率高很多(相应的,如果只需要访问少量数据,那么索引查找的效率远高于表扫描,因为通过随机IO来访问少量数据的效率远高于通过顺序IO来访问少量数据,关键是扫描访问了很多不需要的数据)。


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


1、建表,插入数据


if OBJECT_ID('test') is not null
   drop table test
go

create table test
(
id int identity(1,1),
vid int ,
v varchar(600),
constraint pk_test_id primary key (id)
)
go



insert into test(vid,v)
select 1,REPLICATE('a',600) union all
select 2,REPLICATE('b',600) union all
select 3,REPLICATE('c',600) union all
select 4,REPLICATE('d',600) union all
select 5,REPLICATE('e',600) union all
select 6,REPLICATE('f',600) union all
select 7,REPLICATE('g',600) union all
select 8,REPLICATE('h',600) union all
select 9,REPLICATE('i',600) union all
select 10,REPLICATE('j',600)
go


--select POWER(2,18) * 10
--2621440条数据
begin tran
	insert into test(vid,v)
	select vid,v
	from test
commit
go 18


--建立非聚集索引
create index idx_test_vid on test(vid)


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

--输出详细的IO和时间(cpu、流逝的时间)上的开销信息
set statistics io on
set statistics time on


/* 采用聚集索引

SQL Server 分析和编译时间: 
   CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。

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

 SQL Server 执行时间:
   CPU 时间 = 921 毫秒,占用时间 = 277 毫秒。
*/
select COUNT(*)
from test with(index (pk_test_id))



/*采用非聚集索引

SQL Server 分析和编译时间: 
   CPU 时间 = 0 毫秒,占用时间 = 1 毫秒。

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

 SQL Server 执行时间:
   CPU 时间 = 327 毫秒,占用时间 = 137 毫秒。
*/
select count(*)
from test with(index (idx_test_vid))


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



那么如果表没有聚集索引,也没有非聚集索引,效率又会怎么样呢?
--删除主键,也就删除了聚集索引
alter table test
drop constraint pk_test_id


--删除非聚集索引
drop index idx_test_vid on test


/* 表扫描

SQL Server 分析和编译时间: 
   CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。

 SQL Server 执行时间:
   CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。
SQL Server 分析和编译时间: 
   CPU 时间 = 0 毫秒,占用时间 = 1 毫秒。

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

(1 行受影响)

 SQL Server 执行时间:
   CPU 时间 = 765 毫秒,占用时间 = 233 毫秒。
SQL Server 分析和编译时间: 
   CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。

 SQL Server 执行时间:
   CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。
*/
select count(*)
from test




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

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

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

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