SQL Server如何为外键引用选择索引键?


9

我正在使用从MS Access导入的旧数据库。在MS Access> SQL Server升级期间创建了大约二十个带有非集群唯一主键的表。

这些表中的许多表还具有唯一的非聚集索引,这些索引与主键重复。

我正在尝试清理。

但是我发现的是,在将主键重新创建为聚簇索引之后,然后尝试重建外键,该外键引用了旧的重复索引(这是唯一的)。

我知道这是因为它不会让我删除重复的索引。

我认为如果存在,SQL Server总是会选择一个主键。SQL Server是否具有在唯一索引和主键之间进行选择的方法?

要复制该问题(在SQL Server 2008 R2上):

IF EXISTS (SELECT * FROM sys.tables WHERE name = 'Child') DROP TABLE Child
GO
IF EXISTS (SELECT * FROM sys.tables WHERE name = 'Parent') DROP TABLE Parent
GO

-- Create the parent table
CREATE TABLE Parent (ParentID INT NOT NULL IDENTITY(1,1)) 

-- Make the parent table a heap
ALTER TABLE Parent ADD CONSTRAINT PK_Parent PRIMARY KEY NONCLUSTERED (ParentID) 

-- Create the duplicate index on the parent table
CREATE UNIQUE NONCLUSTERED INDEX IX_Parent ON Parent (ParentID) 

-- Create the child table
CREATE TABLE Child  (ChildID  INT NOT NULL IDENTITY(1,1), ParentID INT NOT NULL ) 

-- Give the child table a normal PKey
ALTER TABLE Child ADD CONSTRAINT PK_Child PRIMARY KEY CLUSTERED (ChildID) 

-- Create a foreign key relationship with the Parent table on ParentID
ALTER TABLE Child ADD CONSTRAINT FK_Child FOREIGN KEY (ParentID) 
REFERENCES Parent (ParentID) ON DELETE CASCADE NOT FOR REPLICATION

-- Try to clean this up
-- Drop the foreign key constraint on the Child table
ALTER TABLE Child DROP CONSTRAINT FK_Child

-- Drop the primary key constraint on the Parent table
ALTER TABLE Parent DROP CONSTRAINT PK_Parent

-- Recreate the primary key on Parent as a clustered index
ALTER TABLE Parent ADD CONSTRAINT PK_Parent PRIMARY KEY CLUSTERED (ParentID) 

-- Recreate the foreign key in Child pointing to parent ID
ALTER TABLE Child ADD CONSTRAINT FK_Child FOREIGN KEY (ParentID) 
REFERENCES Parent (ParentID) ON DELETE CASCADE NOT FOR REPLICATION

-- Try to drop the duplicate index on Parent 
DROP INDEX IX_Parent ON Parent 

错误消息:

消息3723,级别16,状态6,第36行,索引'Parent.IX_Parent'上不允许使用显式的DROP INDEX。它被用于FOREIGN KEY约束实施。


评论不作进一步讨论;此对话已转移至聊天
保罗·怀特9

Answers:


7

(缺少)文档表明此行为是实现细节,因此未定义并且随时可能更改。

这与CREATE FULLTEXT INDEX形成鲜明对比,在CREATE FULLTEXT INDEX中,您必须指定要附加的索引的名称-AFAIK,没有任何未记录的FOREIGN KEY语法可以进行等效操作(尽管理论上可能会在将来出现)。

如前所述,SQL Server选择与外键关联的最小物理索引确实有意义。如果更改脚本以创建唯一约束为CLUSTERED,则脚本在2008 R2上“有效”。但是,这种行为仍然不确定的,不应该被依赖。

与大多数旧版应用程序一样,您只需精打细算并清理一下。


实际上,“ SQL Server选择与外键关联的最小物理索引”。相邻答案中有一个示例,其中SqlServer选择不是最小物理大小的索引。
i-one

3

SQL Server是否具有在唯一索引和主键之间进行选择的方法?

当创建外键并且被引用的表上确实存在替代键约束或唯一索引时,至少可以将SqlServer定向为引用主键。

如果需要引用主键,则仅应在外键定义中指定被引用表的名称,而被引用列的列表应省略:

ALTER TABLE Child
    ADD CONSTRAINT FK_Child_Parent FOREIGN KEY (ParentID)
        -- omit key columns of the referenced table
        REFERENCES Parent /*(ParentID)*/;

下面有更多详细信息。


请考虑以下设置:

CREATE TABLE T (id int NOT NULL, a int, b int, c uniqueidentifier, filler binary(1000));
CREATE TABLE TRef (tid int NULL);

where table TRef打算引用table T

要创建引用约束,可以使用ALTER TABLE带有两种替代方法的命令:

ALTER TABLE TRef
    ADD CONSTRAINT FK_TRef_T_1 FOREIGN KEY (tid) REFERENCES T (id);

ALTER TABLE TRef
    ADD CONSTRAINT FK_TRef_T_2 FOREIGN KEY (tid) REFERENCES T;

请注意,在第二种情况下,未指定要引用的表的列(REFERENCES TREFERENCES T (id))。

由于尚无键索引T,因此执行这些命令将产生错误。

第一条命令返回以下错误:

消息1776,级别16,状态0,第4行

有没有主或候选键在引用表“T”的引用列的列表中的外键“FK_TRef_T_1”匹配。

但是,第二个命令返回不同的错误:

消息1773,级别16,状态0,第4行

外键“ FK_TRef_T_2”具有对对象“ T”的隐式引用,该对象上未定义主键

看到在第一种情况下,期望是主键或候选键,而在第二种情况下,期望仅是主键

让我们检查SqlServer是否在第二个命令中使用主键以外的东西。

如果我们在上添加一些唯一索引和唯一键T

CREATE UNIQUE INDEX IX_T_1 on T(id) INCLUDE (filler);
CREATE UNIQUE INDEX IX_T_2 on T(id) INCLUDE (c);
CREATE UNIQUE INDEX IX_T_3 ON T(id) INCLUDE (a, b);

ALTER TABLE T
    ADD CONSTRAINT UQ_T UNIQUE CLUSTERED (id);

FK_TRef_T_1创建命令成功,但是FK_TRef_T_2创建命令仍然失败,消息为1773。

最后,如果我们在上添加主键T

ALTER TABLE T
    ADD CONSTRAINT PK_T PRIMARY KEY NONCLUSTERED (id);

FK_TRef_T_2创建命令成功。

让我们检查一下表T的外键引用了表的哪些索引TRef

select
    ix.index_id,
    ix.name as index_name,
    ix.type_desc as index_type_desc,
    fk.name as fk_name
from sys.indexes ix
    left join sys.foreign_keys fk on
        fk.referenced_object_id = ix.object_id
        and fk.key_index_id = ix.index_id
        and fk.parent_object_id = object_id('TRef')
where ix.object_id = object_id('T');

这将返回:

index_id  index_name  index_type_desc   fk_name
--------- ----------- ----------------- ------------
1         UQ_T        CLUSTERED         NULL
2         IX_T_1      NONCLUSTERED      FK_TRef_T_1
3         IX_T_2      NONCLUSTERED      NULL
4         IX_T_3      NONCLUSTERED      NULL
5         PK_T        NONCLUSTERED      FK_TRef_T_2

看到FK_TRef_T_2对应于PK_T

因此,是的,使用REFERENCES T语法将的外键TRef映射到的主键T

我无法直接在SqlServer文档中找到这种行为,但是专用消息1773表示这不是偶然的。这样的实现可能符合SQL标准,以下摘自ANSI / ISO 9075-2:2003第11.8节

11模式定义和操作

11.8 <引用约束定义>

功能
指定引用约束。

格式

<referential constraint definition> ::=
    FOREIGN KEY <left paren> <referencing columns> <right paren>
        <references specification>

<references specification> ::=
    REFERENCES <referenced table and columns>
    [ MATCH <match type> ]
    [ <referential triggered action> ]
...

语法规则
...
3)情况:
...
b)如果<被引用表和列>未指定<引用列列表>,则被引用表的表描述符应包括一个指定主键的唯一约束。令引用的 列为在该唯一约束中由唯一列标识的一个或多个列,令引用的列为此类列中的一个。<引用表和列>应视为隐式指定与该<唯一列列表>相同的<引用列列表>。
...

Transact-SQL支持并扩展ANSI SQL。但是,它不完全符合SQL Standard。有一个名为SQL Server Transact-SQL ISO / IEC 9075-2标准支持文档(简称MS-TSQLISO02,请参阅此处),描述了Transact-SQL提供的支持级别。该文档列出了标准的扩展和变体。例如,它记录MATCH了引用约束定义中不支持该子句的信息。但是没有记录的与所引用标准有关的变体。因此,我认为观察到的行为已被充分记录在案。

并且使用REFERENCES T (<reference column list>)语法,似乎SqlServer在被引用的表的索引中选择第一个合适的非聚集索引(index_id看似最少的一个,而不是问题注释中假定的最小物理大小的索引),或者如果选择聚集索引适合,并且没有合适的非聚集索引。自SqlServer 2008(版本10.0)以来,这种行为似乎是一致的。当然,这只是观察,在这种情况下不能保证。

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.