인덱스 고유성 오버 헤드


14

인덱스 비용 및 고유성이 유익한 지 아닌지 (아마도 둘 다)에 대해 사무실의 여러 개발자와 토론을 진행하고 있습니다. 문제의 핵심은 경쟁 리소스입니다.

배경

나는 이전에 언급 한 토론을 읽었습니다. Unique 인덱스가 유지 관리에 추가 비용이 들지 않는다는Insert 작업이 B- 트리에 맞는 위치를 암시 적으로 확인하고 고유하지 않은 인덱스에서 중복이 발견되면 유니티를 추가하기 때문에 키의 끝이지만 직접 삽입합니다. 이 일련의 이벤트에서 Unique인덱스는 추가 비용이 없습니다.

저의 동료 Unique는 B- 트리에서 새로운 위치를 찾은 후 두 번째 작업으로 시행되므로 고유하지 않은 인덱스보다 유지 관리 비용이 더 많이 든다는 말을 통해이 진술에 맞서고 있습니다.

최악의 경우, 테이블의 클러스터링 키인 식별 컬럼 (내재적으로 고유)이있는 테이블을 보았지만 고유하지 않은 것으로 명시되었습니다. 최악의 다른 측면에는 고유성에 대한 집착이 있으며 모든 인덱스는 고유 한 것으로 생성되며 인덱스와 명시 적으로 고유 한 관계를 정의 할 수없는 경우 인덱스의 끝에 테이블의 PK를 추가하여 독창성이 보장됩니다.

개발자 팀의 코드 검토에 자주 참여하며 따라야 할 일반적인 지침을 제공 할 수 있어야합니다. 예, 모든 인덱스를 평가해야하지만 테이블에 각각 수천 개의 테이블이 있고 5 개의 서버가 있고 20 개의 인덱스가있는 경우 특정 수준의 품질을 보장하기 위해 몇 가지 간단한 규칙을 적용 할 수 있어야합니다.

질문

고유성이 Insert아닌 인덱스를 유지하는 비용과 비교하여 백엔드에 추가 비용이 있습니까? 둘째, 고유성을 보장하기 위해 인덱스 끝에 테이블의 기본 키를 추가하는 데 어떤 문제가 있습니까?

테이블 정의 예

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

Unique인덱스 끝에 키를 추가하는 이유 는 팩트 테이블 중 하나에 있습니다. 있다 Primary Key입니다 Identity열입니다. 그러나 Clustered Index대신 파티션 구성표 열 뒤에 고유성이없는 3 개의 외래 키 차원이옵니다. 이 테이블의 선택 성능은 끔찍하며을 Primary Key활용하는 대신 키 조회를 사용하여 검색 시간을 늘리는 것이 좋습니다 Clustered Index. 비슷한 디자인을 따르지만 Primary Key끝에 추가 된 다른 테이블의 성능은 상당히 향상되었습니다.

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

답변:


16

개발자 팀의 코드 검토에 자주 참여하며 따라야 할 일반적인 지침을 제공 할 수 있어야합니다.

현재 관련된 환경에는 2500 개의 데이터베이스가있는 250 개의 서버가 있습니다. 나는 30,000 개의 데이터베이스를 가진 시스템에서 일했다 . 색인에 대한 지침은 색인에 포함 할 열에 대한 "규칙"이 아니라 명명 규칙 등을 중심으로 이루어져야합니다. 모든 개별 색인은 특정 비즈니스 규칙 또는 테이블을 터치하는 코드에 대한 올바른 색인이되도록 설계되어야합니다.

고유성이 Insert아닌 인덱스를 유지하는 비용과 비교하여 백엔드에 추가 비용이 있습니까? 둘째, 고유성을 보장하기 위해 인덱스 끝에 테이블의 기본 키를 추가하는 데 어떤 문제가 있습니까?

고유하지 않은 인덱스를 기본 키 열 끝에 추가하면 안티 패턴으로 보입니다. 비즈니스 규칙에 따라 데이터가 고유해야한다면 열에 고유 제한 조건을 추가하십시오. 고유 색인을 자동으로 생성합니다. 성능을 위해 인덱싱하는 경우 인덱스에 열 추가하는 이유는 무엇입니까?

고유성을 적용한다고해서 추가 오버 헤드가 발생하지 않는다고 가정하더라도 ( 특정 경우에는 맞지 않음 ) 인덱스를 불필요하게 복잡하게하여 무엇을 해결하고 있습니까?

인덱스 정의에 UNIQUE수정자를 포함시킬 수 있도록 인덱스 키 끝에 기본 키를 추가하는 특정 인스턴스에서는 실제로 디스크의 실제 인덱스 구조와 전혀 차이가 없습니다. 이는 B- 트리 인덱스 키 구조의 특성으로 인해 항상 고유해야하기 때문입니다.

데이비드 브라운은 코멘트에 언급 :

비 클러스터형 인덱스는 모두 고유 인덱스로 저장되므로 고유 인덱스 에 삽입하는 데 추가 비용 이 들지 않습니다 . 실제로 추가 키로 후보 키를 고유 인덱스로 선언 하지 못하면 클러스터 된 인덱스 키가 인덱스 키에 추가됩니다.

최소한의 완전하고 검증 가능한 다음 예를 보자 .

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

두 번째 인덱스 키 정의의 꼬리 끝에 기본 키를 추가하는 것을 제외하고 동일한 두 개의 인덱스를 추가하겠습니다.

CREATE INDEX IndexTest_rowDate_ix01
ON dbo.IndexTest(rowDate);

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

다음으로 테이블에 몇 개의 행을 보겠습니다.

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

위에서 볼 수 있듯이 세 개의 행에는 동일한 rowDate열 값이 있고 두 개의 행에는 고유 한 값이 있습니다.

다음으로 문서화되지 않은 DBCC PAGE명령을 사용하여 각 색인의 실제 페이지 구조를 살펴 보겠습니다 .

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 *****************************************';

Beyond Compare를 사용하여 출력을 살펴 보았고 할당 페이지 ID 등의 명백한 차이점을 제외하고 두 인덱스 구조는 동일합니다.

여기에 이미지 설명을 입력하십시오

모든 인덱스에 기본 키를 포함하고 고유 한 것으로 정의하는 것이 A Good Thing ™이라는 것을 의미하기 위해 위의 내용을 취할 수 있습니다. 나는 그 가정을하지 않을 것이며 실제로 인덱스의 자연 데이터가 이미 고유 한 경우 인덱스를 고유 한 것으로 정의하는 것이 좋습니다.

Interwebz에는이 주제와 관련하여 다음과 같은 훌륭한 자료가 있습니다.

참고로, identity컬럼의 존재만으로 고유성을 보장 하지는 않습니다 . 열을 기본 키로 정의하거나 해당 열에 저장된 값이 실제로 고유 하도록 고유 제한 조건을 정의 해야 합니다. 이 SET IDENTITY_INSERT schema.table ON;문을 통해 고유하지 않은 값을로 정의 된 열에 삽입 할 수 있습니다 identity.


5

Max의 우수 답변 에 대한 추가 기능입니다 .

고유하지 않은 클러스터형 인덱스를 만들 때 SQL Server Uniquifier는 어쨌든 백그라운드에서 무언가를 만듭니다 .

Uniquifier플랫폼은 CRUD 작업을 많이 가지고있는 경우이 있기 때문에, 미래에 잠재적 인 문제를 일으킬 수있는 Uniquifier큰 단지 4 바이트 (기본 32 비트 정수)입니다. 따라서 시스템에 CRUD 작업이 많은 경우 사용 가능한 모든 고유 번호를 모두 사용할 수 있으며 갑자기 오류가 발생하여 더 이상 데이터를 테이블에 삽입 할 수 없습니다 ( 더 이상 새로 삽입 된 행에 할당 할 고유 한 값이 없습니다).

이 경우 다음과 같은 오류가 발생합니다.

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.

uniquifier고유하지 않은 단일 키 집합에 대해 2,147,483,647 개가 넘는 행을 사용하는 경우 오류 666 (위의 오류)이 발생합니다 .

따라서 단일 키 값에 대해 ~ 20 억 행이 있거나이 오류를 보려면 20 억 번 단일 키 값을 수정해야합니다. 따라서이 제한에 부딪 칠 가능성은 거의 없습니다.


숨겨진 uniquifier가 키 공간을 잃을 수 있다는 것을 몰랐지만 모든 경우에 제한이 있다고 생각합니다. 방법 CaseIf구조가 10 레벨로 제한되는 것과 마찬가지로 고유하지 않은 엔터티를 해결하는 데 제한이 있다는 것도 의미가 있습니다. 귀하의 진술에 따르면, 이는 클러스터링 키가 고유하지 않은 경우에만 적용되는 것처럼 들립니다. 이것이 Nonclustered Index클러스터링 키 Unique에 문제가 Nonclustered있습니까, 아니면 인덱스에 문제가 없습니까?
Solonotix

고유 인덱스는 열 유형의 크기에 의해 제한됩니다 (따라서 BIGINT 유형의 경우 8 바이트가 필요합니다). 또한 Microsoft의 공식 문서에 따르면 클러스터형 인덱스에는 최대 900 바이트, 비 클러스터형에는 1700 바이트가 허용됩니다 (테이블 당 하나 이상의 비 클러스터형 인덱스와 하나의 클러스터형 인덱스 만 가질 수 있으므로). docs.microsoft.com/en-us/sql/sql-server/…
Chessbrain

1
@Solonotix- 클러스터형 인덱스 의 단일화자가 비 클러스터형 인덱스 에 사용됩니다. 예제에서 기본 키없이 코드를 실행하면 (클러스터형 인덱스 생성) 출력이 고유하지 않은 인덱스와 고유 한 인덱스 모두에 대해 동일하다는 것을 알 수 있습니다.
Max Vernon

-2

인덱스가 고유해야하는지 여부와이 방법에 더 많은 오버 헤드가 있는지 여부에 대해서는 다루지 않겠습니다. 그러나 몇 가지 것들이 일반적인 디자인에서 나를 괴롭 혔습니다.

  1. dt datetime not null 기본값 (current_timestamp). Datetime은 이전 형식이거나 datetime2 () 및 sysdatetime ()을 사용하여 최소한의 공간을 절약 할 수 있습니다.
  2. #test_index에 인덱스 [nonunique_nonclustered_example]을 만듭니다 (is_deleted) include (val). 이것은 나를 귀찮게합니다. 데이터에 액세스하는 방법을 살펴보고 (개 이상이 있음 WHERE is_deleted = 0) 베팅 된 인덱스를 사용하는 방법을 살펴보십시오. 필터링 된 인덱스 2 개를 사용하는 것도 고려할 것 where is_deleted = 0입니다.where is_deleted = 1

근본적으로 이것은 실제 문제 / 솔루션이 아니라 가설을 테스트하기 위해 설계된 코딩 연습과 비슷하지만이 두 패턴은 코드 검토에서 분명히 찾고 있습니다.


datetime 대신 datetime2를 사용하여 절약 할 수있는 대부분은 1 바이트이며, 정밀도가 3보다 작은 경우 분수 초에 정밀도를 잃어 버릴 수 있습니다. 항상 실행 가능한 솔루션은 아닙니다. 제공된 예제 색인과 관련하여 디자인은 내 질문에 집중하기 위해 단순하게 유지되었습니다. Nonclustered지수는 내부적으로 키 조회에 대한 데이터 행의 끝에 추가 클러스터링 키를해야합니다. 따라서 두 지수는 물리적으로 동일하므로 내 질문의 요점입니다.
Solonotix

규모에서 우리는 한두 바이트를 절약 할 때 빠르게 누적됩니다. 그리고 나는 당신이 부정확 한 날짜 시간을 사용했기 때문에 정밀도를 줄일 수 있다고 가정했습니다. 인덱스의 경우 다시 비트 열을 인덱스의 리드 열로 선택하는 것이 좋지 않은 패턴이라고 언급하겠습니다. 모든 것과 마찬가지로 마일리지가 다를 수 있습니다. 근사 모델의 단점을 아아.
Toby

-4

대체로 작은 인덱스를 만들기 위해 PK를 사용하는 것처럼 보입니다. 따라서 성능이 더 빠릅니다.

대규모 데이터 테이블이있는 회사 (예 : 마스터 데이터 테이블)에서이 사실을 알 수 있습니다. 누군가는 다양한보고 그룹의 요구를 충족시킬 것으로 예상되는 하나의 대규모 클러스터형 인덱스를 갖기로 결정합니다.

그러나 한 그룹에는 해당 인덱스의 일부만 필요할 수도 있고 다른 그룹에는 다른 파트가 필요할 수도 있습니다. 따라서 "성능을 최적화"하기 위해 태양 아래 모든 열에 두드리는 인덱스는 실제로 도움이되지 않습니다.

한편, 여러 개의 작고 표적화 된 지수를 만들기 위해 분류하는 것은 종종 문제를 해결합니다.

그리고 그것은 당신이하고있는 것 같습니다. 이 거대한 클러스터형 인덱스는 끔찍한 성능을 가지고 있으며 PK를 사용하여 더 적은 수의 열을 가진 또 다른 인덱스를 생성하여 놀라운 성능을 제공합니다.

따라서 분석을 수행하고 단일 클러스터형 인덱스를 가져와 특정 작업에 필요한 더 작은 목표 인덱스로 분류 할 수 있는지 알아보십시오.

인덱스를 만들고 업데이트하는 데 오버 헤드가 있기 때문에 "단일 인덱스 대 다중 인덱스"관점에서 성능을 분석해야합니다. 그러나 전체적인 관점에서이를 분석해야합니다.

EG : 하나의 대규모 클러스터형 인덱스에 비해 리소스를 많이 사용하지 않을 수도 있고 여러 개의 작은 대상 인덱스를 갖기 위해 리소스를 많이 사용할 수도 있습니다. 그러나 백엔드에서 대상 쿼리를 훨씬 빠르게 실행하여 시간과 비용을 절약 할 수 있다면 그만한 가치가 있습니다.

따라서 엔드-투-엔드 분석을 수행해야합니다. 분석이 자신의 세계에 미치는 영향뿐만 아니라 최종 사용자에게 미치는 영향도 살펴보십시오.

PK 식별자를 잘못 사용하고 있다고 생각합니다. 그러나 1 개의 인덱스 (?) 만 허용하는 데이터베이스 시스템을 사용하고있을 수 있지만 PK를 사용하는 경우 다른 인덱스를 몰래 숨길 수 있습니다 (요즘 모든 관계형 데이터베이스 시스템에서 b / c는 PK를 자동으로 인덱스하는 것으로 보입니다). 그러나 대부분의 최신 RDBMS는 다중 인덱스 작성을 허용해야합니다. 만들 수있는 인덱스 수에는 제한이 없어야합니다 (1 PK 제한과 반대).

따라서 PK whicih를 만들면 고도 색인처럼 작동합니다. PK를 다 사용하고 있습니다. PK는 나중에 테이블이 역할을 확장 할 때 필요할 수 있습니다.

SOP DB (101)는 "모든 테이블에는 PK가 있어야한다"고 말합니다. 그러나 데이터웨어 하우징 상황 등에서 테이블에 PK를 두는 것은 불필요한 오버 헤드 일 수 있습니다. 또는 속임수 항목을 두 번 추가하지 않도록 신에게 보낼 수도 있습니다. 그것은 실제로 당신이하고있는 일과 왜하고 있는지의 문제입니다.

그러나 방대한 테이블은 인덱스를 사용하면 이점이 있습니다. 그러나 하나의 대규모 클러스터형 인덱스가 최고라고 가정하는 것이 가장 좋습니다 ... 최고가 될 수 있습니다. 그러나 특정 유스 케이스 시나리오를 대상으로 인덱스를 여러 개의 작은 인덱스로 구분하는 테스트 환경에서 테스트하는 것이 좋습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.