我应该摆脱 Guid 列上的聚集索引吗

Posted

技术标签:

【中文标题】我应该摆脱 Guid 列上的聚集索引吗【英文标题】:Should I get rid of clustered indexes on Guid columns 【发布时间】:2010-09-21 14:44:55 【问题描述】:

我正在开发一个通常使用 GUID 作为主键的数据库。

默认情况下,SQL Server 在主键列上放置一个聚集索引。我知道这对于 GUID 列来说是一个愚蠢的想法,并且非聚集索引更好。

你怎么看 - 我应该摆脱所有聚集索引并用非聚集索引替换它们吗?

为什么 SQL 的性能调整器不提供此建议?

【问题讨论】:

看看以下 Paul Randal 的帖子。 Clustered or nonclustered index on a random GUID? 【参考方案1】:

您几乎肯定希望在数据库中的每个表上建立一个聚集索引。 如果一个表没有聚集索引,它就是所谓的“堆”,大多数常见查询类型的性能是less for a heap than for a clustered index table。

应该在哪些字段上建立聚集索引取决于表本身,以及针对表的查询的预期使用模式。在几乎所有情况下,您可能希望聚集索引位于唯一的列或列组合上,即(备用键),因为如果不是,SQL 将在任何内容的末尾添加一个唯一值无论如何选择的字段。如果您的表中有一个或多个列将被查询频繁使用以选择或过滤多条记录,(例如,如果您的表包含销售交易,并且您的应用程序将经常按产品 ID 请求销售交易,甚至更好,发票详细信息表,几乎在每种情况下,您都将检索特定发票的所有详细记录,或者您经常检索特定客户的所有发票的发票表......无论您是否会被选中,这都是正确的单个值或一系列值的记录数)

这些列是聚集索引的候选列。聚集索引中列的顺序很关键。索引中定义的第一列应该是在预期查询中将首先选择或过滤的列。

这一切的原因是基于对数据库索引的内部结构的理解。这些索引称为平衡树 (B-Tree) 索引。它们有点像二叉树,除了树中的每个节点可以有任意数量的条目(和子节点),而不仅仅是两个。聚集索引的不同之处在于,聚集索引中的叶节点是表本身的实际物理磁盘数据页。而非聚集索引的叶节点只是“指向”表的数据页。

因此,当表具有聚集索引时,表数据页是该索引的叶级,并且每个数据页都有一个指向索引顺序中的上一页和下一页的指针(它们形成双向链接-列表)。

因此,如果您的查询请求的行范围与聚集索引的顺序相同...处理器只需遍历索引一次(或可能两次)即可找到数据的起始页,并且然后按照链表指针依次到达下一页和下一页,直到读完它需要的所有数据页。

对于非聚集索引,它必须为检索到的每一行遍历索引一次......

注意:编辑 要解决 Guid Key 列的顺序问题,请注意 SQL2k5 具有 NEWSEQUENTIALID() 实际上确实以“旧”顺序方式生成 Guid。

或者您可以研究在客户端代码中实现的 Jimmy Nielsens COMB guid 算法:

COMB Guids

【讨论】:

但是 GUID 呢?除非它们是顺序 GUID,否则您永远不会以与聚集索引相同的顺序检索一系列行。因此我的问题 你是对的,一般来说,当必须获取非索引列时,对于单行访问,非聚集索引会比聚集索引稍快一些。对于“覆盖”索引,otoh,这不重要。 (续) 但是聚集索引可以帮助查询“组”数据,即使您使用的是非顺序 Guid。例如,如果 guid 是父表中的 PK,而复合聚集索引的第一 (FK) 列是子表中的 PK,则所有聚集索引的好处都适用。 另外,您“可以”创建连续的 Guid... 见 yafla.com/dennisforbes/Sequential-GUIDs-in-SQL-Server/…【参考方案2】:

正如大多数人所提到的,避免在聚集索引中使用随机标识符——你不会获得聚集的好处。实际上,您会遇到延迟增加的情况。摆脱所有这些是可靠的建议。还要记住 newsequentialid() 在多主复制场景中可能会出现极大的问题。如果数据库 A 和 B 在复制之前都调用了 newsequentialid(),就会发生冲突。

【讨论】:

【参考方案3】:

聚集索引的一个重要原因是当您经常想要检索给定列的一系列值的行时。由于数据是按物理顺序排列的,因此可以非常有效地提取行。

像 GUID 这样的东西,虽然对于主键非常有用,但可能会对性能产生积极的不利影响,因为插入会产生额外的成本,而选择不会带来明显的好处。

所以是的,不要在 GUID 上聚集索引。

至于为什么不作为推荐提供,我建议调谐器知道这一事实。

【讨论】:

使用 SQL 2005 和 newsequentialid(),碎片问题在很大程度上得到了解决。最好通过查看 sys.dm_db_index_physical_stats 和 sys_indexes 来衡量。 不过,您的查询仍然没有任何好处。如果需要,您应该只在 UNIQUEIDENTIFIER 上集群,例如用于复制。【参考方案4】:

是的,在随机值上使用聚集索引是没有意义的。

您可能确实希望在数据库中的某个位置使用聚簇索引。例如,如果您有一个“Author”表和一个带有“Author”外键的“Book”表,并且如果您的应用程序中有一个查询显示“select ... from Book where AuthorId = .. ",那么您将阅读一组书籍。如果这些书在磁盘上物理上彼此相邻,则速度会更快,这样磁盘头就不必从一个扇区跳到另一个扇区来收集该作者的所有书籍。

因此,您需要考虑您的应用程序,它查询数据库的方式。

进行更改。

然后测试,因为你永远不知道......

【讨论】:

【参考方案5】:

虽然在 GUID 上集群通常不是一个好主意,但请注意,GUID 在某些情况下可以 cause fragmentation even in non-clustered indexes。

请注意,如果您使用的是 SQL Server 2005,newsequentialid() 函数会生成 顺序 GUID。这有助于防止碎片问题。

我建议在做出任何决定之前使用如下 SQL 查询来测量碎片(请原谅非 ANSI 语法):

SELECT OBJECT_NAME (ips.[object_id]) AS 'Object Name',
       si.name AS 'Index Name',
       ROUND (ips.avg_fragmentation_in_percent, 2) AS 'Fragmentation',
       ips.page_count AS 'Pages',
       ROUND (ips.avg_page_space_used_in_percent, 2) AS 'Page Density'
FROM sys.dm_db_index_physical_stats 
     (DB_ID ('MyDatabase'), NULL, NULL, NULL, 'DETAILED') ips
CROSS APPLY sys.indexes si
WHERE si.object_id = ips.object_id
AND   si.index_id = ips.index_id
AND   ips.index_level = 0;

【讨论】:

【参考方案6】:

如果您使用 NewId(),您可以切换到 NewSequentialId()。这应该有助于插入性能。

【讨论】:

【参考方案7】:

这取决于您是否要进行大量插入,或者您是否需要通过 PK 快速查找。

【讨论】:

集群不会影响查找速度 - 一个唯一的非集群索引应该可以完成这项工作。【参考方案8】:

是的,由于 Galwegian 上述原因,您应该删除 GUID 主键上的聚集索引。我们已经在我们的应用程序上做到了这一点。

【讨论】:

【参考方案9】:

GUID 字段中的聚集索引的问题在于 GUID 是随机的,因此当插入新记录时,必须移动磁盘上的大部分数据才能将记录插入表的中间。

但是,对于基于整数的聚集索引,整数通常是连续的(就像 IDENTITY 规范一样),所以它们只是被添加到末尾,不需要移动任何数据。

另一方面,聚集索引在 GUID 上并不总是不好的……这完全取决于您的应用程序的需要。如果你需要能够快速SELECT 记录,那么使用聚集索引...INSERT 的速度会受到影响,但SELECT 的速度会有所提高。

【讨论】:

以上是关于我应该摆脱 Guid 列上的聚集索引吗的主要内容,如果未能解决你的问题,请参考以下文章

索引视图的两列上的唯一聚集索引

具有不同排序方向的多列上的Sql server聚集索引

非聚集索引 - 几乎相同需求的一个或两个索引(两个表之间的连接)?

堆上的非聚集索引与聚集索引的性能 [关闭]

GUID做主键真的合适吗

sqlserver 在数据查询时是按时间顺序排列的 在时间字段上还有必要加聚集索引吗 为啥