자연 키는 대리 정수 키보다 SQL Server에서 더 높거나 낮은 성능을 제공합니까?


25

나는 대리 키의 팬입니다. 내 결과가 확인 편향 될 위험이 있습니다.

여기와 http://stackoverflow.com 에서 본 많은 질문 은 IDENTITY()값을 기반으로하는 대리 키 대신 자연 키를 사용 합니다.

컴퓨터 시스템에 대한 나의 배경은 정수에 대한 비교 작업을 수행하는 것이 문자열을 비교하는 것보다 빠릅니다.

의견은 저의 신념에 의문을 제기했기 때문에 SQL Server에서 키로 사용하기 위해 정수가 문자열보다 빠르다는 논문을 조사하는 시스템을 만들 것이라고 생각했습니다.

작은 데이터 집합에는 눈에 띄지 않는 차이가 거의 없기 때문에 기본 테이블에 1,000,000 개의 행이 있고 보조 테이블에 기본 테이블의 각 행에 대해 10 만 개의 행이있는 2 개의 테이블 설정을 즉시 생각했습니다. 보조 테이블. 내 테스트의 전제는 자연 키를 사용하는 것과 정수 키를 사용하는 것과 같은 두 가지 테이블 세트를 만들고 다음과 같은 간단한 쿼리에서 타이밍 테스트를 실행하는 것입니다.

SELECT *
FROM Table1
    INNER JOIN Table2 ON Table1.Key = Table2.Key;

다음은 테스트 베드로 만든 코드입니다.

USE Master;
IF (SELECT COUNT(database_id) FROM sys.databases d WHERE d.name = 'NaturalKeyTest') = 1
BEGIN
    ALTER DATABASE NaturalKeyTest SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
    DROP DATABASE NaturalKeyTest;
END
GO
CREATE DATABASE NaturalKeyTest 
    ON (NAME = 'NaturalKeyTest', FILENAME = 
        'C:\SQLServer\Data\NaturalKeyTest.mdf', SIZE=8GB, FILEGROWTH=1GB) 
    LOG ON (NAME='NaturalKeyTestLog', FILENAME = 
        'C:\SQLServer\Logs\NaturalKeyTest.mdf', SIZE=256MB, FILEGROWTH=128MB);
GO
ALTER DATABASE NaturalKeyTest SET RECOVERY SIMPLE;
GO
USE NaturalKeyTest;
GO
CREATE VIEW GetRand
AS 
    SELECT RAND() AS RandomNumber;
GO
CREATE FUNCTION RandomString
(
    @StringLength INT
)
RETURNS NVARCHAR(max)
AS
BEGIN
    DECLARE @cnt INT = 0
    DECLARE @str NVARCHAR(MAX) = '';
    DECLARE @RandomNum FLOAT = 0;
    WHILE @cnt < @StringLength
    BEGIN
        SELECT @RandomNum = RandomNumber
        FROM GetRand;
        SET @str = @str + CAST(CHAR((@RandomNum * 64.) + 32) AS NVARCHAR(MAX)); 
        SET @cnt = @cnt + 1;
    END
    RETURN @str;
END;
GO
CREATE TABLE NaturalTable1
(
    NaturalTable1Key NVARCHAR(255) NOT NULL 
        CONSTRAINT PK_NaturalTable1 PRIMARY KEY CLUSTERED 
    , Table1TestData NVARCHAR(255) NOT NULL 
);
CREATE TABLE NaturalTable2
(
    NaturalTable2Key NVARCHAR(255) NOT NULL 
        CONSTRAINT PK_NaturalTable2 PRIMARY KEY CLUSTERED 
    , NaturalTable1Key NVARCHAR(255) NOT NULL 
        CONSTRAINT FK_NaturalTable2_NaturalTable1Key 
        FOREIGN KEY REFERENCES dbo.NaturalTable1 (NaturalTable1Key) 
        ON DELETE CASCADE ON UPDATE CASCADE
    , Table2TestData NVARCHAR(255) NOT NULL  
);
GO

/* insert 1,000,000 rows into NaturalTable1 */
INSERT INTO NaturalTable1 (NaturalTable1Key, Table1TestData) 
    VALUES (dbo.RandomString(25), dbo.RandomString(100));
GO 1000000 

/* insert 10,000,000 rows into NaturalTable2 */
INSERT INTO NaturalTable2 (NaturalTable2Key, NaturalTable1Key, Table2TestData)
SELECT dbo.RandomString(25), T1.NaturalTable1Key, dbo.RandomString(100)
FROM NaturalTable1 T1
GO 10 

CREATE TABLE IDTable1
(
    IDTable1Key INT NOT NULL CONSTRAINT PK_IDTable1 
    PRIMARY KEY CLUSTERED IDENTITY(1,1)
    , Table1TestData NVARCHAR(255) NOT NULL 
    CONSTRAINT DF_IDTable1_TestData DEFAULT dbo.RandomString(100)
);
CREATE TABLE IDTable2
(
    IDTable2Key INT NOT NULL CONSTRAINT PK_IDTable2 
        PRIMARY KEY CLUSTERED IDENTITY(1,1)
    , IDTable1Key INT NOT NULL 
        CONSTRAINT FK_IDTable2_IDTable1Key FOREIGN KEY 
        REFERENCES dbo.IDTable1 (IDTable1Key) 
        ON DELETE CASCADE ON UPDATE CASCADE
    , Table2TestData NVARCHAR(255) NOT NULL 
        CONSTRAINT DF_IDTable2_TestData DEFAULT dbo.RandomString(100)
);
GO
INSERT INTO IDTable1 DEFAULT VALUES;
GO 1000000
INSERT INTO IDTable2 (IDTable1Key)
SELECT T1.IDTable1Key
FROM IDTable1 T1
GO 10

위의 코드는 데이터베이스와 4 개의 테이블을 만들고 테스트 할 준비가 된 데이터로 테이블을 채 웁니다. 내가 실행 한 테스트 코드는 다음과 같습니다.

USE NaturalKeyTest;
GO
DECLARE @loops INT = 0;
DECLARE @MaxLoops INT = 10;
DECLARE @Results TABLE (
    FinishedAt DATETIME DEFAULT (GETDATE())
    , KeyType NVARCHAR(255)
    , ElapsedTime FLOAT
);
WHILE @loops < @MaxLoops
BEGIN
    DBCC FREEPROCCACHE;
    DBCC FREESESSIONCACHE;
    DBCC FREESYSTEMCACHE ('ALL');
    DBCC DROPCLEANBUFFERS;
    WAITFOR DELAY '00:00:05';
    DECLARE @start DATETIME = GETDATE();
    DECLARE @end DATETIME;
    DECLARE @count INT;
    SELECT @count = COUNT(*) 
    FROM dbo.NaturalTable1 T1
        INNER JOIN dbo.NaturalTable2 T2 ON T1.NaturalTable1Key = T2.NaturalTable1Key;
    SET @end = GETDATE();
    INSERT INTO @Results (KeyType, ElapsedTime)
    SELECT 'Natural PK' AS KeyType, CAST((@end - @start) AS FLOAT) AS ElapsedTime;

    DBCC FREEPROCCACHE;
    DBCC FREESESSIONCACHE;
    DBCC FREESYSTEMCACHE ('ALL');
    DBCC DROPCLEANBUFFERS;
    WAITFOR DELAY '00:00:05';
    SET @start = GETDATE();
    SELECT @count = COUNT(*) 
    FROM dbo.IDTable1 T1
        INNER JOIN dbo.IDTable2 T2 ON T1.IDTable1Key = T2.IDTable1Key;
    SET @end = GETDATE();
    INSERT INTO @Results (KeyType, ElapsedTime)
    SELECT 'IDENTITY() PK' AS KeyType, CAST((@end - @start) AS FLOAT) AS ElapsedTime;

    SET @loops = @loops + 1;
END
SELECT KeyType, FORMAT(CAST(AVG(ElapsedTime) AS DATETIME), 'HH:mm:ss.fff') AS AvgTime 
FROM @Results
GROUP BY KeyType;

결과는 다음과 같습니다.

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

여기에 잘못된 것이 있습니까, 아니면 INT 키가 25 문자의 자연 키보다 3 배 빠릅니다.

여기에 후속 질문을 작성했습니다 .


1
INT는 4 바이트이고 유효 NVARCHAR (25)는 길이와 같은 시스템 데이터를 포함하여 약 14 배 더 길기 때문에 인덱스만으로도 훨씬 넓고 깊은 PK 인덱스를 가질 수 있다고 생각합니다. 처리 시간에 영향을주는 / O가 필요합니다. Howevev 자연 정수 (아마도 체크 숫자로 표시)는 대리 ID 열에 사용하는 것과 거의 동일한 INT입니다. 따라서 "자연 키"는 INT, BIGINT, CHAR, NVARCHAR 등 모든 것이 중요합니다.
RLF

7
@ MikeSherrill'Catcall '의 성능 향상은 자연 키를 사용할 때 실제로 "lookup"테이블에 대한 조인이 필요하지 않다는 것입니다. 쿼리를 비교하여 값이 기본 테이블에 이미 저장된 쿼리와 조인으로 조회 값을 가져옵니다. 조회 테이블의 자연 키 길이와 행 수에 따라 다른 "우승자"가 표시 될 수 있습니다.
Mikael Eriksson

3
@MikaelEriksson이 말한 것 외에도 2 개 이상의 테이블 (예 : 4) 사이에 조인이있는 경우 (대리인을 사용하면 A와 D를 통해 B와 C를 통해 테이블 ​​A를 조인해야하며 자연 키를 사용하면 A와 D를 직접 조인 할 수 있음)
ypercubeᵀᴹ

답변:


18

일반적으로 SQL Server는 인덱스에 B + Tree 를 사용합니다. 인덱스 검색 비용은이 스토리지 형식의 키 길이와 직접 관련이 있습니다. 따라서 대리 키는 일반적으로 인덱스 탐색에서 자연 키보다 성능이 뛰어납니다.

SQL Server는 기본적으로 기본 키에 테이블을 클러스터링합니다. 클러스터형 인덱스 키는 행을 식별하는 데 사용되므로 다른 모든 인덱스에 포함 된 열로 추가됩니다. 해당 키가 넓을수록 모든 보조 인덱스가 커집니다.

더 나쁜 것은 보조 인덱스가 명시 적으로 정의되지 않은 UNIQUE경우 클러스터 된 인덱스 키가 자동으로 각 인덱스의 일부가됩니다. 일반적으로 인덱스는 요구 사항이 고유성을 적용해야하는 경우에만 고유 한 것으로 선언되므로 대부분의 인덱스에 적용됩니다.

따라서 자연 대 대리 클러스터 인덱스에 대한 질문이라면 대리자가 거의 항상 이길 것입니다.

반면에, 대리 열을 테이블에 추가하여 테이블 자체를 더 크게 만듭니다. 이로 인해 클러스터형 인덱스 스캔이 더 비싸 질 수 있습니다. 따라서 보조 인덱스가 매우 적고 작업 부하에서 모든 (또는 대부분의) 행을 자주 확인해야하는 경우 실제로 적은 바이트를 절약하는 자연스러운 키를 사용하는 것이 좋습니다.

마지막으로 자연 키를 사용하면 데이터 모델을 더 쉽게 이해할 수 있습니다. 더 많은 저장 공간을 사용하는 동안 자연 기본 키는 자연 외래 키로 이어지고 로컬 정보 밀도가 증가합니다.

따라서 데이터베이스 세계에서와 마찬가지로 실제 답변은 "의존"입니다. 또한 항상 실제 데이터를 사용하여 자신의 환경에서 테스트하십시오.


10

나는 최고가 중간에 있다고 믿는다 .

자연 키 개요 :

  1. 데이터 모델은 다른 사람의 머리가 아닌 주제 영역에서 나왔기 때문에보다 분명합니다.
  2. 간단한 키 ( CHAR(4)와 열 사이의 하나의 열 CHAR(20))는 여분의 바이트를 절약하지만 일관성을 관찰해야합니다 ( ON UPDATE CASCADE변경 될 수있는 해당 키에 중요 함).
  3. 자연 키가 복잡한 경우가 많을 경우 : 2 개 이상의 열로 구성됩니다. 이러한 키가 다른 키로 이전 키로 마이그레이션 될 경우 데이터 오버 헤드 (표시 및 데이터 열이 커질 수 있음)가 추가되고 성능이 저하됩니다.
  4. key가 큰 문자열 인 경우 단순 검색 조건이 데이터베이스 엔진에서 바이트 배열 비교가되므로 대부분 정수 키로 느슨해집니다. 대부분의 경우 정수 비교보다 느립니다.
  5. 키가 다국어 문자열 인 경우 데이터 정렬도 확인해야합니다.

이점 : 1과 2

감시 : 3, 4 및 5.


인공 신원 키 개요 :

  1. 이 기능은 데이터베이스 엔진에서 처리하므로 생성 및 처리 (대부분의 경우)에 대해서는 신경 쓸 필요가 없습니다. 기본적으로 고유하며 많은 공간을 차지하지 않습니다. ON UPDATE CASCADE키 값이 변경되지 않기 때문에 같은 사용자 지정 작업 이 생략 될 수 있습니다.

  2. 이들은 종종 다음과 같은 이유로 외래 키로 마이그레이션하기에 가장 적합한 후보입니다.

    2.1. 하나의 열로 구성됩니다.

    2.2. 무게가 작고 비교 작업에 빠르게 작용하는 간단한 유형을 사용합니다.

  3. 키가 어디에서나 마이그레이션되지 않는 연결 엔터티의 경우 키가 유용하지 않아 순수한 데이터 오버 헤드가 될 수 있습니다. 복잡한 자연 기본 키 (문자열 열이없는 경우)가 더 유용합니다.

이점 : 1과 2

감시 : 3.


결론:

기능 키는이 기능을 위해 설계되었으므로 유지 관리가 쉽고 안정적이며 빠릅니다. 그러나 어떤 경우에는 필요하지 않습니다. 예를 들어, CHAR(4)대부분의 경우 단일 열 후보는 다음과 같이 동작 INT IDENTITY합니다. 그래서 또 다른 질문은 여기에 있습니다 : 유지 보수의 +의 안정성 또는 명백한 ?

질문 "인공 키를 주입해야합니까?" 항상 자연스러운 키 구조에 따라 다릅니다 .

  • 큰 문자열이 포함 된 경우 다른 엔티티로 외부로 마이그레이션하는 경우 속도가 느려지고 데이터 오버 헤드가 추가됩니다.
  • 여러 열로 구성된 경우 다른 엔티티로 외부로 마이그레이션하는 경우 속도가 느려지고 데이터 오버 헤드가 추가됩니다.

5
"키 값이 변경되지 않기 때문에 ON UPDATE CASCADE와 같은 사용자 정의 작업이 생략 될 수 있습니다." 대리 키의 효과는 모든 외래 키 참조를 "ON UPDATE CASCADE"와 동일하게 만드는 것입니다. 키는 변경되지 않지만,이 나타내는 값은 않습니다 .
Mike Sherrill 'Cat Recall'9

@ MikeSherrill'Catcall '물론입니다. 그러나 ON UPDATE CASCADE키가 업데이트되지 않은 동안에는 사용되지 않습니다. 그러나 이들이 ON UPDATE NO ACTION구성되어 있으면 문제가 될 수 있습니다 . 나는 DBMS가 그것을 사용하지 않고 키 열 값은 변경되지 않았다는 것을 의미합니다.
BlitZ

4

키는 데이터베이스의 논리적 기능이지만 성능은 항상 스토리지의 물리적 구현과 해당 구현에 대해 실행되는 물리적 작업에 의해 결정됩니다. 따라서 성능 특성을 키에 귀속시키는 것은 실수입니다.

그러나이 특정 예에서는 두 가지 가능한 테이블 및 쿼리 구현이 서로 비교됩니다. 이 예에서는 제목에 제기 된 질문에 대한 답변을 제공하지 않습니다. 비교는 하나의 인덱스 유형 (B- 트리)을 사용하는 두 개의 서로 다른 데이터 유형 (정수 및 문자)을 사용하여 조인하는 것입니다. "명백한"포인트는 해시 인덱스 또는 다른 유형의 인덱스가 사용 된 경우 두 구현간에 측정 가능한 성능 차이가 거의 없을 것입니다. 그러나 예제에는 더 근본적인 문제가 있습니다.

두 쿼리는 성능을 비교하지만 두 쿼리는 서로 다른 결과를 반환하기 때문에 논리적으로 동일하지 않습니다! 보다 현실적인 테스트는 동일한 결과를 반환 하지만 다른 구현을 사용 하는 두 쿼리를 비교합니다 .

서로 게이트 키의 핵심은 테이블이 비즈니스 도메인에서 사용되는 "의미있는"키 속성이있는 테이블 의 추가 속성 이라는 것입니다 . 쿼리 결과가 유용하려면 관심이없는 대리 속성입니다. 따라서 실제 테스트에서는 자연 키만 사용하는 테이블과 동일한 테이블에 자연 키 대리 키 가 모두있는 대체 구현을 비교 합니다. 대리 키에는 일반적으로 추가 스토리지 및 인덱싱이 필요하며 정의에 따라 추가 고유성 제약이 필요합니다. 서로 게이트는 외부 자연 키 값을 서로 게이트에 매핑하거나 그 반대로 매핑하려면 추가 처리가 필요합니다.

이제이 잠재적 쿼리를 비교하십시오.

에이.

SELECT t2.NaturalTable2Key, t2.NaturalTable1Key
FROM Table2 t2;

Table2의 NaturalTable1Key 속성이 대리 IDTable1Key로 바뀌면 논리적으로 동일합니다.

비.

SELECT t2.NaturalTable2Key, t1.NaturalTable1Key
FROM Table2 t2
INNER JOIN Table1 t1
ON t1.IDTable1Key = t2.IDTable1Key;

쿼리 B에는 조인이 필요합니다. 쿼리 A는 그렇지 않습니다. 이것은 (대략) 대리 사용하는 데이터베이스에서 익숙한 상황입니다. 쿼리는 불필요하게 복잡해지고 최적화하기가 훨씬 어려워집니다. 비즈니스 로직 (특히 데이터 무결성 제약)은 구현, 테스트 및 검증이 더 어려워집니다.

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