Як 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 

Помилка msg:

Посилання 3723, Рівень 16, Стан 6, Рядок 36 Явний DROP INDEX заборонено в індексі "Parent.IX_Parent". Він використовується для примусового виконання закордонних ключів.


Коментарі не для розширеного обговорення; ця розмова була переміщена до чату .
Пол Білий 9

Відповіді:


7

(Відсутність) документації говорить про те, що така поведінка є деталізацією реалізації, а тому не визначена і підлягає зміні в будь-який час.

Це суворо на відміну від CREATE FULLTEXT INDEX , де вам потрібно вказати ім'я індексу, до якого слід приєднатись - AFAIK, немає недокументованого FOREIGN KEYсинтаксису, який би виконував еквівалент (хоча теоретично це може бути в майбутньому).

Як згадувалося, має сенс, що SQL Server вибирає найменший фізичний індекс, з яким асоціюватиме іноземний ключ. Якщо ви зміните сценарій, щоб створити унікальне обмеження як CLUSTERED, сценарій "працює" на R2 2008 року. Але така поведінка все ще не визначена, і на неї не слід покладатися.

Як і у більшості застарілих додатків, вам просто доведеться перейти до азотно-крутої та очистити речі.


"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);

де таблиця 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ще немає, виконання цих команд призведе до помилок.

Перша команда повертає таку помилку:

Msg 1776, рівень 16, стан 0, рядок 4

У посилальній таблиці "T" немає первинних або кандидатських ключів, які б відповідали списку стовпців, що посилаються, в іноземному ключі "FK_TRef_T_1".

Однак друга команда повертає різні помилки:

Msg 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) Якщо <посилається таблиця та стовпці> не вказує <список посилальних стовпців>, дескриптор таблиці згаданої таблиці повинен містити унікальне обмеження, яке визначає ПЕРШИЙ КЛЮЧ. Нехай посилаються стовпці - це стовпець або стовпці, ідентифіковані унікальними стовпцями в цьому унікальному обмеженні, і посилається стовпець - один такий стовпець. Вважається, що <посилання на таблицю та стовпці> неявно вказує <список посилальних стовпців>, ідентичний тому <унікальний список стовпців>.
...

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). Це лише спостереження, звичайно, жодних гарантій у цьому випадку.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.