现在问题又来了,为什么使用sysindexes速度会很快?索引是为检索而存在的,就是说索引并不是一个表必须的。表索引由多个页面组成,这些页面一起组成了一个树形结构,即我们通常说的B树(平衡树),首先来看下表索引的组成部分:根极节点,root,它指向另外两个页,把一个表的记录从逻辑上分成非叶级节点Non-Leaf Level(枝),它指向了更加小的叶级节点Leaf Level(叶)。根节点、非叶级节点和叶级节点都位于索引页中,统称为索引叶节点,属于索引页的范筹。这些“枝”、“叶”最终指向数据页Page。根级节点和叶级节点之间的叶又叫数据中间页。根节点对应了sysindexes表的root字段,记载了非叶级节点的物理位置(即指针);非叶级节点位于根节点和叶节点之间,记载了指向叶级节点的指针;而叶级节点则最终指向数据页,这就是最后的B树。sysindexes中我们需要关注root字段和indid字段。我们看下官方文档中对这两个字段的解释,如图二十三:

图二十三 官方文档对root字段和indid字段的解释
从上图中我们知道,索引ID为0表示堆,也就是在没有索引下所做的全表扫描;为1是表示聚集索引,大于1表示非聚集索引。root字段在全表扫描时是不会使用到的,而只有在有索引的情况下才使用。聚集索引中,数据所在的数据页是叶级,索引数据所在的索引页是非叶级。由于记录是按聚集索引键值进行排序,即聚集索引的索引键值也就是具体的数据页。访问有聚集索引的表,步骤是这样的:首先在sysindexes表查询INDID值为1,说明表中建立了聚集索;然后从根出发,在非叶级节点中定位最接近1的值,也就是枝节点,再查到其位于叶级页面的第n页;在叶级页面第n页下搜寻值为1的条目,而这一条目就是数据记录本身;将该记录返回客户端。同样,我们查询某张表有多少记录数,我们使用到的删选条件是indid in (0,1),也就是把普通表(这里指没有聚集索引的表)和有聚集索引的表都查找到。由于sysindexes记录了每张表的记录数,无论该表是普通表还是有聚集索引的表,都可以很快地把返回结果。如上所述,这个数值并不一定准确,至于你想获得真实记录数,还是初略记录数,这就看你获得记录数的需求是什么。
综上所述,count(*)在没有索引的情况下速度慢的原因是走的全表扫描,使用sysindexes速度快的原因是直接从该视图中得到记录数。
说点题外话,在插入数据时,最开始我采用了WHILE循环插入10亿条数据,等了两个多小时还没插入完,只好停掉,改用CTE插入数据。CTE插入数据的效率很高,数据文件大小以近2M/s的速度递增,但是由于数据量太大,也只好停掉,把10亿改成1000万。插入1000万数据用时4分52秒,数据文件占用磁盘空间470M,日志文件占用磁盘空间2.3G,但做统计记录数时看不到效果,所以改成插入1亿条数据。插入1亿条数据用时47分29秒,数据文件占用磁盘空间4.54G,日志文件占用磁盘空间33.28G。从插入数据的数据量级别我们知道,每多一个数量级,插入数据的时间会成倍地增长,具体多少倍有很多因素影响,比如系统空闲率、机器CPU和IO负载、插入的数据每行占用空间是否一致等等。这里还需要搞明白一个问题,那就是为什么CTE法那么快?首先我们了解下CTE。公用表表达式(Common Table Expression)是SQL SERVER 2005版本之后引入的一个特性。CTE可以看作是一个临时的结果集,可以在接下来的一个SELECT,INSERT,UPDATE,DELETE,MERGE语句中被多次引用。使用公用表达式可以让语句更加清晰简练。本文中的插入示例使用了CTE递归查询。CTE递归查询原理是这样的:第一步,将CTE表达示拆分为“定位点成员”和“递归成员”;第二步,运行定位点成员,执行创建第一个结果集R0;第三步,运行递归成员时,将前一个结果集作为输入(Ri),将Ri+1作为输出;第四步,重复第三步,直到返回空集;第五步,返回结果集,通过UNION ALL合并R0 到 Rn的结果。熟知编程的读者清楚,递归在编程中效率也是极高的。同样,CTE采用递归后插入数据会变得相当得高,从数据文件的增长速率就可以看出,使用CTE之前数据文件增长以几K每秒的速度增长,使用CTE之后,数据文件以近2M每秒的速度增长。搞清楚CTE为什么那么快后,这里还说下清空日志文件的小技巧。我们使用DROP TABLE count_Test后,数据文件和日志文件的空间并不会真正清空,这时如果我们执行DBCC SHRINKDATABASE(db_test_wgb)(注:db_test_wgb为数据库名)后,你会发觉数据文件和日志文件从数十G一下变成几M。这和Oracle中的SHRINK TABLE有几丝类似。这里还得着重强调下,不要在生产库中执行此语句,否则会让你后悔莫及!切记!
最后说明下,本文参考了姜敏前辈的这两篇文章,软件开发人员真的了解SQL索引吗(聚集索引)和软件开发人员真的了解SQL索引吗(索引原理),还参考了宋 剑前辈的文章:T-SQL查询进阶--详解公用表表达式(CTE)。如果想了解索引原理,强烈建议阅读姜敏前辈的这篇文章:软件开发人员真的了解SQL索引吗(索引原理)。对于什么是IAM,读者可以看下微软的官方文档,管理对象使用的空间。