Bagaimana SQL Server memilih kunci indeks untuk referensi kunci asing?

9

Saya bekerja dengan database lama yang diimpor dari MS Access. Ada sekitar dua puluh tabel dengan kunci primer non-clustered, unik yang dibuat selama MS Access> peningkatan SQL Server.

Banyak dari tabel ini juga memiliki indeks unik, non-cluster yang merupakan duplikat dari kunci utama.

Saya mencoba untuk membersihkan ini.

Tapi apa yang saya temukan adalah setelah saya membuat kembali kunci utama sebagai indeks berkerumun, dan kemudian mencoba untuk membangun kembali kunci asing, kunci asing referensi lama, indeks duplikat (yang unik).

Saya tahu ini karena tidak akan membiarkan saya menjatuhkan indeks duplikat.

Saya akan berpikir SQL Server akan selalu memilih kunci utama jika ada. Apakah SQL Server memiliki metode pemilihan antara indeks unik dan kunci utama?

Untuk menduplikasi masalah (pada 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 

Pesan kesalahan:

Msg 3723, Level 16, Negara 6, Baris 36 INDEX DROP eksplisit tidak diizinkan pada indeks 'Parent.IX_Parent'. Ini sedang digunakan untuk penegakan kendala ASING KUNCI.

8kb
sumber
Komentar bukan untuk diskusi panjang; percakapan ini telah dipindahkan ke obrolan .
Paul White 9

Jawaban:

7

Dokumentasi (kurangnya) menunjukkan bahwa perilaku ini merupakan detail implementasi, dan karenanya tidak terdefinisi dan dapat berubah sewaktu-waktu.

Ini sangat kontras dengan CREATE FULLTEXT INDEX , di mana Anda harus menentukan nama indeks yang akan dilampirkan - AFAIK, tidak ada FOREIGN KEYsintaks yang tidak berdokumen untuk melakukan yang setara (walaupun secara teoritis, mungkin ada di masa depan).

Seperti disebutkan, masuk akal bahwa SQL Server memilih indeks fisik terkecil untuk mengaitkan kunci asing. Jika Anda mengubah skrip untuk membuat batasan unik CLUSTERED, skrip "berfungsi" pada 2008 R2. Namun perilaku itu masih belum jelas dan tidak bisa diandalkan.

Seperti sebagian besar aplikasi lawas, Anda hanya perlu turun ke hal-hal sepele dan membersihkan.

Jon Seigel
sumber
"SQL Server memilih indeks fisik terkecil untuk mengaitkan kunci asing" belum tentu sebenarnya. Ada contoh dalam jawaban tetangga di mana SqlServer memilih indeks yang bukan dari ukuran fisik terkecil.
i-one
3

Apakah SQL Server memiliki metode pemilihan antara indeks unik dan kunci utama?

Setidaknya dimungkinkan untuk mengarahkan SqlServer ke referensi kunci utama, ketika kunci asing sedang dibuat dan kendala kunci alternatif atau indeks unik memang ada pada tabel yang direferensikan.

Jika kunci primer perlu direferensikan, maka hanya nama tabel yang direferensikan harus ditentukan dalam definisi kunci asing dan daftar kolom yang direferensikan harus dihilangkan:

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

Lebih detail di bawah ini.


Pertimbangkan pengaturan berikut:

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

di mana tabel TRefbermaksud ke tabel referensi T.

Untuk membuat batasan referensial seseorang dapat menggunakan ALTER TABLEperintah dengan dua alternatif:

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;

perhatikan bahwa dalam kasus kedua tidak ada kolom dari tabel yang direferensikan ditentukan ( REFERENCES Tversus REFERENCES T (id)).

Karena belum ada indeks kunci T, eksekusi perintah ini akan menghasilkan kesalahan.

Perintah pertama mengembalikan kesalahan berikut:

Msg 1776, Level 16, Negara 0, Jalur 4

Tidak ada kunci utama atau kandidat dalam tabel referensi 'T' yang cocok dengan daftar kolom referensi di kunci asing 'FK_TRef_T_1'.

Perintah kedua, bagaimanapun, mengembalikan kesalahan yang berbeda:

Msg 1773, Level 16, Negara 0, Jalur 4

Kunci asing 'FK_TRef_T_2' memiliki referensi implisit ke objek 'T' yang tidak memiliki kunci utama yang ditentukan di atasnya.

melihat bahwa dalam harapan kasus pertama adalah kunci primer atau kandidat , sedangkan dalam kasus kedua harapan adalah kunci primer saja.

Mari kita periksa apakah SqlServer akan menggunakan sesuatu selain kunci utama dengan perintah kedua atau tidak.

Jika kami menambahkan beberapa indeks unik dan kunci unik pada 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);

perintah untuk FK_TRef_T_1penciptaan berhasil, tetapi perintah untuk FK_TRef_T_2penciptaan masih gagal dengan Msg 1773.

Akhirnya, jika kita menambahkan kunci utama pada T:

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

perintah untuk FK_TRef_T_2penciptaan berhasil.

Mari kita periksa indeks apa dari tabel Tyang dirujuk oleh kunci asing tabel 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');

ini mengembalikan:

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

lihat yang FK_TRef_T_2sesuai dengan PK_T.

Jadi, ya, dengan menggunakan REFERENCES Tsintaks kunci asing TRefdipetakan ke kunci primer T.

Saya tidak dapat menemukan perilaku seperti yang dijelaskan dalam dokumentasi SqlServer secara langsung, tetapi Msg 1773 khusus menunjukkan bahwa itu tidak disengaja. Kemungkinan implementasi tersebut memberikan kepatuhan dengan Standar SQL, di bawah ini adalah kutipan singkat dari bagian 11.8 dari ANSI / ISO 9075-2: 2003

11 Definisi dan manipulasi skema

11.8 <definisi kendala referensial>

Fungsi:
Menentukan batasan referensial.

Format

<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> ]
...

Aturan Sintaks
...
3) Kasus:
...
b) Jika <tabel dan kolom yang direferensikan> tidak menentukan <daftar kolom referensi>, maka deskriptor tabel dari tabel yang direferensikan harus menyertakan kendala unik yang menentukan KUNCI UTAMA. Biarkan kolom yang direferensikan menjadi kolom atau kolom yang diidentifikasi oleh kolom unik dalam batasan unik tersebut dan biarkan kolom yang direferensikan menjadi salah satu kolom tersebut. <Tabel dan kolom yang direferensikan> harus dianggap secara implisit menentukan <daftar kolom referensi> yang identik dengan <daftar kolom unik> tersebut.
...

Transact-SQL mendukung dan memperluas ANSI SQL. Namun tidak sesuai dengan SQL Standard. Ada dokumen bernama SQL Server Transact-SQL ISO / IEC 9075-2 Dokumen Pendukung Standar (MS-TSQLISO02 secara singkat, lihat di sini ) yang menggambarkan tingkat dukungan yang disediakan oleh Transact-SQL. Dokumen mencantumkan ekstensi dan variasi ke standar. Sebagai contoh, dokumen ini menyatakan bahwa MATCHklausa tidak didukung dalam definisi kendala referensial. Tetapi tidak ada variasi terdokumentasi yang relevan dengan potongan standar yang dikutip. Jadi, pendapat saya adalah bahwa perilaku yang diamati cukup terdokumentasi.

Dan dengan menggunakan REFERENCES T (<reference column list>)sintaksis tampaknya SqlServer memilih indeks nonclustered yang cocok pertama di antara indeks tabel yang direferensikan (satu dengan yang index_idtampaknya paling sedikit , bukan yang dengan ukuran fisik terkecil seperti yang diasumsikan dalam komentar pertanyaan), atau indeks berkelompok jika cocok dan tidak ada indeks nonclustered yang cocok. Perilaku seperti itu tampaknya konsisten sejak SqlServer 2008 (versi 10.0). Ini hanya pengamatan saja, tidak ada jaminan dalam hal ini.

i-satu
sumber