每行的字节数包含所有列的字节数相加外加行开销,有很多因素决定行开销的大小,这些因素可以总结如下:

  ● 每行6字节用于存储状态信息和长度信息

  ● 每列占一位,向上取整到字节

  ● 如果包含变长数据,第一个变长列4个字节,之后每个变长列额外占两个字节

  ● 除此之外,页尾还包含每个行2个字节的偏移指针

  因为SalesOrderDetail含有变长列,所以每行所占的大小并不能提前预测,但是还是可以算出平均每行所占的字节为95字节。因为每页的大小是8k,因此每页大约可以容纳75行,这要比我们上面的例子多出很多,在接下来的文章中我们将会详细讨论通过SQL Server management studio来查看每页所含的行数。

  所以,虽然我们通常说SQL Server是”读取行”,但如果深究起来这种说法是错误的,其所读取的小单位是页。我之前的也说过SQL Server通过给定的索引键值可以快速的找到行,但其实深究的话正确的说法是SQL Server通过给定的索引键值找到页。在SQL Server将找到的页存入内存之后,在从内存中找到指定的行。

  区

  SQL Server基于页之上做了另外一种逻辑分组,它将8个物理上连续的页分为一个区。正常情况下,和页一样,区也是所有权的小单位,如果区中的一页属于表A或者表B,那么区中其它页也属于表A或者表B,但对于特别小的表或者索引不适用了,对于很小的表和索引,一个区中的页可以属于两个以上的表。但对于大多数区来说,区都是所有权的基本单位。

  因此,对于表扫描来说,SQL Server并不是扫描所有的行,而是属于表的所有的页和区。SQL Server对于IO做的请求是8K或者64K字节的请求,甚至可能是并行读取表。这使得表扫描并不像想象的那么吓人,因为扫描是页为单位而不是每行都要做一个IO请求。

  以页和区作为单位不仅仅意味着减轻了表扫描的成本,还意味着,要从非聚集索引获益,查询请求的过滤条件要更具有选择性。加下下面的对于SalesOrderDetail表的请求,获取这个表4%左右的数据。


查询
 SELECT *
FROM Sales.SalesOrderDetail
WHERE ProductID = 712
 
聚集索引
 SalesOrderID / SalesOrderDetailID
 
每页平均行数
 75
 
非聚集索引
 ProductID
 
请求行所占比例
 4%
 


  因为平均每25行中只选择一行,并且where后的条件是根据ProductID来的,而且还存在以ProductID作为KEY的非聚集索引,索引使用非聚集索引来定位每行的信息看上去是个不错的主意,是这样吗?请在想一想。

  由于表中以SalesOrderID/SalesOrderDetailID作为聚集索引,因为平均每页存在75行数据,而查询从每25行中取一行,也是平均每页只能取三行。换句话说,几乎表中的每一页都要读到内存中才能满足这个查询,因此还不如直接进行聚集索引扫描来的快,因为扫描以区为单位,因此每次IO请求可以将24行数据载入内存(3行每页*8页每区)。

  SQL Server新手通常会问”非聚集索引在什么样的选择率下才会被使用”,在本篇文章中你可以知道,比平均每页只能获取查询请求的一行要多行。本系列文章接下来的章节中将会包含更多细节。

  总结

  SQL Server读取的单位是页而不是行。页是IO小的单位,也是8K,8个连续的页被称为区。通常情况下,页和区都只能属于一个对象,因为IO读取的特性,一个查询必须要有很高的选择率才能够从非聚集索引获益。

  在第五篇文章中,我们来看如何提高使用非聚集索引来减少查询成本的概率。一种解决方法是非聚集索引完全覆盖所请求的查询。换句话说,下一讲我们讲解使用Include列。