我对 SQL (Server 2008) 的低级知识是有限的,现在正受到我们的 DBA 的挑战。让我解释一下(我已经提到了明显的陈述,希望我是对的,但是如果您发现有问题,请告诉我)场景:
我们有一张桌子,上面放着人们的“法庭命令”。当我创建表(名称:CourtOrder)时,我创建它的方式如下:
CREATE TABLE dbo.CourtOrder
(
CourtOrderID INT NOT NULL IDENTITY(1,1), (Primary Key)
PersonId INT NOT NULL,
+ around 20 other fields of different types.
)
然后我对主键应用了一个非聚集索引(为了提高效率)。我的原因是它是一个唯一字段(主键),应该被索引,主要用于选择目的,因为我们经常Select from table where primary key = ...
然后我在 PersonId 上应用了一个 CLUSTERED 索引。原因是对特定人的订单进行物理分组,因为绝大多数工作都是为一个人获得订单。所以,select from mytable where personId = ...
我现在已经被吸引了。有人告诉我,我们应该将聚集索引放在主键上,将普通索引放在 personId 上。这对我来说似乎很奇怪。首先,为什么要将聚集索引放在唯一列上?它是什么聚类?这肯定是对聚集索引的浪费吗?我曾相信正常索引将用于唯一列。此外,对索引进行聚类意味着我们不能对不同的列进行聚类(每个表一个,对吗?)。
我被告知我犯了一个错误的原因是他们认为在 PersonId 上放置一个聚集索引会使插入速度变慢。对于 5% 的选择速度增益,我们的插入和更新速度将降低 95%。这是正确和有效的吗?
他们说,因为我们对 personId 进行了聚类,所以当我们插入或更改 PersonId 时,SQL Server 必须重新排列数据。
那么我问,如果 SQL 这么慢,为什么会有 CLUSTERED INDEX 的概念?有他们说的那么慢吗?我应该如何设置索引以实现最佳性能?我原以为 SELECT 比 INSERT 使用得更多……但他们说我们在 INSERTS 上遇到了锁定问题……
希望可以有人帮帮我。
聚集索引与非聚集索引之间的区别在于聚集索引确定数据库中行的物理顺序。换句话说,将聚集索引应用于 PersonId
意味着行将在表中按 PersonId
进行物理排序,从而允许对此进行索引搜索直接到行(而不是非聚集索引,后者将引导您到该行的位置,添加一个额外的步骤)。
也就是说,主键不是聚集索引是不寻常,但并非闻所未闻。您的方案的问题实际上与您的假设相反:您希望 unique 值在聚集索引中,而不是重复。因为聚集索引确定行的物理顺序,如果索引位于非唯一列上,则服务器必须为具有重复键值的行添加背景值(在您的情况下,任何具有相同键值的行) PersonId
) 以便组合值(键 + 背景值)是唯一的。
我唯一建议的是不要使用代理键(您的 CourtOrderId
)列作为主键,而是使用 PersonId
的复合主键和其他一些唯一标识列或一组列。但是,如果这不可能(或不切实际),则将聚集索引放在 CourtOrderId
上。
我绝不是 SQL 专家……所以请将此视为开发人员的观点,而不是 DBA 的观点……
在不按顺序的聚集(物理排序)索引上插入会导致插入/更新的额外工作。此外,如果您有许多插入同时发生并且它们都发生在同一个位置,那么您最终会出现争用。您的具体表现因您的数据和访问方式而异。一般的经验法则是在表中最独特的窄值(通常是 PK)上构建聚集索引
我假设您的 PersonId 不会改变,因此更新不会在这里发挥作用。但是考虑几行的快照,其中 PersonId 为 1 2 3 3 4 5 6 7 8 8
现在为 PersonId 为 3 插入 20 个新行。首先,由于这不是唯一键,因此服务器会在您的值(在幕后)中添加一些额外的字节以使其唯一(这也增加了额外的空间),然后是位置这些将居住必须改变。将其与插入在末尾发生的自动递增 PK 进行比较。非技术性的解释可能会归结为:如果它在表格末尾自然地取得更高的值,而不是在插入项目时在该位置重新加工现有项目的位置,那么“改组”工作要做的就更少了。
现在,如果您在使用 Inserts 时遇到问题,那么您可能会同时插入一堆相同(或相似)的 PersonId 值,这会导致在整个表格的各个位置进行这项额外工作,并且碎片会杀死您。在您的情况下切换到集群的 PK 的缺点是,如果您今天在 PersonIds 上的插入问题在整个表中分布的值不同,如果您将集群索引切换到 PK 并且所有插入现在都发生在一个位置,那么由于争用集中度的增加,您的问题实际上可能会变得更糟。 (另一方面,如果您今天的插入不是遍布各处,而是通常都集中在相似的区域,那么您的问题可能会通过将聚集索引从 PersonId 切换到您的 PK 来缓解,因为您将最小化碎片化。)
您的性能问题应针对您的独特情况进行分析,并将这些类型的答案仅作为一般指导。最好的办法是依靠能够准确验证问题所在的 DBA。听起来您遇到的资源争用问题可能超出了简单的索引调整。这可能是一个更大问题的征兆。 (可能的设计问题......否则资源限制。)
无论如何,祝你好运!
一些作者确实建议不要在 identity
列上“浪费”CI
,如果有其他有利于范围查询的替代方法。
从 MSDN Clustered Index Design Guidelines 中,应根据以下标准选择密钥
可用于经常使用的查询。提供高度的独特性。可用于范围查询。
您的 CourtOrderID
列符合 2
。您的 PersonId
符合 1
和 3
。由于大多数行最终都会添加 uniqueifier
,因此您不妨将其声明为唯一并使用 PersonId,CourtOrderID
,因为这将是相同的宽度,但更有用,因为将聚集索引键添加到所有 NCI 作为行定位器,这将允许他们覆盖更多查询。
使用 PersonId,CourtOrderID
作为 CI 的主要问题是可能会出现逻辑碎片(这尤其会影响您试图帮助的范围查询),因此您需要监控填充因子和碎片级别并更频繁地执行索引维护.
以下链接对此进行了说明:https://msdn.microsoft.com/en-us/ms190457.aspx
集群的
聚集索引根据键值对表或视图中的数据行进行排序和存储。这些是索引定义中包含的列。每个表只能有一个聚集索引,因为数据行本身只能按一种顺序排序。
表中的数据行按排序顺序存储的唯一时间是表包含聚集索引时。当表具有聚集索引时,该表称为聚集表。如果表没有聚集索引,则其数据行存储在称为堆的无序结构中。
非聚集
非聚集索引具有与数据行分开的结构。非聚集索引包含非聚集索引键值,每个键值条目都有一个指向包含键值的数据行的指针。
从非聚集索引中的索引行指向数据行的指针称为行定位器。行定位器的结构取决于数据页是存储在堆还是聚集表中。对于堆,行定位器是指向行的指针。对于聚集表,行定位符是聚集索引键。
您可以将非键列添加到非聚集索引的叶级别,以绕过现有的索引键限制、900 字节和 16 个键列,并执行完全覆盖的索引查询。
一些带有一些讨厌的选择的数据库,加入一个存储过程 - 唯一的区别是索引
索引 - 集群与非集群
891 rows
10 sec
NONCLUSTERED
OR
891 rows
14 sec
CLUSTERED
PersonId
物理排序。它按PersonId
进行逻辑排序,逻辑和物理顺序之间的任何差异都是逻辑碎片的程度。