본문 바로가기

RDBMS/SQL Server

조인에 쓰이는 PK를 각각 int, varchar 타입으로 선언했을 때의 성능 차이

커뮤니티에 올라온 질문에 답변을 하다가, 'PK(기본키)는 꼭 int 타입으로 사용해야 하나요?'라는 질문에 대한 답을 하기 위한 간단한 테스트. 다음과 같은 과정을 거쳤다 :

1. int, varchar 컬럼이 하나씩 있는 테이블 t_a, t_b를 생성하고, 한 테이블은 int 컬럼을, 다른 한 테이블은 varchar 컬럼을 기본키로 설정한다.
2. (1,a), (2,b), ... (26, z)의 데이터를 두 테이블에 삽입한다.
3. t_a, t_b 테이블의 기본키에 조인을 거는 테이블 t_a1, t_b1을 생성한다.
   t_a1은 (int(PK), int(FK), varchar), t_b1은 (int(PK), int, varchar(FK)) 형태로 생성한다.
4. 26 x 10만건의 데이터를 t_a1, t_b1 테이블에 삽입한다.
   데이터 형식은 (1, 1, a), (2, 1, a) ... (2600000, 26, z) 형태로 삽입한다.
5. 결과를 살펴본다.

* 테이블 t_c는 t_b와 비슷하나, pk 키가 aaaaaaaaaa 처럼 10개의 문자로 되어 있음

쓰인 SQL 구문은 다음과 같다 :

USE [test]
GO
/****** 개체:  Table [dbo].[t_a]    스크립트 날짜: 09/20/2007 15:58:56 ******/
CREATE TABLE [dbo].[t_a](
    [c_a] [int] IDENTITY(1,1) NOT NULL,
    [c_b] [varchar](50) NOT NULL,
 CONSTRAINT [PK_t_a] PRIMARY KEY CLUSTERED
(
    [c_a] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

CREATE TABLE [dbo].[t_b](
    [c_a] [int] NOT NULL,
    [c_b] [varchar](50) NOT NULL,
 CONSTRAINT [PK_t_b] PRIMARY KEY CLUSTERED
(
    [c_b] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

CREATE TABLE [dbo].[t_c](
    [c_a] [int] NOT NULL,
    [c_b] [varchar](50) NOT NULL,
 CONSTRAINT [PK_t_b] PRIMARY KEY CLUSTERED
(
    [c_b] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

-- t_a에 데이터 삽입
Declare @a as int;
SET @a = 0;
WHILE @a < 26
BEGIN
    INSERT INTO t_a (c_b)
        select char(ascii('a')+@a)
    SET @a = @a + 1;
END

-- t_b에 데이터 삽입
INSERT INTO t_b (c_a, c_b)
    SELECT c_a, c_b from t_a;

-- t_c에 데이터 삽입
INSERT INTO t_c (c_a, c_b)
    SELECT c_a, c_b+c_b+c_b+c_b+c_b+c_b+c_b+c_b+c_b+c_b from t_a;

CREATE TABLE [dbo].[t_a1](
    [c_a1] [int] IDENTITY(1,1) NOT NULL,
    [c_a] [int] NOT NULL,
    [c_b] [varchar](50) NOT NULL,
 CONSTRAINT [PK_t_a1] PRIMARY KEY CLUSTERED
(
    [c_a1] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
CREATE NONCLUSTERED INDEX [IX_t_a1] ON [dbo].[t_a1]
(
    [c_a] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]

CREATE TABLE [dbo].[t_b1](
    [c_b1] [int] IDENTITY(1,1) NOT NULL,
    [c_a] [int] NOT NULL,
    [c_b] [varchar](50) NOT NULL,
 CONSTRAINT [PK_t_b1] PRIMARY KEY CLUSTERED
(
    [c_b1] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
CREATE NONCLUSTERED INDEX [IX_t_b1] ON [dbo].[t_b1]
(
    [c_b] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]

CREATE TABLE [dbo].[t_c1](
    [c_c1] [int] IDENTITY(1,1) NOT NULL,
    [c_a] [int] NOT NULL,
    [c_b] [varchar](50) NOT NULL,
 CONSTRAINT [PK_t_c1] PRIMARY KEY CLUSTERED
(
    [c_c1] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
CREATE NONCLUSTERED INDEX [IX_t_c1] ON [dbo].[t_c1]
(
    [c_b] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]

-- t_a1, t_b1에 데이터 삽입
SET @a = 0;
WHILE @a < 100000
BEGIN
    INSERT INTO t_a1 (c_a, c_b)
        select c_a, c_b from t_a
    INSERT INTO t_b1 (c_a, c_b)
        select c_a, c_b from t_b
    INSERT INTO t_c1 (c_a, c_b)
        select c_a, c_b from t_c
    SET @a = @a + 1;
END

select t_a.c_a, t_a1.c_a1 from t_a inner join t_a1 on t_a.c_a = t_a1.c_a;
select t_b.c_a, t_b1.c_b1 from t_b inner join t_b1 on t_b.c_b = t_b1.c_b;
select t_c.c_a, t_c1.c_c1 from t_c inner join t_c1 on t_c.c_b = t_c1.c_b;

GO

맨 아래의, 조인을 이용한 쿼리 결과는 다음과 같다 (단위:ms)

  1차시도 2차시도
a(int) 4736 4796       
b(varchar) 7590        7981       
c(varchar) 29483        9463       

쿼리 실행 계획까지 아울러 적어두면 좋겠지만... 뭐 그건 각자 확인해보시도록 하고.
Merge Join에서도 근소하게 CPU 비용이 증가하지만, 그것보다는 인덱스 스캔 부분에서 증가하는 CPU 비용이 만만치 않음. 이 경우 Outer 조인으로 변경하고 인덱스를 제거하면 오히려 성능이 증가할 것 같다는 생각도 드는데 ... 확인해보기가 귀찮아서 패스 ;