如果复合索引包含主键,是否应该将其标记为唯一?


9

给定一些带有主键的表,例如:

CREATE TABLE Customers (
   CustomerID int NOT NULL PRIMARY KEY,
   FirstName nvarchar(50),
   LastName nvarchar(50),
   Address nvarchar(200),
   Email nvarchar(260)
   --...
)

我们在上有一个唯一的主键CustomerID

传统上,我可能需要一些额外的覆盖索引。例如,通过CustomerID或快速查找用户Email

CREATE INDEX IX_Customers_CustomerIDEmail ON Customers
(
   CustomerID,
   Email
)

这些都是我数十年来创建的索引。

不一定要唯一,但实际上是

索引本身的存在是为了避免进行表扫描。它是覆盖索引,以帮助提高性能(该索引不存在作为强制执行唯一性的约束)。

今天,我想起了一些信息-SQL Server可以使用以下事实:

  • 列具有外键约束
  • 列具有唯一索引
  • 约束是可信的

为了帮助它优化查询执行。实际上,根据《SQL Server索引设计指南》

如果数据是唯一的并且您要强制执行唯一性,那么在相同的列组合上创建唯一索引而不是非唯一索引将为查询优化器提供附加信息,从而可以产生更有效的执行计划。在这种情况下,建议创建唯一索引(最好通过创建UNIQUE约束)。

鉴于我的多列索引包含主键,因此该复合索引实际上将是唯一的。这不是我特别需要SQL Server在每次插入或更新期间强制执行的约束;但是事实是该非聚集索引唯一的。

将这个事实上的唯一索引标记为实际上唯一有什么好处吗?

在客户上创建唯一索引IX_Customers_CustomerIDEmail
(
   客户ID,
   电子邮件
)

在我看来,SQL Server 可能足够聪明,可以意识到我的索引已经具有唯一性,因为它包含主键。

  • 但是也许它不知道,如果我将索引声明为唯一的话,对于优化器来说是有好处的。
  • 除非现在可能会导致插入和更新过程中的速度变慢,否则它必须执行唯一性检查-以前从未需要这样做。
  • 除非它知道索引保证已经是唯一的,否则因为它包含主键。

对于复合索引包含主键时该怎么办,我找不到Microsoft的任何指导。

唯一索引的优点包括:

  • 确保已定义列的数据完整性。
  • 提供了有助于查询优化器的其他信息。

如果复合索引已经包含主键,是否应该将其标记为唯一?还是SQL Server可以自己解决这个问题?


4
要通过CustomerID快速找到客户,PK可能就足够了。为了通过电子邮件找到客户,我宁愿仅在电子邮件上使用第二个索引(它已经涵盖了集群键)。无论如何,我知道这是虚构的,但是,是的,如果您知道它必须是唯一的,我会将其标记为唯一,该约束可能无法充分损害插入内容以抵消可能对优化器有所帮助的情况。但是,这一切都取决于您的工作量,我们将不得不猜测...
亚伦·伯特兰

1
我们在这里详细讨论了它。尽管我们想不出任何方法来提供任何数据来证明它,但我们假设SQL Server专家可能非常聪明,优化器可能已经能够进行这种元优化。我们还认为,最好是让索引传达其意图 -这只是一个索引,并且如果不尝试对SQL Server的功能进行第二次尝试,则不强加唯一性作为业务规则。
伊恩·博伊德

Answers:


11

如果复合索引已经包含主键,是否应该将其标记为唯一?

可能不是。无论如何,优化器通常都可以使用有关所包含键列的唯一性的信息,因此没有真正的优势。

在更新计划上将索引标记为唯一的一个重要后果是修改该索引的键以考虑:

设定

CREATE TABLE dbo.Customers 
(
   CustomerID int NOT NULL PRIMARY KEY,
   FirstName nvarchar(50),
   LastName nvarchar(50),
   [Address] nvarchar(200),
   Email nvarchar(260)
);

CREATE NONCLUSTERED INDEX 
    IX_Customers_CustomerIDEmail 
ON dbo.Customers
(
   CustomerID,
   Email
);

-- Pretend we have some rows
UPDATE STATISTICS dbo.Customers 
WITH ROWCOUNT = 100000, PAGECOUNT = 20000;

每索引更新计划(非唯一索引)

UPDATE dbo.Customers 
SET Email = N'New', [Address] = 'New Address'
WHERE Email = N'Old' 
OPTION (QUERYTRACEON 8790); -- Per-index update plan

执行计划:

分割和过滤

优化器通常在每行(“窄”计划)或每索引(“宽”计划)更新非聚集索引之间做出基于成本的决策。默认策略(内存中的OLTP表除外)是一个广泛的计划。

窄计划(在其中非聚集索引与堆/聚集索引同时维护)是针对小更新的性能优化。并非在所有情况下都实现此优化-使用某些功能(如索引视图)意味着将在广泛的计划中维护关联的索引。

详细信息:优化更改数据的T-SQL查询

在这种情况下,我使用了未记录的跟踪标志8790来强制执行广泛的更新计划:因此,该计划显示了群集索引和非群集索引是分别维护的。

拆分将每个更新变成单独的删除和插入对;过滤器会过滤掉不会导致索引更改的所有行。

详细信息:(非更新更新)由SQL Server QO团队提供。

每索引更新计划(唯一索引)

-- Same index, but unique
CREATE UNIQUE INDEX IX_Customers_CustomerIDEmail ON Customers
(
   CustomerID,
   Email
)
WITH (DROP_EXISTING = ON);

UPDATE dbo.Customers 
SET Email = N'New', [Address] = 'New Address'
WHERE Email = N'Old' 
OPTION (QUERYTRACEON 8790); -- Per-index update plan

执行计划:

拆分-排序-折叠

当索引标记为唯一时,请注意额外的“排序和折叠”运算符

更新唯一索引的键时,需要此Split-Sort-Collapse模式,以防止出现中间的唯一键冲突。

详细信息:Craig Freedman 维护唯一索引

特别是排序可能是个问题。这不仅是不必要的额外成本,而且如果估计不正确,它可能会泄漏到磁盘上。

关于非集群键

要考虑的另一个因素是,即使UNIQUE未指定,非聚集索引结构在索引的每个级别上始终都是唯一的。聚簇键(如果聚簇索引未标记为唯一的话,也可能是唯一符)被添加到所有级别的非唯一非聚簇索引中。

因此,将定义以下索引:

CREATE INDEX IX_Customers_CustomerIDEmail ON Customers
(
   Email
)
WITH (DROP_EXISTING = ON);

...实际上包含所有级别的键(电子邮件,客户ID)。因此,这两列均是“可搜索的”:

SELECT * 
FROM dbo.Customers AS C WITH (INDEX(IX_Customers_CustomerIDEmail))
WHERE C.Email = N'Email'
AND C.CustomerID = 1;

寻求

更多信息:Kalen Delaney的更多有关非聚集索引键的信息


8

SQL已经知道它是唯一的(如果它包含PK,就无法再具有唯一性),无论您是否明确告诉它。

非唯一索引和唯一索引之间的最大区别是,非唯一索引需要在索引的更高级别(而不只是在叶子)处使用聚集索引键(如果CIX未声明为唯一的话,则使用唯一符值)水平。

对于您的情况,您已经在密钥中包含了CIX,这意味着它已经在索引的每个级别上了。

但是您可以创建一个具有单独的PK(唯一)和CIX(无关紧要)的表。然后创建一个非唯一索引,该索引的密钥中包含PK。在表中放入一些行,包括CIX列的一些容易找到的varchar值。放入足够多的行以引起多个级别的索引。然后,您可以使用DBCC IND在NCIX中查找页面,并使用DBCC PAGE打开一些页面以查看数据,以查看CIX键值是否处于较高级别。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.