HEAP表的有效使用方案是什么?


31

我目前正在将一些数据导入到旧版系统中,并且发现该系统未使用单个群集索引。快速的Google搜索向我介绍了HEAP表的概念,现在我很好奇在哪种使用情况下,HEAP表比集群表更可取?

据我了解,HEAP表仅对审计表和/或插入比选择频繁发生的地方有用。由于没有要维护的聚簇索引,而且由于非常罕见的读取,因此不会产生额外的碎片,因此,这将节省磁盘空间和磁盘I / O。


1
您在谈论SQL Server吗?
a_horse_with_no_name 2012年

@a_horse_with_no_name是的,我忘了提到这个问题
marc.d 2012年

堆表非常适合具有数百万行的表,这些表会受到用户的严重打击。缺点是它们会占用大量空间,因为数据在物理上未排序存储。此外,您还依赖于索引来调整查询。由于性能问题,我在根本不使用聚集索引的地方工作。可能是由于簇索引选择不当所致,但是如果您仅使用堆表,则不必担心。更好的解决方案是使用sql server企业版并水平分区大表。但是,如果您没有耳鼻喉


Answers:


22

唯一有效的用途是用于

  • 导入/导出/ ETL流程中使用的登台表。
  • 使用以下方式临时,临时和短期备份表 SELECT * INTO..

临时表通常相当平坦,在使用之前/之后会被截断。

请注意,与数据大小相比,聚簇索引通常小一些:数据索引结构的最低级别。

堆表也有问题。至少这些:

  • 不能进行碎片整理以减少磁盘空间。这很重要,因为使用过的数据页将分散在整个MDF中,例如,因为数据没有来自聚集索引的“顺序”
  • 现在,非聚集索引指向该行,而不是聚集索引条目。这会影响性能:需要通过具有非聚集索引的聚集索引来访问数据

另见


2
它通常将堆用于两个独立的事物。ETL暂存和工作表,当设置为临时表有效时,它用于临时存储数据。所有这些在下一次加载时都会被截断。
Zane 2012年

好的问题。
Zane 2012年

1
略微调整-如果在进行更改之前执行SELECT INTO以便创建小表的快速备份,则默认情况下会创建堆。我会说这是有效的用法-但这只是挑剔。我想知道我的工作完成后就想摆脱那个堆。
布伦特·奥扎

@BrentOzar:同意,我一直在做。我的回答是“长期和持久的表格”,但我将更新
gbn 2015年

9

主要注意事项

我看到了堆的一个重要优势,而集群表的一个优势,再加上第三个考虑因素,无论哪种方式都可以。

  • 堆为您节省了间接层。索引包含行ID,它们直接指向磁盘位置(不是,实际上,但是尽可能直接)。因此,针对堆的索引查找应该花费大约是针对簇表的非聚簇索引查找的一半。

  • 归因于(几乎)自由索引,对聚集索引本身进行了排序。因为聚簇索引是按照数据的物理顺序反映的,所以它在实际数据本身的顶部占用相对较小的空间,当然无论如何您都必须存储这些空间。因为它是物理排序的,所以针对此索引的范围扫描可以查找到起点,然后非常有效地将其压缩到终点。

  • 堆上的索引引用了64位的RID。如前所述,聚簇表上的非聚簇索引引用聚簇键,聚簇键可以更小(32位INT),相同(64位BIGINT)或更大(48位DATETIME2()加32位INT,或128位GUID)。显然,更广泛的参考意味着更大,更昂贵的索引。

空间要求

使用这两个表:

CREATE TABLE TmpClustered
(
ID1 INT NOT NULL,
ID2 INT NOT NULL
)
ALTER TABLE TmpClustered ADD CONSTRAINT PK_Tmp1 PRIMARY KEY CLUSTERED (ID1)
CREATE UNIQUE INDEX UQ_Tmp1 ON TmpClustered (ID2)

CREATE TABLE TmpNonClustered
(
ID1 INT NOT NULL,
ID2 INT NOT NULL
)
ALTER TABLE TmpNonClustered ADD CONSTRAINT PK_Tmp2 PRIMARY KEY NONCLUSTERED (ID1)
CREATE UNIQUE INDEX UQ_Tmp2 ON TmpNonClustered (ID2)

...每个记录都有870万条记录,两个记录所需的空间均为150 MB;群集表的索引为120 MB,非群集表的索引为310 MB。这反映出聚簇索引比RID窄,并且聚簇索引主要是“免费赠品”。如果没有启用唯一索引ID2,则非集群表所需的索引空间将降至155 MB(如您所料,为一半),而集群PK 的索引空间仅为150 KB-几乎没有。

因此,具有32位索引(名义上总共64位)的群集表中的32位字段的非聚簇索引占用了120 MB,而具有64位的堆中的32位字段的索引却占用了120 MB RID(总共96位,名义上)占用了155 MB,比天真地期望从64位密钥更改为96位密钥所增加的50%少一点,但是当然,开销会减小有效大小的差异。

填充两个表并创建它们的索引对于每个表花费的时间相同。在运行涉及扫描或搜寻的简单测试时,我发现表之间没有实质性的性能差异,这与gbn有用链接的Microsoft白皮书相符。所述论文的确显示出高度并发访问的显着差异。我不确定为什么会发生这种情况,希望比我拥有使用大量OLTP系统的经验更多的人可以告诉我们。

添加〜40字节的随机可变长度数据不会明显改变这种等效性。INT也不用宽UUID 替换s(每个表的速度减慢到大约相同的程度)。您的里程可能会有所不同,但是在大多数情况下,是否有索引比哪种索引更为重要。

点点滴滴

对非聚簇索引进行范围扫描(因为表是堆索引或索引不是聚簇索引),涉及扫描索引,然后针对每个命中对表进行查找。这可能非常昂贵,因此仅扫描表有时会更便宜。但是,您可以使用覆盖索引来解决此问题。无论是否对表进行群集,这都适用。

正如@gbn指出的那样,没有简单的方法来压缩堆。但是,如果您的表随着时间的推移逐渐增加(这是一种很常见的情况),则几乎没有浪费,因为删除所释放的空间将被新数据填充。

我见过的关于堆与集群表的几个讨论提出了一个奇怪的草人论点,即没有索引的堆不如集群表,因为它总是需要进行表扫描。确实是这样,但是更有意义的比较是“大型索引良好的群集表”与“大型索引良好的堆”。如果您的表很小,或者您总是要进行表扫描,那么是否对它进行群集都没关系。

由于聚簇表中的每个索引都引用聚簇索引,因此它们实际上是所有覆盖索引。引用索引列和集群列的查询可以执行索引扫描,而无需任何表查找。如果您的聚簇索引是一个合成键,那么通常这没有什么价值,但是如果您仍然需要检索它是一个业务键,那么这是一个很好的功能。

TL; DR

我是数据仓库专家,而不是OLTP专家。对于事实表,我几乎总是在最可能需要范围扫描的字段(通常是日期字段)上使用聚簇索引。对于维度表,我将其聚集在PK上,因此已针对事实表进行了合并联接的预排序。

使用聚簇索引的原因有很多,但是如果这些原因都不适用,那么开销可能就不值得了。我怀疑人们普遍使用聚簇索引的背后有很多“我们一直都这样做”和“这只是最佳实践”。与尝试既您的数据和您的负载,看看有什么效果最好。


5

我认为至少可以说,“唯一有效的用途是用于导入/导出/ ETL流程中的登台表”。您必须采用给定系统的预期用例,然后根据堆或索引组织表的优点进行选择(我知道,这是一个Oracle术语,但描述得很好)。

我们的仓库每天要装载约15亿行数据,并且必须支持高度并发的写入,处理和读取。关系存储支持OLAP数据库,因此读取通常主要是表扫描。生成的报告和下游提要通常也没有足够的选择性,因此任何索引都将是有用的。该系统支持数据的滑动窗口,因此,一旦加载了表,我们很少再写入该表,并且鉴于表分区的实现效果较差,需要使用Sch-M锁进行分区拆分,切换和合并,而使用Sch-S锁进行读取等。 ,尽管我们也有一些分区表,但系统必须使用许多表。使用许多表有助于简化数据分段和清除周期,同时还减少了争用。

这样,在某些任意列上的索引组织表(集群表)的额外开销与能够bcp进入堆,处理OLAP分区,执行一些表扫描查询,然后在3天后删除它意味着只是不值得。请注意,在我们的情况下,数据是从大型网格集群返回的,因此也没有对数据进行排序,因此,插入具有聚集索引的表中可能会引入其他问题,例如“热点”和页面拆分等。

另外,我认为关于页面分散的说法有些虚假。聚集索引的页面也可以分散在整个文件中。只是在重新索引(假设超过1000页)之后,这可能比堆更好,但随后您还必须重新索引。

如果需要考虑的话,也可以使用稀疏列和压缩来节省空间。的确,在某些情况下,在具有聚集索引的表上进行选择可能会更快,但您必须权衡它的装入和维护所需的资源。

[编辑]我可能应该弄清楚,只有我们未分区的事实表是堆。分区表和维表都具有聚簇索引,以支持有效的查找等。[Edit2]已将25亿修正为15亿。Tut,这两个数字彼此相邻。我猜在手机上键入回复会发生什么...

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.