【问题标题】:Composite primary key / clustered index, fragmentation, performance复合主键/聚集索引、碎片、性能
【发布时间】:2014-01-22 03:25:32
【问题描述】:

经过 20 年的专业发展,我仍然发现自己对数据库性能的某些方面一无所知。这是其中之一。关于表和索引碎片及其对性能的影响,这里和其他地方有成千上万的问题。我知道基本的注意事项,但有时似乎没有“好的”答案。这是我的问题,我经常遇到它:

表仅用于存储定义一对多关系的 id 对,让我们以朋友为例。 Friends 表仅包含 personId (int)、friendId (int)。每对当然都是独一无二的。 (因此,但可能与问题无关,每个关系的逆对也存在。)因此数据的一个非常小的样本将是:

1001, 1011
1001, 1012
1001, 1013
1011, 1001
1012, 1001
1013, 1001
etc...

个人 1001 有 3 个朋友,当然每个朋友都有个人 1001 作为朋友,等等。这个表可能有数百万甚至数亿的关系(行),任何给定的人都会可能有数百个朋友。并且它们会被频繁地插入和更新(实际上在这种情况下,删除了一些现有的,添加了新的,没有实际的行更新),并且没有特定的顺序。对于任何给定批次的插入,它们都可以按 personId,friendId 排序,但除此之外,随着时间的推移,插入在很大程度上将是无序的。

此表的用途是查询给定人员的所有朋友,或内部联接到人员查询以分组和聚合与每个人的朋友相关的其他数据等,这是您期望的典型用途一对多关系表。查询性能可能比插入性能更重要,但两者都很重要,因为两者都会经常发生。示例查询:

SELECT p.Name FROM Friends f
INNER JOIN People p ON f.friendId = p.id
WHERE f.personId = @personId

过去,我想都没想就给表一个复合主键personId,friendId,在SQL Server中默认创建为聚集索引,完成用它。但我以前从未处理过如此庞大且对性能至关重要的数据,所以我质疑这个决定。我看不出有任何方式可以以一种不会导致严重且频繁的碎片化的方式来构造这样的表。我的问题是:

  1. 有没有更好的方法来构建这些数据?

  2. 考虑到聚集索引的两个 int 列代表表中的唯一数据,碎片是否可能像我假设的那样糟糕,如果是这样,这些条件下的碎片是否会导致同样严重我假设的性能受到影响?

(除非我不熟悉 RDBMS 中有一些完全不同的概念,否则我假设第一个问题的答案是否定的。所以这主要是第二个问题,我希望有人有良好的经验基础来回答一下。顺便说一句,如果有区别的话,数据库是 SQL Azure。)

感谢你们中的 DBA 大师,他们提供了一些见解!

【问题讨论】:

  • 你需要维护一个索引,不管它是否聚集。据推测,索引需要包含两列(或者您将进行 lot 的 RID 查找),因此它也可能是一个聚集索引。免责声明:不是 DBA。
  • 是的,很明显我需要一个索引,无论是否聚集。如果,或者,我给每一行一个顺序标识 id 作为主键/聚集索引,例如relationId,因为插入会经常发生并且不经常删除,所以聚集索引应该保持相对完整,但也永远不会被使用 - 我需要一个关于 personId,friendId 的非聚集索引b>,它会被使用,它会变得非常支离破碎,一事无成,对吧?
  • 你明白聚集索引就是表吗?
  • @Namphibian 是的,我知道聚集索引实际上就是表本身。但对其他人来说很好的澄清。
  • @Namphibian 是的,这正是我的意思。

标签: sql sql-server database database-administration


【解决方案1】:

您只需要包含两个字段的聚集索引。索引是有序数据,无论是否聚集。 如果您创建非聚集索引,您的数据将加倍,并且每个插入操作都需要加倍的资源,因为它将在堆(或 row_id 聚集索引)和非聚集索引中插入数据。但是查找操作将只使用非聚集索引,因为所有需要的数据都包含在其中。

所以制作聚集索引并快乐:)

【讨论】:

  • 谢谢布吉。在这种情况下,我愿意为性能牺牲一些字节,但正如您所指出的,我认为没有任何收获。我怀疑我只需要观察碎片和性能,并根据需要进行更正(重建)。除非其他人对这种情况下碎片化的可能程度和影响提供了一些深刻的见解,否则我会将其标记为正确的。
【解决方案2】:

您可以在一段时间内不断重组表(CTAS 等)以解决碎片问题。

但是,最重要的是,我建议调整 SQL 和 wd 强烈建议不要将“人”与“朋友”连接起来,因为在这种情况下,人似乎也是大表。

为了让您的查询执行得更快,我首先将您的 SQL 调整为:

SELECT f.*, p.NAME FROM 
(
SELECT personId, friendId FROM Friends f
WHERE f.personId = @personId
) f
, People p ON f.friendId = p.ID

试一试看看...

【讨论】:

  • 所以.. 你是从人加入到朋友的子查询中,而不是直接加入?我不认为这会更快..
  • @Blorgbeard 它对我产生了影响,在 Oracle 中没有类似的场合。不确定 SQL Server/Azure。但是,我相信它应该有所作为。子查询应该首先为一个人拉一个满是朋友的手。然后可以将此结果集与 'people' 表连接以获取 NAME。
  • 我在过去几年中看到的由 SQL Server 生成的任何执行计划都会在查找 People 表中的行之前应用过滤器 (WHERE),并且会执行得非常好。有些人喜欢使用或不使用 JOIN 语法,但我认为性能不会有所不同。甚至可以生成相同的执行计划。
  • @reads0520 可以分享执行计划吗?另外,请查看以下建议 SQL Server 应优先考虑此子查询:- stackoverflow.com/questions/2263186/…
  • @pahariayogi 我想说那篇文章完全符合我的观点,当他说它将即时决定它认为最有意义的事情时。在像我上面的示例查询这样一个简单的情况下,它不会选择加入一百万行然后将其过滤到几行,它会过滤然后加入结果。
猜你喜欢
  • 1970-01-01
  • 2019-10-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-02-15
  • 1970-01-01
  • 2011-11-08
  • 1970-01-01
相关资源
最近更新 更多