Как 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 Явный индекс DROP INDEX недопустим для индекса 'Parent.IX_Parent'. Он используется для применения ограничений FOREIGN KEY.

8kb
источник
Комментарии не для расширенного обсуждения; этот разговор был перенесен в чат .
Пол Уайт 9

Ответы:

7

(Отсутствие) документации говорит о том, что это поведение - деталь реализации, поэтому оно не определено и может быть изменено в любое время.

Это резко контрастирует с CREATE FULLTEXT INDEX , где вы должны указать имя индекса для присоединения - AFAIK, нет недокументированного FOREIGN KEYсинтаксиса, чтобы сделать эквивалент (хотя теоретически это может произойти в будущем).

Как уже упоминалось, имеет смысл, что SQL Server выбирает наименьший физический индекс, чтобы связать внешний ключ. Если вы измените сценарий для создания уникального ограничения как CLUSTERED, сценарий «работает» в 2008 R2. Но это поведение все еще не определено и на него нельзя полагаться.

Как и в случае с большинством устаревших приложений, вам нужно просто разобраться с мелочами и привести в порядок вещи.

Джон Сайгель
источник
«SQL Server выбирает наименьший физический индекс, с которым связывается внешний ключ», не обязательно на самом деле. В соседнем ответе есть пример, где SqlServer выбирает индекс, который не имеет наименьшего физического размера.
я
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);

где таблица TRefпредназначена для ссылки на таблицу 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 Tсравнению с REFERENCES 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создания все еще терпит неудачу с Msg 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 напрямую, но выделенная Msg 1773 предполагает, что это не случайно. Вероятно, такая реализация обеспечивает соответствие стандарту SQL, ниже приведен небольшой отрывок из раздела 11.8 стандарта ANSI / ISO 9075-2: 2003.

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) Если <ссылочная таблица и столбцы> не указывает <ссылочный список столбцов>, то дескриптор таблицы ссылочной таблицы должен включать уникальное ограничение, которое задает PRIMARY KEY. Пусть ссылочные столбцы будут столбцом или столбцами, идентифицированными уникальными столбцами в этом уникальном ограничении, и пусть ссылочный столбец будет одним из таких столбцов. <Ссылочная таблица и столбцы> должны рассматриваться как неявно указывающие <список ссылочных столбцов>, который идентичен этому <уникальному списку столбцов>.
...

Transact-SQL поддерживает и расширяет ANSI SQL. Однако это не совсем соответствует стандарту SQL. Существует документ под названием 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). Это просто наблюдение, конечно, никаких гарантий в этом случае.

я-один
источник