Làm thế nào để SQL Server chọn khóa chỉ mục cho tham chiếu khóa ngoài?


9

Tôi đang làm việc với cơ sở dữ liệu cũ được nhập từ MS Access. Có khoảng hai mươi bảng với các khóa chính duy nhất không được phân cụm, được tạo trong quá trình nâng cấp MS Access> SQL Server.

Nhiều bảng trong số này cũng có các chỉ mục duy nhất, không phân cụm là các bản sao của khóa chính.

Tôi đang cố gắng để làm sạch này.

Nhưng những gì tôi đã tìm thấy là sau khi tôi tạo lại các khóa chính dưới dạng các chỉ mục được nhóm lại, và sau đó cố gắng xây dựng lại khóa ngoại, khóa ngoại đang tham chiếu chỉ mục cũ, trùng lặp (là duy nhất).

Tôi biết điều này bởi vì nó sẽ không cho phép tôi bỏ các chỉ mục trùng lặp.

Tôi nghĩ rằng SQL Server sẽ luôn chọn khóa chính nếu có. SQL Server có phương pháp chọn giữa một chỉ mục duy nhất và khóa chính không?

Để nhân đôi sự cố (trên 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 

Thông báo lỗi:

Msg 3723, Cấp 16, Bang 6, Dòng 36 INDEX DROP rõ ràng không được phép trên chỉ mục 'Parent.IX_Parent'. Nó đang được sử dụng để thực thi ràng buộc FOREIGN KEY.


Bình luận không dành cho thảo luận mở rộng; cuộc trò chuyện này đã được chuyển sang trò chuyện .
Paul White 9

Câu trả lời:


7

Các (thiếu) tài liệu cho thấy rằng hành vi này là một chi tiết thực hiện, và do đó không xác định và phụ thuộc vào sự thay đổi bất cứ lúc nào.

Điều này trái ngược hoàn toàn với CREATE FULLTEXT INDEX , nơi bạn phải chỉ định tên của một chỉ mục để đính kèm - AFAIK, không có FOREIGN KEYcú pháp không có giấy tờ để làm tương tự (mặc dù về mặt lý thuyết, có thể có trong tương lai).

Như đã đề cập, có nghĩa là SQL Server chọn chỉ mục vật lý nhỏ nhất để liên kết khóa ngoại. Nếu bạn thay đổi tập lệnh để tạo ràng buộc duy nhất như CLUSTERED, tập lệnh "hoạt động" trên 2008 R2. Nhưng hành vi đó vẫn chưa được xác định và không nên dựa vào.

Như với hầu hết các ứng dụng cũ, bạn sẽ chỉ cần xuống những thứ bẩn thỉu và sạch sẽ.


"SQL Server chọn chỉ số vật lý nhỏ nhất để liên kết khóa ngoại" không nhất thiết phải thực sự. Có một ví dụ trong câu trả lời lân cận nơi SqlServer chọn chỉ mục không có kích thước vật lý nhỏ nhất.
i-một

3

SQL Server có phương pháp chọn giữa một chỉ mục duy nhất và khóa chính không?

Ít nhất có thể hướng SqlServer tới khóa chính tham chiếu, khi khóa ngoại được tạo và các ràng buộc khóa thay thế hoặc các chỉ mục duy nhất tồn tại trên bảng được tham chiếu.

Nếu khóa chính cần được tham chiếu, thì chỉ nên chỉ định tên của bảng được tham chiếu trong định nghĩa khóa ngoài và danh sách các cột được tham chiếu nên được bỏ qua:

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

Chi tiết bên dưới.


Hãy xem xét các thiết lập sau:

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

nơi bảng TRefdự định tham khảo bảng T.

Để tạo ràng buộc tham chiếu, người ta có thể sử dụng ALTER TABLElệnh với hai lựa chọn thay thế:

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;

lưu ý rằng trong trường hợp thứ hai, không có cột nào của bảng được tham chiếu được chỉ định ( REFERENCES Tso với REFERENCES T (id)).

Vì chưa có chỉ mục chính Tnào, việc thực thi các lệnh này sẽ phát sinh lỗi.

Lệnh đầu tiên trả về lỗi sau:

Msg 1776, Cấp 16, Bang 0, Dòng 4

Không có khóa chính hoặc khóa ứng cử viên trong bảng được tham chiếu 'T' khớp với danh sách cột tham chiếu trong khóa ngoại 'FK_TRef_T_1'.

Lệnh thứ hai, tuy nhiên, trả về lỗi khác nhau:

Msg 1773, Cấp 16, Bang 0, Dòng 4

Khóa ngoại 'FK_TRef_T_2' có tham chiếu ngầm đến đối tượng 'T' không có khóa chính được xác định trên đó.

thấy rằng trong trường hợp đầu tiên, kỳ vọng là khóa chính hoặc khóa ứng viên , trong khi đó, trường hợp thứ hai chỉ là khóa chính .

Hãy kiểm tra xem SqlServer có sử dụng thứ gì khác ngoài khóa chính với lệnh thứ hai hay không.

Nếu chúng tôi thêm một số chỉ mục duy nhất và khóa duy nhất trên 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);

lệnh FK_TRef_T_1tạo sáng tạo thành công, nhưng lệnh FK_TRef_T_2tạo sáng tạo vẫn thất bại với Msg 1773.

Cuối cùng, nếu chúng ta thêm khóa chính vào T:

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

lệnh để FK_TRef_T_2tạo thành công.

Hãy kiểm tra xem các chỉ mục nào của bảng Tđược tham chiếu bởi các khóa ngoại của bảng 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');

cái này trả về:

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

thấy rằng FK_TRef_T_2tương ứng với PK_T.

Vì vậy, có, với việc sử dụng REFERENCES Tcú pháp khóa ngoài của TRefánh xạ được ánh xạ tới khóa chính của T.

Tôi không thể tìm thấy hành vi như vậy được mô tả trực tiếp trong tài liệu SqlServer, nhưng Msg 1773 dành riêng cho thấy rằng nó không phải là ngẫu nhiên. Việc triển khai như vậy có thể cung cấp sự tuân thủ với Tiêu chuẩn SQL, bên dưới là đoạn trích ngắn từ phần 11.8 của ANSI / ISO 9075-2: 2003

11 Định nghĩa và thao tác lược đồ

11.8 <định nghĩa ràng buộc tham chiếu>

Chức năng
Chỉ định một ràng buộc tham chiếu.

định dạng

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

Quy tắc cú pháp
...
3) Trường hợp:
...
b) Nếu <bảng và cột tham chiếu> không chỉ định <danh sách cột tham chiếu>, thì bộ mô tả bảng của bảng được tham chiếu sẽ bao gồm một ràng buộc duy nhất chỉ định KEY PRIMARY. Đặt các cột được tham chiếu là cột hoặc cột được xác định bởi các cột duy nhất trong ràng buộc duy nhất đó và để cột được tham chiếu là một cột như vậy. <Bảng và cột được tham chiếu> sẽ được xem xét để chỉ định ngầm định <danh sách cột tham chiếu> giống hệt với <danh sách cột duy nhất>.
...

Transact-SQL hỗ trợ và mở rộng ANSI SQL. Tuy nhiên, nó không phù hợp với tiêu chuẩn SQL. Có một tài liệu có tên SQL Server Transact-SQL ISO / IEC 9075-2 Tài liệu hỗ trợ tiêu chuẩn (viết tắt là MS-TSQLISO02, xem tại đây ) mô tả mức độ hỗ trợ được cung cấp bởi Transact-SQL. Tài liệu liệt kê các phần mở rộng và các biến thể theo tiêu chuẩn. Ví dụ, tài liệu đó là MATCHmệnh đề không được hỗ trợ trong định nghĩa ràng buộc tham chiếu. Nhưng không có biến thể tài liệu liên quan đến phần trích dẫn của tiêu chuẩn. Vì vậy, ý kiến ​​của tôi là hành vi quan sát được ghi lại đủ.

Và với việc sử dụng REFERENCES T (<reference column list>)cú pháp, có vẻ như SqlServer chọn chỉ mục không bao gồm phù hợp đầu tiên trong số các chỉ mục của bảng được tham chiếu (cái có ít nhất index_id, không phải là cái có kích thước vật lý nhỏ nhất như được giả định trong các câu hỏi) hoặc chỉ mục được nhóm phù hợp và không có chỉ số không bao gồm phù hợp. Hành vi như vậy có vẻ phù hợp kể từ SqlServer 2008 (phiên bản 10.0). Đây chỉ là quan sát tất nhiên, không có đảm bảo trong trường hợp này.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.