我正在研究一个通常使用 GUID 作为主键的数据库。
默认情况下,SQL Server 在主键列上放置一个聚集索引。我知道这对于 GUID 列是一个愚蠢的想法,并且非聚集索引更好。
你怎么看 - 我应该摆脱所有聚集索引并用非聚集索引替换它们吗?
为什么 SQL 的性能调整器不提供此建议?
我正在研究一个通常使用 GUID 作为主键的数据库。
默认情况下,SQL Server 在主键列上放置一个聚集索引。我知道这对于 GUID 列是一个愚蠢的想法,并且非聚集索引更好。
你怎么看 - 我应该摆脱所有聚集索引并用非聚集索引替换它们吗?
为什么 SQL 的性能调整器不提供此建议?
聚集索引的一个重要原因是当您经常想要检索给定列的一系列值的行时。因为数据是按物理顺序排列的,所以可以非常有效地提取行。
像 GUID 这样的东西,虽然对于主键非常有用,但可能会对性能产生积极的不利影响,因为插入会产生额外的成本,而对选择没有明显的好处。
所以是的,不要在 GUID 上聚集索引。
至于为什么不将其作为推荐提供,我建议调谐器知道这一事实。
您几乎肯定希望在数据库中的每个表上建立一个聚集索引。如果表没有聚集索引,则它被称为“堆”,并且大多数类型的常见查询的性能对于堆来说不如聚集索引表。
应该在哪些字段上建立聚集索引取决于表本身,以及针对表的查询的预期使用模式。在几乎所有情况下,您可能希望聚集索引位于唯一的列或列组合上,即(备用键),因为如果不是,SQL 将在任何内容的末尾添加一个唯一值无论如何选择的字段。如果您的表中有一个或多个列将被查询频繁使用以选择或过滤多条记录,(例如,如果您的表包含销售交易,并且您的应用程序将经常按产品 ID 请求销售交易,甚至更好,发票详细信息表,几乎在每种情况下,您都将检索特定发票的所有详细记录,
这些列是聚集索引的候选。聚集索引中列的顺序很关键。索引中定义的第一列应该是在预期查询中将首先选择或过滤的列。
这一切的原因是基于对数据库索引的内部结构的理解。这些索引称为平衡树 (B-Tree) 索引。它们有点像二叉树,除了树中的每个节点可以有任意数量的条目(和子节点),而不仅仅是两个。聚集索引的不同之处在于,聚集索引中的叶节点是表本身的实际物理磁盘数据页。而非聚集索引的叶节点只是“指向”表的数据页。
因此,当表具有聚集索引时,表数据页是该索引的叶级,并且每个表都有一个指向索引顺序中的上一页和下一页的指针(它们形成一个双向链表) .
因此,如果您的查询请求与聚集索引顺序相同的一系列行......处理器只需遍历索引一次(或可能两次),即可找到数据的起始页,然后按照链表指针指向下一页和下一页,直到它读取了它需要的所有数据页。
对于非聚集索引,它必须为检索到的每一行遍历索引一次......
注意:编辑
要解决 Guid Key 列的顺序问题,请注意 SQL2k5 具有 NEWSEQUENTIALID() 实际上确实以“旧”顺序方式生成 Guid。
或者您可以研究在客户端代码中实现的 Jimmy Nielsens COMB guid 算法:
GUID 字段中的聚集索引的问题在于 GUID 是随机的,因此当插入新记录时,必须移动磁盘上的大部分数据以将记录插入到表的中间。
然而,对于基于整数的聚集索引,整数通常是顺序的(就像使用IDENTITY
规范一样),所以它们只是被添加到末尾,不需要移动任何数据。
另一方面,聚集索引在 GUID 上并不总是不好的……这完全取决于您的应用程序的需要。如果您需要能够SELECT
快速记录,那么使用聚集索引......INSERT
速度会受到影响,但SELECT
速度会有所提高。
虽然在 GUID 上进行集群通常不是一个好主意,但请注意,即使在非集群索引中,GUID 在某些情况下也会导致碎片。
请注意,如果您使用的是 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;
如果您使用 NewId(),则可以切换到 NewSequentialId()。这应该有助于插入性能。
是的,在随机值上使用聚集索引是没有意义的。
您可能确实希望在数据库中的某个地方使用聚簇索引。例如,如果您有一个“Author”表和一个带有“Author”外键的“Book”表,并且如果您的应用程序中有一个查询显示“select ... from Book where AuthorId = .. ",那么您将阅读一组书籍。如果这些书在磁盘上物理上彼此相邻,则速度会更快,这样磁盘头就不必从一个扇区跳到另一个扇区来收集该作者的所有书籍。
因此,您需要考虑您的应用程序,它查询数据库的方式。
进行更改。
然后测试,因为你永远不知道...
正如大多数人所提到的,避免在聚集索引中使用随机标识符——你不会获得聚集的好处。实际上,您会遇到延迟增加的情况。摆脱所有这些是可靠的建议。还要记住 newsequentialid() 在多主复制场景中可能会出现极大的问题。如果数据库 A 和 B 在复制之前都调用了 newsequentialid(),就会发生冲突。
是的,由于 Galwegian 上述原因,您应该删除 GUID 主键上的聚集索引。我们已经在我们的应用程序上做到了这一点。
这取决于您是否要进行大量插入,或者您是否需要通过 PK 快速查找。