第5行:【Clustered Index Seek(OBJECT:([MyNorthwind].[dbo].[Customers].[PK_Customers]), SEEK:([MyNorthwind].[dbo].[Customers].[CustomerID]=[MyNorthwind].[dbo].[Orders].[CustomerID]) ORDERED FORWARD)】,意思是说,SqlServer在对表Customers做Seek操作,而且是按照【Clustered Index Seek】的方式,对应的索引是【PK_Customers】,seek的值来源于[Orders].[CustomerID]
第4行:【Clustered Index Scan(OBJECT:([MyNorthwind].[dbo].[Orders].[PK_Orders]), WHERE:([MyNorthwind].[dbo].[Orders].[OrderDate]>='2010-12-01 00:00:00.000' AND [MyNorthwind].[dbo].[Orders].[OrderDate]<'2011-12-01 00:00:00.000'))】,意思是说,SqlServer在对表Customers做Scan操作,即:最差的【表扫描】的方式,原因是,OrderDate列上没有索引,所以只能这样了。
第3行:【Nested Loops(Left Outer Join, OUTER REFERENCES:([MyNorthwind].[dbo].[Orders].[CustomerID]))】,意思是说,SqlServer把第5行和第4行产生的数据用【Nested Loops】的方式联接起来,其中Outer表是Orders,要联接的匹配操作也在第5行中指出了。
第2行:【Compute Scalar(DEFINE:([Expr1006]=isnull([MyNorthwind].[dbo].[Customers].[CustomerName],N'')))】,意思是说,要执行一个isnull()函数的调用。具体原因请参考本文前部分中给出视图定义代码。
第1行:【SELECT [v].[OrderID],[v].[CustomerID],[v].[CustomerName],[v].[OrderDate],[v].[SumMoney],[v].[Finished] FROM [OrdersView] [v] WHERE [v].[OrderDate]>=@1 AND [v].[OrderDate]<@2】,通常第1行就是整个查询,表示它的返回值。
索引统计信息:查询计划的选择依据
前面一直说到【执行计划】,既然是计划,就表示要在具体执行前就能确定下来的操作方案。那么SqlServer是如何选择一种执行计划的呢? SqlServer怎么知道什么时候该用索引或者用哪个索引?对于SqlServer来说,每当要执行一个查询时,都要首先检查有没有这个查询的执行计划是否存在缓存中,如果没有,则要生成一个执行计划,具体在产生执行计划时,并不是看有哪些索引可用(随机选择),而是会参考一种被称为【索引统计信息】的数据。 如果您仔细地看一下前面的执行计划或者执行过程表格,会发现SqlServer能预估每个步骤所产生的数据量,正是因为SqlServer能预估这些数据量,SqlServer才能选择一个它认为最合适的方法去执行查询过程,此时【索引统计信息】就能告诉SqlServer这些数据。说到这里,您是不是有点好奇呢,为了让您对【索引统计信息】有个感性的认识,我们来看看【索引统计信息】是个什么样子的。请在【SQL Server Management Studio】,输入以下语句,然后执行。
dbcc show_statistics (Products, IX_CategoryID)
得到的结果如下图:

首先,还是解释一下命令:【dbcc show_statistics】这个命令可以显示我们想知道的【索引统计信息】,它需要二个参数,1. 表名,2. 索引名
再来看看命令的结果,它有三个表格组成:
1. 第一个表格,它列出了这个索引统计信息的主要信息。
| 列名 | 说明 |
|---|---|
| Name | 统计信息的名称。 |
| Updated | 上一次更新统计信息的日期和时间。 |
| Rows | 表中的行数。 |
| Rows Sampled | 统计信息的抽样行数。 |
| Steps | 数据可分成多少个组,与第三个表对应。 |
| Density | 第一个索引列前缀的选择性(不包括 EQ_ROWS)。 |
| Average key length | 所有索引列的平均长度。 |
| String Index | 如果为“是”,则统计信息中包含字符串摘要索引,以支持为 LIKE 条件估算结果集大小。仅适用于 char、varchar、nchar 和nvarchar、varchar(max)、nvarchar(max)、text 以及 ntext 数据类型的前导列。 |
2. 第二个表格,它列出各种字段组合的选择性,数据越小表示重复越性越小,当然选择性也就越高。
| 列名 | 说明 |
|---|---|
| All density | 索引列前缀集的选择性(包括 EQ_ROWS)。注意:这个值越小就表示选择性越高。 如果这个值小于0.1,这个索引的选择性就比较高,反之,则表示选择性就不高了。 |
| Average length | 索引列前缀集的平均长度。 |
| Columns | 为其显示 All density 和 Average length 的索引列前缀的名称。 |
3. 第三个表格,数据分布的直方图,SqlServer就是靠它预估一些执行步骤的数据量。
| 列名 | 说明 |
|---|---|
| RANGE_HI_KEY | 每个组中的最大值。 |
| RANGE_ROWS | 每组数据组的估算行数,不包含最大值。 |
| EQ_ROWS | 每组数据组中与最大值相等的行的估算数目。 |
| DISTINCT_RANGE_ROWS | 每组数据组中的非重复值的估算数目,不包含最大值。 |
| AVG_RANGE_ROWS | 每组数据组中的重复值的平均数目,不包含最大值,计算公式:RANGE_ROWS / DISTINCT_RANGE_ROWS for DISTINCT_RANGE_ROWS > 0 |
为了能让您更好的理解这些数据,尤其是第三组,请看下图:

当时我在填充测试数据时,故意把CategoryId为1到8的组,每组取了78条数据。所以【索引统计信息】的第三个表格的数据也都是正确的,也正是根据这些统计信息,SqlServer才能对每个执行步骤预估相应的数据量,从而影响Join之类的选择。当然了,在选择Join方式时,也要参考第二个表格中的字段选择性。最终在为新的查询生成执行计划时,查询优化器使用这些统计信息并通过估计使用索引评估查询的开销来确定最佳查询计划。
再来个例子来说明一下统计信息对于查询计划选择的重要性。首先多加点数据,请看以下代码:
declare @newCategoryId int; insert into dbo.Categories (CategoryName) values(N'Test statistics');set @newCategoryId = scope_identity(); declare @count int; set @count = 0; while( @count < 100000 )begin insert into Products (ProductName, CategoryID, Unit, UnitPrice, Quantity, Remark) values(cast(newid() as nvarchar(50)), @newCategoryId, N'个', 100, @count +1, N''); set @count = @count +1; end go update statistics Products; go