PK索引中的列顺序重要吗?


33

我有一些具有相同基本结构的非常大的表。每个都有一个RowNumber (bigint)DataDate (date)列。每天晚上使用SQLBulkImport加载数据,并且从未加载过任何“新”数据-它是历史记录(SQL Standard,不是Enterprise,因此没有分区)。

因为每个数据位都需要绑定到其他系统,并且每个RowNumber/DataDate组合都是唯一的,所以这就是我的主键。

我注意到,由于我在SSMS Table Designer中定义PK的方式,RowNumber第一和DataDate第二列出了。

我还注意到,我的碎片始终非常高,高达〜99%。

现在,由于每个DataDate索引仅出现一次,所以我希望索引器每天都添加到页面中,但是我想知道它是否实际上是RowNumber首先基于索引编制的,因此是否需要转移其他所有内容?


Rownumber不是一个标识列,它是一个由外部系统生成的int(很糟糕)。它在每个开始时重置DataDate

示例数据

RowNumber | DataDate | a | b | c..... 
   1      |2013-08-01| x | y | z 
   2      |2013-08-01| x | y | z 
...
   1      |2013-08-02| x | y | z 
   2      |2013-08-02| x | y | z 
...

数据按RowNumber顺序加载,DataDate每次加载一次。

导入过程为bcp-我尝试加载到临时表中,然后从中按顺序选择(ORDER BY RowNumber, DataDate),但仍然会出现高碎片。

Answers:


50

PK索引中的列顺序重要吗?

是的,它确实。

默认情况下,主键约束在SQL Server中由唯一的聚集索引强制执行。聚集索引定义表中行的逻辑顺序。可能添加了许多额外的索引页来表示b树索引的较高级别,但是聚簇索引的最低(叶)级别只是数据本身的逻辑顺序。

为了清楚起见,页面上的行不一定按照聚簇索引键顺序物理存储。页面内有一个单独的间接结构,用于存储指向每一行的指针。此结构按聚簇索引键排序。同样,每个页面都有一个指针,指向聚集索引键顺序中同一级别的上一页和下一页。

使用集群化的主键时(RowNumber, DataDate),将首先按逻辑顺序对行进行排序RowNumber,然后再按DataDate- RowNumber = 1对逻辑上的所有行进行分组,然后对逻辑上的所有行进行分组,RowNumber = 2依此类推。

当您添加新数据(RowNumbers从1到n)时,新行在逻辑上属于现有页面,因此SQL Server可能需要做大量工作来拆分页面以腾出空间。所有这些活动都会产生很多额外的工作(包括记录更改),但毫无益处。

拆分页面也从大约50%的空白开始,因此过多的拆分也会导致较低的页面密度(行数少于每页的最佳行数)。这不仅是从磁盘读取的坏消息(较低的密度=需要读取的页面更多),而且较低密度的页面在缓存时还会占用更多的内存空间。

将聚簇索引更改为(DataDate, RowNumber表示将新数据(可能DataDates比当前存储的数据高)追加到新页面上的聚簇索引的逻辑末尾。这将消除不必要的页面拆分开销,并缩短加载时间。较少碎片的数据还意味着预读活动(在正在进行的查询需要它们之前,从磁盘读取页面)可以更有效。

如果不出意外,你的查询更容易搜索上DataDateRowNumber。上的聚集索引(DataDate, RowNumber支持上的索引搜索DataDate(然后RowNumber)。现有的安排仅支持上的搜索RowNumber(并且只有在上才支持DataDate)。DataDate更改主键后,您很可能可以将现有的非聚集索引放置在上面。聚集索引将比其替换的非聚集索引宽,因此您应该进行测试以确保性能仍然可以接受。

当使用导入新数据时bcp,如果导入文件中的数据通过聚簇索引键(理想情况下(DataDate, RowNumber)排序,并且指定了以下bcp选项,则可能会获得更高的性能:

-h "ORDER(DataDate,RowNumber), TABLOCK"

为了获得最佳的数据加载性能,您可以尝试实现最少记录的插入。有关更多信息,请参见:


4
一个很好的答案-我现在知道我应该做什么以及为什么。我曾经这么想,但不是这样!谢谢。
BlueChippy

在将DB放入我的本地SQL Server进行测试时花了很长时间:更改索引负载之前花了45分钟……之后,只花了5分钟!!!
BlueChippy

13

是的,顺序很关键。我非常怀疑您是否通过RowNumber查询(例如WHERE RowNumber=1)。绝大多数时间序列是按日期(WHERE DataDate BEWEEN @start AND @end)查询的,而此类查询将需要按进行聚类组织DataDate

碎片通常是一条红鲱鱼。减少碎片化不是您这里的目标,但应该为查询提供适当的组织。另外,减少碎片是一个很好的想法,但这并不是一个目标。如果您有一个组织合理的数据模型来匹配您的工作负载(查询已得到适当覆盖),并且您的度量表明零散会影响性能,那么我们可以讨论一下。


我在DataDate上也有一个非聚集索引,正如您所说的,它通常WHERE是查询中的子句。
BlueChippy 2013年

1
如果列的ORDER至关重要,那么incorrecrt订单的影响会导致我的I / O增加吗?我的想法是,它是按RowNumber排序的,因此每次都必须在索引上做很多工作,而它应该基于DataDate?
BlueChippy
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.