HEAP 表的有效使用场景是什么?

mar*_*c.d 31 sql-server clustered-index heap

我目前正在将一些数据导入到遗留系统中,并发现该系统不使用单个聚集索引。一个快速的谷歌搜索向我介绍了 HEAP 表的概念,现在我很好奇在什么使用场景中 HEAP 表应该比集群表更受欢迎?

据我了解,HEAP 表仅对审计表和/或插入发生的频率远高于选择的情况有用。它将节省磁盘空间和磁盘 I/O,因为没有要维护的聚集索引,并且由于非常罕见的读取,额外的碎片不会成为问题。

gbn*_*gbn 22

唯一有效的用途是

  • 导入/导出/ETL 过程中使用的暂存表。
  • 表的临时、临时和短期备份使用 SELECT * INTO..

临时台通常非常平坦并且在使用前/使用后被截断。

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

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

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

另见


Jon*_*des 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)
Run Code Online (Sandbox Code Playgroud)

...每条记录都填充了 870 万条记录,两者所需的数据空间均为 150 MB;120 MB 用于聚簇表的索引,310 MB 用于非聚簇表的索引。这反映了聚集索引比 RID 窄,并且聚集索引大多是“免费赠品”。如果没有在 上的唯一索引ID2,非聚集表所需的索引空间降至 155 MB(如您所料的一半),但聚集 PK所需的索引空间仅为 150 KB - 几乎为零。

因此,具有 32 位索引(名义上总共 64 位)的聚簇表中 32 位字段的非聚簇索引需要 120 MB,而具有 64 位索引的堆中 32 位字段的索引RID(名义上总共 96 位)占用了 155 MB,比人们天真地期望从 64 位到 96 位密钥增加 50% 略少,但当然存在减少大小有效差异的开销。

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

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

点点滴滴

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

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

我见过的一些堆与聚簇表的讨论提出了一个奇怪的稻草人论点,即没有索引的堆不如聚簇表,因为它总是需要表扫描。这当然是正确的,但更有意义的比较是“大型索引良好的聚簇表”与“大型索引良好的堆”。如果您的表非常小,或者您总是要进行表扫描,那么您是否对它进行集群并不重要。

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

TL; 博士

我是一名数据仓库专家,而不是 OLTP 专家。对于事实表,我几乎总是在最可能需要范围扫描的字段上使用集群索引,通常是日期字段。对于维度表,我聚集在 PK 上,因此它预先排序以针对事实表进行合并连接。

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


Phi*_*son 5

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

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

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

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

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

[编辑] 我可能应该说清楚只有我们的非分区事实表是堆。分区表和维度表都有聚集索引以支持高效查找等。 [Edit2] 将 25 亿更正为 15 亿。啧,这两个数字是相邻的。我猜在手机上输入回复时会发生什么......