Chỉ số duy nhất trên không


14

Tôi đã có một cuộc tranh luận đang diễn ra với các nhà phát triển khác nhau trong văn phòng của tôi về chi phí của một chỉ mục, và liệu tính duy nhất có lợi hay tốn kém (có thể là cả hai). Mấu chốt của vấn đề là nguồn lực cạnh tranh của chúng tôi.

Lý lịch

Trước đây tôi đã đọc một cuộc thảo luận nói rằng một Uniquechỉ mục không phải trả thêm chi phí để duy trì, vì một Inserthoạt động hoàn toàn kiểm tra xem nó phù hợp với cây B nào và, nếu tìm thấy một bản sao trong một chỉ mục không duy nhất, hãy thêm một chỉ thị vào cuối khóa, nhưng nếu không thì chèn trực tiếp. Trong chuỗi sự kiện này, một Uniquechỉ mục không có chi phí bổ sung.

Đồng nghiệp của tôi chống lại tuyên bố này bằng cách nói rằng nó Uniqueđược thi hành như một hoạt động thứ hai sau khi tìm kiếm vị trí mới trong cây B, và do đó tốn kém hơn để duy trì hơn một chỉ số không duy nhất.

Tệ nhất, tôi đã thấy các bảng có một cột định danh (vốn là duy nhất) là khóa phân cụm của bảng, nhưng được tuyên bố rõ ràng là không duy nhất. Mặt khác, điều tồi tệ nhất là nỗi ám ảnh của tôi về tính độc đáo và tất cả các chỉ mục được tạo là duy nhất và khi không thể xác định mối quan hệ duy nhất rõ ràng với một chỉ mục, tôi nối PK của bảng vào cuối chỉ mục để đảm bảo tính độc đáo được đảm bảo.

Tôi thường xuyên tham gia vào các đánh giá mã cho nhóm nhà phát triển và tôi cần có thể đưa ra các hướng dẫn chung để họ tuân theo. Đúng, mọi chỉ số đều được đánh giá, nhưng khi bạn có năm máy chủ với hàng nghìn bảng mỗi bảng và có tới hai mươi chỉ mục trên một bảng, bạn cần có thể áp dụng một số quy tắc đơn giản để đảm bảo mức chất lượng nhất định.

Câu hỏi

Liệu tính duy nhất có một chi phí bổ sung ở mặt sau Insertso với chi phí duy trì một chỉ số không duy nhất? Thứ hai, có gì sai khi gắn thêm Khóa chính của bảng vào cuối chỉ mục để đảm bảo tính duy nhất?

Định nghĩa bảng ví dụ

create table #test_index
    (
    id int not null identity(1, 1),
    dt datetime not null default(current_timestamp),
    val varchar(100) not null,
    is_deleted bit not null default(0),
    primary key nonclustered(id desc),
    unique clustered(dt desc, id desc)
    );

create index
    [nonunique_nonclustered_example]
on #test_index
    (is_deleted)
include
    (val);

create unique index
    [unique_nonclustered_example]
on #test_index
    (is_deleted, dt desc, id desc)
include
    (val);

Thí dụ

Một ví dụ về lý do tại sao tôi sẽ thêm Uniquekhóa vào cuối chỉ mục là trong một trong các bảng thực tế của chúng tôi. Có một Primary Keycái đó là một Identitycột. Tuy nhiên, Clustered Indexthay vào đó là cột lược đồ phân vùng, theo sau là ba kích thước khóa ngoài không có tính duy nhất. Chọn hiệu suất trên bảng này là rất nhiều và tôi thường nhận được thời gian tìm kiếm tốt hơn bằng cách sử dụng Primary Keyvới tra cứu chính thay vì tận dụng Clustered Index. Các bảng khác theo một thiết kế tương tự, nhưng có phần cuối được Primary Keythêm vào có hiệu suất tốt hơn đáng kể.

-- date_int is equivalent to convert(int, convert(varchar, current_timestamp, 112))
if not exists(select * from sys.partition_functions where [name] = N'pf_date_int')
    create partition function 
        pf_date_int (int) 
    as range right for values 
        (19000101, 20180101, 20180401, 20180701, 20181001, 20190101, 20190401, 20190701);
go

if not exists(select * from sys.partition_schemes where [name] = N'ps_date_int')
    create partition scheme 
        ps_date_int
    as partition 
        pf_date_int all 
    to 
        ([PRIMARY]);
go

if not exists(select * from sys.objects where [object_id] = OBJECT_ID(N'dbo.bad_fact_table'))
    create table dbo.bad_fact_table
        (
        id int not null, -- Identity implemented elsewhere, and CDC populates
        date_int int not null,
        dt date not null,
        group_id int not null,
        group_entity_id int not null, -- member of group
        fk_id int not null,
        -- tons of other columns
        primary key nonclustered(id, date_int),
        index [ci_bad_fact_table] clustered (date_int, group_id, group_entity_id, fk_id)
        )
    on ps_date_int(date_int);
go

if not exists(select * from sys.objects where [object_id] = OBJECT_ID(N'dbo.better_fact_table'))
    create table dbo.better_fact_table
        (
        id int not null, -- Identity implemented elsewhere, and CDC populates
        date_int int not null,
        dt date not null,
        group_id int not null,
        group_entity_id int not null, -- member of group
        -- tons of other columns
        primary key nonclustered(id, date_int),
        index [ci_better_fact_table] clustered(date_int, group_id, group_entity_id, id)
        )
    on ps_date_int(date_int);
go

Câu trả lời:


16

Tôi thường xuyên tham gia vào các đánh giá mã cho nhóm nhà phát triển và tôi cần có thể đưa ra các hướng dẫn chung để họ tuân theo.

Môi trường tôi hiện đang tham gia có 250 máy chủ với 2500 cơ sở dữ liệu. Tôi đã làm việc trên các hệ thống với 30.000 cơ sở dữ liệu . Nguyên tắc lập chỉ mục nên xoay quanh quy ước đặt tên, v.v., không phải là "quy tắc" cho các cột cần bao gồm trong một chỉ mục - mỗi chỉ mục riêng lẻ phải được thiết kế để trở thành chỉ mục chính xác cho quy tắc kinh doanh cụ thể hoặc mã đó chạm vào bảng.

Liệu tính duy nhất có một chi phí bổ sung ở mặt sau Insertso với chi phí duy trì một chỉ số không duy nhất? Thứ hai, có gì sai khi gắn thêm Khóa chính của bảng vào cuối chỉ mục để đảm bảo tính duy nhất?

Thêm cột khóa chính vào cuối của một chỉ mục không duy nhất để làm cho nó trông độc đáo đối với tôi là một mô hình chống. Nếu quy tắc kinh doanh chỉ đạo dữ liệu phải là duy nhất, thì hãy thêm một ràng buộc duy nhất cho cột; Nó sẽ tự động tạo ra một chỉ mục duy nhất. Nếu bạn đang lập chỉ mục một cột cho hiệu suất , tại sao bạn lại thêm một cột vào chỉ mục?

Ngay cả khi giả định của bạn về việc thực thi tính duy nhất không thêm bất kỳ chi phí phụ nào là đúng (điều này không đúng với một số trường hợp nhất định), bạn sẽ giải quyết được gì bằng cách làm phức tạp chỉ số?

Trong trường hợp cụ thể của việc thêm khóa chính vào cuối khóa chỉ mục của bạn để bạn có thể thực hiện định nghĩa chỉ mục bao gồm công cụ UNIQUEsửa đổi, nó thực sự tạo ra sự khác biệt về cấu trúc chỉ mục vật lý trên đĩa. Điều này là do bản chất của cấu trúc của các khóa chỉ mục cây B, trong đó chúng luôn cần phải là duy nhất.

Như David Browne đã đề cập trong một bình luận:

Vì mỗi chỉ mục không bao gồm được lưu trữ dưới dạng chỉ mục duy nhất, nên không có thêm chi phí khi chèn vào một chỉ mục duy nhất. Trong thực tế, chi phí bổ sung duy nhất sẽ không thể khai báo khóa ứng viên là một chỉ mục duy nhất, điều này sẽ khiến các khóa chỉ mục được nhóm được gắn vào các khóa chỉ mục.

Lấy ví dụ tối thiểu đầy đủ và có thể kiểm chứng sau đây :

USE tempdb;

DROP TABLE IF EXISTS dbo.IndexTest;
CREATE TABLE dbo.IndexTest
(
    id int NOT NULL
        CONSTRAINT IndexTest_pk
        PRIMARY KEY
        CLUSTERED
        IDENTITY(1,1)
    , rowDate datetime NOT NULL
);

Tôi sẽ thêm hai chỉ mục giống hệt nhau ngoại trừ việc thêm khóa chính ở cuối đuôi của định nghĩa khóa chỉ mục thứ hai:

CREATE INDEX IndexTest_rowDate_ix01
ON dbo.IndexTest(rowDate);

CREATE UNIQUE INDEX IndexTest_rowDate_ix02
ON dbo.IndexTest(rowDate, id);

Tiếp theo, chúng tôi sẽ vài hàng đến bàn:

INSERT INTO dbo.IndexTest (rowDate)
VALUES (DATEADD(SECOND, 0, GETDATE()))
     , (DATEADD(SECOND, 0, GETDATE()))
     , (DATEADD(SECOND, 0, GETDATE()))
     , (DATEADD(SECOND, 1, GETDATE()))
     , (DATEADD(SECOND, 2, GETDATE()));

Như bạn có thể thấy ở trên, ba hàng chứa cùng một giá trị cho rowDatecột và hai hàng chứa các giá trị duy nhất.

Tiếp theo, chúng ta sẽ xem xét các cấu trúc trang vật lý cho từng chỉ mục, sử dụng DBCC PAGElệnh không có giấy tờ :

DECLARE @dbid int = DB_ID();
DECLARE @fileid int;
DECLARE @pageid int;
DECLARE @indexid int;

SELECT @fileid = ddpa.allocated_page_file_id
    , @pageid = ddpa.allocated_page_page_id
FROM sys.indexes i 
CROSS APPLY sys.dm_db_database_page_allocations(DB_ID(), i.object_id, i.index_id, NULL, 'LIMITED') ddpa
WHERE i.name = N'IndexTest_rowDate_ix01'
    AND ddpa.is_allocated = 1
    AND ddpa.is_iam_page = 0;

PRINT N'*************************************** IndexTest_rowDate_ix01 *****************************************';
DBCC TRACEON(3604);
DBCC PAGE (@dbid, @fileid, @pageid, 1);
DBCC TRACEON(3604);
PRINT N'*************************************** IndexTest_rowDate_ix01 *****************************************';

SELECT @fileid = ddpa.allocated_page_file_id
    , @pageid = ddpa.allocated_page_page_id
FROM sys.indexes i 
CROSS APPLY sys.dm_db_database_page_allocations(DB_ID(), i.object_id, i.index_id, NULL, 'LIMITED') ddpa
WHERE i.name = N'IndexTest_rowDate_ix02'
    AND ddpa.is_allocated = 1
    AND ddpa.is_iam_page = 0;

PRINT N'*************************************** IndexTest_rowDate_ix02 *****************************************';
DBCC TRACEON(3604);
DBCC PAGE (@dbid, @fileid, @pageid, 1);
DBCC TRACEON(3604);
PRINT N'*************************************** IndexTest_rowDate_ix02 *****************************************';

Tôi đã xem kết quả đầu ra bằng cách sử dụng Beyond So sánh, và ngoại trừ sự khác biệt rõ ràng xung quanh ID trang phân bổ, v.v., hai cấu trúc chỉ mục giống hệt nhau.

nhập mô tả hình ảnh ở đây

Bạn có thể hiểu ở trên có nghĩa là bao gồm khóa chính trong mọi chỉ mục và xác định duy nhất là A Good Thing ™ vì dù sao đó cũng là những gì xảy ra. Tôi sẽ không đưa ra giả định đó và sẽ đề nghị chỉ xác định một chỉ mục là duy nhất nếu trên thực tế dữ liệu tự nhiên trong chỉ mục là duy nhất.

Có một số tài nguyên tuyệt vời trong Interwebz về chủ đề này, bao gồm:

FYI, sự hiện diện đơn thuần của một identitycột không đảm bảo tính duy nhất. Bạn cần xác định cột là khóa chính hoặc với một ràng buộc duy nhất để đảm bảo các giá trị được lưu trữ trong cột đó thực tế là duy nhất. Câu SET IDENTITY_INSERT schema.table ON;lệnh sẽ cho phép bạn chèn vào các giá trị không duy nhất vào một cột được xác định là identity.


5

Chỉ là một phần bổ sung cho câu trả lời tuyệt vời của Max .

Khi nói đến việc tạo một chỉ mục cụm không duy nhất, SQL Server tạo ra một cái gì đó được gọi là Uniquifierdù sao trong nền.

Điều này Uniquifiercó thể gây ra sự cố tiềm ẩn trong tương lai nếu nền tảng của bạn có nhiều hoạt động CRUD, vì đây Uniquifierchỉ là 4 byte lớn (số nguyên 32 bit cơ bản). Vì vậy, nếu hệ thống của bạn có nhiều thao tác CRUD, có thể bạn sẽ sử dụng hết tất cả các số duy nhất có sẵn và đột nhiên bạn sẽ gặp lỗi và nó sẽ không cho phép bạn chèn dữ liệu nữa vào các bảng của mình (vì nó sẽ không còn có bất kỳ giá trị duy nhất nào để gán cho các hàng mới được chèn của bạn).

Khi điều này xảy ra, bạn sẽ nhận được lỗi này:

The maximum system-generated unique value for a duplicate group 
was exceeded for index with partition ID (someID). 

Dropping and re-creating the index may resolve this;
otherwise, use another clustering key.

Lỗi 666 (lỗi trên) xảy ra khi uniquifiermột bộ khóa không duy nhất tiêu thụ hơn 2.147.483.647 hàng.

Vì vậy, bạn sẽ cần có ~ 2 tỷ hàng cho một giá trị khóa duy nhất hoặc bạn sẽ cần sửa đổi một giá trị khóa duy nhất ~ 2 tỷ lần để thấy lỗi này. Như vậy, không có khả năng cao bạn sẽ gặp phải giới hạn này.


Tôi không biết rằng trình duy nhất ẩn có thể hết dung lượng khóa, nhưng tôi đoán tất cả mọi thứ đều bị giới hạn trong một số trường hợp. Giống như cách CaseIfcấu trúc được giới hạn ở 10 cấp độ, điều đó có ý nghĩa rằng cũng có giới hạn để giải quyết các thực thể không duy nhất. Theo tuyên bố của bạn, điều này nghe có vẻ như chỉ áp dụng cho các trường hợp khi khóa phân cụm không phải là duy nhất. Đây có phải là vấn đề đối với một Nonclustered Indexhoặc nếu khóa phân cụm Uniquethì không có vấn đề gì đối với Nonclusteredcác chỉ mục?
Solonotix

Một chỉ mục duy nhất là (theo như tôi biết) bị giới hạn bởi kích thước của loại cột (vì vậy nếu đó là loại BIGINT, bạn có 8byte để làm việc). Ngoài ra, theo tài liệu chính thức của microsoft, có tối đa 900byte được phép cho một chỉ mục được phân cụm và 1700byte cho không phân cụm (vì bạn có thể có nhiều hơn một chỉ mục không được phân cụm và chỉ có 1 chỉ mục được phân cụm trên mỗi bảng). docs.microsoft.com/en-us/sql/sql-server/ từ
Chessbrain

1
@Solonotix - trình duy nhất từ chỉ mục được nhóm được sử dụng trong các chỉ mục không được phân cụm. Nếu bạn chạy mã trong ví dụ của tôi mà không có khóa chính (thay vào đó hãy tạo một chỉ mục được nhóm), bạn có thể thấy đầu ra giống nhau cho cả các chỉ mục không duy nhất và duy nhất.
Max Vernon

-2

Tôi sẽ không cân nhắc về câu hỏi liệu một chỉ số có nên là duy nhất hay không, và liệu có nhiều chi phí hơn trong phương pháp này hay không. Nhưng một vài điều làm phiền tôi trong thiết kế chung của bạn

  1. dt datetime không null mặc định (current_timestamp). Datetime là một hình thức cũ hơn hoặc này và bạn có thể đạt được ít nhất một số tiết kiệm không gian bằng cách sử dụng datetime2 () và sysdatetime ().
  2. tạo chỉ mục [nonunique_nonclustered_example] trên #test_index (is_delatted) bao gồm (val). Điều này làm phiền tôi. Hãy xem cách truy cập dữ liệu (Tôi đang cá cược nhiều hơn WHERE is_deleted = 0) và xem xét sử dụng một chỉ mục được lọc. Tôi thậm chí sẽ xem xét sử dụng 2 chỉ mục được lọc, một cho where is_deleted = 0và một chowhere is_deleted = 1

Về cơ bản, điều này trông giống như một bài tập mã hóa được thiết kế để kiểm tra một giả thuyết hơn là một vấn đề / giải pháp thực sự, nhưng hai mẫu đó chắc chắn là thứ tôi tìm kiếm trong các bài đánh giá mã.


Phần lớn bạn sẽ tiết kiệm được khi sử dụng datetime2 thay vì datetime là 1 byte và đó là nếu độ chính xác của bạn nhỏ hơn 3, điều đó có nghĩa là mất độ chính xác trên giây phân số, không phải lúc nào cũng là giải pháp khả thi. Đối với chỉ số ví dụ được cung cấp, thiết kế được giữ đơn giản để tập trung vào câu hỏi của tôi. Một Nonclusteredchỉ mục sẽ có khóa phân cụm được gắn vào cuối hàng dữ liệu để tra cứu khóa trong nội bộ. Như vậy, hai chỉ số là giống nhau về thể chất, đó là điểm của câu hỏi của tôi.
Solonotix

Ở quy mô, chúng tôi chạy ở mức tiết kiệm một hoặc hai byte một cách nhanh chóng. Và tôi đã giả định rằng vì bạn đang sử dụng datetime không chính xác, chúng tôi có thể giảm độ chính xác. Đối với các chỉ mục, một lần nữa tôi sẽ nói rằng các cột bit là các cột dẫn trên các chỉ mục là một mẫu mà tôi coi là một lựa chọn kém. Như với tất cả mọi thứ, số dặm của bạn có thể thay đổi. Than ôi những nhược điểm của một mô hình gần đúng.
Toby

-4

Có vẻ như bạn chỉ đơn giản sử dụng PK để tạo một chỉ số thay thế, nhỏ hơn. Do đó, hiệu suất trên nó là nhanh hơn.

Bạn thấy điều này tại các công ty có bảng dữ liệu lớn (ví dụ: bảng dữ liệu chủ). Ai đó quyết định có một chỉ số phân cụm lớn trên đó hy vọng nó sẽ đáp ứng nhu cầu của các nhóm báo cáo khác nhau.

Nhưng, một nhóm có thể chỉ cần một vài phần của chỉ mục đó trong khi một nhóm khác cần các phần khác .. vì vậy chỉ mục này chỉ vỗ vào mỗi cột dưới ánh mặt trời để "tối ưu hóa hiệu suất" không thực sự hữu ích.

Trong khi đó, phá vỡ nó để tạo ra nhiều chỉ số được nhắm mục tiêu, nhỏ hơn, thường giải quyết vấn đề.

Và, đó dường như là những gì bạn đang làm. Bạn có chỉ mục được phân cụm lớn này với hiệu suất khủng khiếp, sau đó bạn đang sử dụng PK để tạo một chỉ mục khác có ít cột hơn (không có gì bất ngờ) có hiệu suất tốt hơn.

Vì vậy, chỉ cần thực hiện một phân tích và tìm hiểu xem bạn có thể lấy chỉ mục cụm đơn và chia nó thành các chỉ số nhỏ hơn, được nhắm mục tiêu mà các công việc cụ thể cần.

Sau đó, bạn sẽ phải phân tích hiệu suất từ ​​quan điểm "một chỉ số so với nhiều chỉ mục", bởi vì có các chỉ số trong việc tạo và cập nhật. Nhưng, bạn phải phân tích điều này từ một quan điểm tổng thể.

EG: có thể ít sử dụng tài nguyên hơn cho một chỉ số phân cụm lớn và cần nhiều tài nguyên hơn để có một số chỉ số được nhắm mục tiêu nhỏ hơn. Nhưng, nếu sau đó bạn có thể chạy các truy vấn được nhắm mục tiêu ở phía sau nhanh hơn nhiều, tiết kiệm thời gian (và tiền bạc) ở đó, điều đó có thể đáng giá.

Vì vậy, bạn phải thực hiện phân tích từ đầu đến cuối .. không chỉ nhìn vào cách nó tác động đến thế giới của riêng bạn mà còn cả cách nó tác động đến người dùng cuối.

Tôi chỉ cảm thấy như bạn đang sử dụng sai định danh PK. Tuy nhiên, bạn có thể đang sử dụng một hệ thống cơ sở dữ liệu chỉ cho phép 1 chỉ mục (?), Nhưng bạn có thể lẻn vào một hệ thống khác nếu bạn PK (b / c mọi hệ thống cơ sở dữ liệu quan hệ ngày nay dường như tự động lập chỉ mục PK). Tuy nhiên, hầu hết các RDBMS hiện đại 'nên cho phép tạo nhiều chỉ mục; không nên có giới hạn về số lượng chỉ mục bạn có thể thực hiện (trái ngược với giới hạn 1 PK).

Vì vậy, bằng cách tạo một PK mà chỉ hoạt động như một chỉ số alt .. bạn đang sử dụng hết PK của mình, điều này có thể cần thiết nếu bảng sau đó được mở rộng trong vai trò của nó.

Điều đó không có nghĩa là bảng của bạn không cần PK .. 101 DB của DB nói rằng "mỗi bảng nên có PK". Nhưng, trong tình huống lưu trữ dữ liệu hoặc đại loại như vậy .. có PK trên bàn có thể chỉ là chi phí phụ mà bạn không cần. Hoặc, đó có thể là một sự gửi gắm để đảm bảo bạn không thêm hai mục nhập vào. Đó thực sự là vấn đề của những gì bạn đang làm và tại sao bạn làm điều đó.

Nhưng, các bảng lớn chắc chắn được hưởng lợi từ việc có các chỉ mục. Nhưng, giả sử một chỉ số phân cụm lớn duy nhất sẽ tốt nhất chỉ là ... nó có thể là tốt nhất .. nhưng tôi khuyên bạn nên thử nghiệm trên một thử nghiệm env phá vỡ chỉ mục thành nhiều chỉ số nhỏ hơn nhắm vào các tình huống sử dụng cụ thể.

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.