728x90

MS-SQL Server 2008 부터 지원되는 데이터 압축 기능

 

•압축방식
  –행압축
    •고정길이데이터형식을가변길이데이터형식처럼저장
    •행메타데이터최적화
  –페이지압축

    •행압축
    •접두사및사전

 

 

 

 데이터를 압축하여 보관 하게 되므로 아래와 같은 장단점이 있다

 

•장점
–물리적인저장공간감소
–메모리내데이터압축
–동일한쿼리의결과반환을위해더적은데이터페이지를참조하므로I/O가감소됨
–어플리케이션수정필요없음


•단점
–CPU 자원사용률이증가함

 

  압축 전후의 차이는 CPU 사용율이 증가하나 Query속도는 비슷하다.

 

DW 또는대용량데이터마트의경우CPU 자원에여유가있으면페이지압축검토

행압축이데이터공간을절약하고,CPU 사용률이10% 정도증가할경우행압축검토

 

•인덱스재작성시평균CPU 자원사용량
  –행압축: 약1.5배필요
  –페이지압축: 약2~5배필요

 

(실제 구현)

 

http://msdn.microsoft.com/ko-kr/library/cc280449.aspx

 

 

(펌 자료)

 

SQL Server 2008(Enterprise Edition)에서는 테이블 및 인덱스 모두에 대해 행 압축과 페이지 압축을 지원합니다.

 

     행 압축
행 압축을 사용하면 데이터 형식과 연결된 데이터의 물리적 저장소 형식이 변경됩니다. 예를 들어 테이블에  데이터 형식을 BIGINT 를 사용했다고 가정한고 값이 10이 입력되었다고 하면 행 압축을 하면 10을 저장하기 위해서 BIGINT 데이터 형식으 크기 8 바이트가 아니라 값을 기준으로 필요한 바이트 즉 1바이트만 사용하게 됩니다.

 

     페이지 압축
페이지 압축은 테이블, 테이블 파티션, 인덱스 및 인덱스 파티션에 대한 압축과 유사합니다. 페이지 압축을 사용할 경우 인덱스의 리프 수준이 아닌 페이지는 행 압축만 사용하여 압축됩니다. 테이블 및 인덱스의 리프수준을 압축은 접두사 압축 후 사전 압축 작업을 진행하여 압축을 합니다.  

가)    접두사 압축

접두사 압축을 진행하면 접두사는 헤더로 이동하고 열 값은 접두사의 참조로 변경됩니다.

접두사 압축전 테이블 페이지

접두사 압축 후 테이블 페이지

 

나)    사전 압축

사전 압축은 페이지에서 반복된 값을 검색하여 CI 영역에 저장합니다.


접두사 압축 후 테이블 페이지

사전 압축 후 테이블 페이지


페이지 압축은 데이터가 첫 데이터 페이지에 추가되는 시점에 데이터의 행이 압축됩니다. 페이지가 가득 차지 않으면 페이지 압축으로 얻는 이득이 없어 행이 가륵 차면 추가할 다음 행에서 페이지 압축 작업을 시작합니다. 그리고 압축하기 전에 페이지 압축으로 얻는 공간에서CI 구조에 필요한 공간을 뺸 값이 크지 않으면 해당 페이지에 대한 페이지 압축을 하지 않습니다
 

 

3     압축된 테이블 쿼리

가)    테이블 생성

CREATE TABLE T1

(c1 int, c2 nvarchar(1024) )

WITH (DATA_COMPRESSION = PAGE | ROW);

 

나)    테이블 변경

ALTER TABLE T1
REBUILD WITH (DATA_COMPRESSION = PAGE | ROW);

 

4    

성능 테스트
가)    개요

     샘플 데이터 1000만 건에 대해서 압축안함, 행 압축, 페이지 압축에 대해서 데이터 크기 및 Select 쿼리 성능 및 Insert 쿼리 성능 측정

나)    데이터 크기

 


데이터 사이즈는 테이블 구조에 따라 틀리겠지만 Page 압축을 했을떄 48%, Row 압축시77%의 압축율을 보입니다.


 

  

가)    Select 쿼리 성능


           Page

압축의 경우 압축안함과 비교하여 CPU 118%로 조금 더 사용하지만 Reads 에서
           208% Duaraion에서 155%의 쿼리 성능이 향상 된다.

 

나)    대량 데이터 Insert 쿼리 성능

 


 

단일 데이터 입력은 거의 성능차이가 없기떄문에 대량 데이터 입력으로 쿼리 성능을 측정하였습니다. 대량 데이터 입력(백만건) Page 압축은 압축 안함과 비교하여 CPU 자원을 압 235%로 자원을 더 사용하며 Duration 또한 249% 더 증가한다. Write 의 경우는 압축율에 비래하여 50% 정도로 감소한다.

 


 

 

5   결론

테이블 압축을 사용을 하면서 데이터 공간의 절약을 할 수 있으며 또한 데이터 조회시 압축을 함으로써 압축을 복원하는데 CPU 자원을 약간 더 사용을 하지만 Reads  Duration 에 많은 성능 이점을 가지고 있습니다. 대량 데이터 Insert 시점에는 다소 성능이 떨어집니다. OLTP 환경에서 데이터 건수가 많고 조회가 많은 테이블에 대해서는 테이블 압축을 적용하면 많은 이점을 얻을 수 있을거라 판단됩니다. 또한 OLAP 환경에서도 대량 데이터를 입력하는 Insert 쿼리는 다소 떨어지지만 저장공간 및 집계 및 조회 성능 이점이 많을거라 판단됩니다.

728x90

김연욱(Vichang)님의 Inside Query Performance - (2)

 

안녕하세요. 김대우 입니다. 더운 여름 잘 지내고 계신지요?

이번에 소개해 드릴 내용은 김연욱님이 이끌어 주시는 Inside Query Performance 입니다.

아주 좋은 SQL 쿼리 성능에 대한 연재글을 올려 주고 계시며 SQLER에 많은 도움을 주시는 분이지요.

이하 내용은 김연욱님이 SQLER 자유게시판에 올려주신 항목을 정리한 내용입니다.

언제나 많은 도움 주시는 김연욱님 감사 드리구요. 앞으로도 좋은... 더운 여름 더더욱 열나게 하는 ^_^;;;

좋은 글 부탁 드립니다. 감사합니다.

이하 김연욱님의 글입니다.


 

 

일반적으로 이력관리 모델에서는 시작일자와 종료일자컬럼을 가진다.

--> 이부분에 대한것은 워낙에 내용이 길기때문에 생략.

--> 뭐 그냥 시작일자나 종료일자 하나만 가지는 설계를 사용하신다면 할 말 없음

이력모델에서 시작일자 + 종료일자 인덱스를 사용하는것이 좋을지

아니면 종료일자 + 시작일자 인덱스를 사용하는것이 좋을지

한번 테스트 해보자.

 


 


선행지식을 위해 between을 생각해보자.

일반적으로 우리가 사용하는 between은 컬럼 between 상수값1 and 상수값2  의 형태이다.

하지만, 조금 변형하여 상수값 between 컬럼1 and 컬럼2 의 형태로도 사용이 가능하다.

간단하게 예를 들어보면

벙개를 위한 장소를 몰색하던 00군이 인터넷으로 벙개용 좌석예약을 할려구 한다.

 

--drop table 좌석예약
create table 좌석예약(좌석등급 char(1), 시작번호 int, 종료번호 int, 좌석수 int, 구분 varchar(10))
go
insert into 좌석예약 values('A', 1, 100, 100, '공석')
insert into 좌석예약 values('B', 1, 100, 100, '공석')
go

 


마침 00주점에 이러한 시스템이 있었다.

사무실에서 pc로 단가가 적은 B석 51번부터 10개의 좌석을 예약한다고 해보자.

이럴때 어떻게 쿼리해야할까 ?

 

--1. 좌석상태확인
select * from 좌석예약
where 좌석등급 = 'B' and 좌석수 >= 10 and 구분='빈자리'
and 51 between 시작번호 and 종료번호

좌석 등급        시작번호    종료번호    좌석수         구분        
---- ----------- ----------- ----------- ----------
B    1           100         100         빈자리

(1개 행 적용됨)


이제 between 사용에 감이 잡히겠지요.

기왕 하는김에 좌석예약을 해보자.
 

 

--2. 좌석예약
set nocount on
declare @좌석등급 char(1), @좌석수 int, @시작번호 int, @종료번호 int, @구분 varchar(10)
declare @좌석위치 int
set @좌석등급 = 'B'
set @좌석수 = 10
set @좌석위치 = 51
set @구분 = 'SQLER 벙개'

set xact_abort on
begin tran
   if exists(select * from 좌석예약 where 좌석등급 = 'B' and 좌석수 >= 10 and 구분='빈자리'
                                      and @좌석위치 between 시작번호 and 종료번호)
      begin
         update 좌석예약
         set 시작번호 = @좌석위치,
             종료번호 = @좌석위치 + @좌석수 - 1,
             좌석수 = @좌석수,
             구분 = @구분,
             @시작번호 = 시작번호,
             @종료번호 = 종료번호
         where 좌석등급 = 'B' and 좌석수 >= 10 and 구분='빈자리'
           and @좌석위치 between 시작번호 and 종료번호

         if @시작번호 < @좌석위치  
            begin
               insert into 좌석예약 values (@좌석등급, @시작번호, @좌석위치-1,
                                            @좌석위치-@시작번호, '빈자리')
            end

         if @종료번호 > (@좌석위치 + @좌석수)
            begin
               insert into 좌석예약 values (@좌석등급, @좌석위치+@좌석수, @종료번호,
                                            @종료번호-@좌석위치-@좌석수, '빈자리')
            end
      end
commit tran
set nocount off
go

 



예약을 잘되었는지 확인해보면...

 

 

select * from 좌석예약 where 좌석등급 = 'B' order by 시작번호

좌석 등급        시작번호    종료번호    좌석수         구분        
---- ----------- ----------- ----------- ----------
B    1           50          50          빈자리
B    51          60          10          SQLER 벙개
B    61          100         39          빈자리

(3개 행 적용됨)

51번부터 60번까지 예약이 잘되있다...  근데 난 언제나 벙개에 함 나갈볼런지...

 


 

 

/*
선행지식
    1. 재미없는 긴글을 끝까지 읽어줄 강인한 인내력 (필수)

    2. sql서버는 복합컬럼인덱스일경우 첫번째 컬럼의 통계정보만을 사용함. (중요)
    3. 인덱스에 대한 기본지식
    4. between에 대한 기본지식
       일반적으로 between을 이용할경우는 변수 between 시작값 and 종료값 의 형식으로
       사용하지만 상수값 between 시작변수 and 종료변수 와 같이 사용할 수 도 있다.
       (좀더 자세한 내용은 관련 서적 참고)

    해당인덱스를 clustered index 로 할것인지 nonclustered index 로 할것인지는 별개의
    문제이므로 논외로 합니다.
*/

--언제나 테스트는 tempdb에서
use tempdb
go

--역시나 테스트를 위해서 테이블 하나 만들구
--drop table a
create table a(edt int not null, sdt int not null, num int identity)
go

--테스트를 편하게 하고자 날짜형 대신에 숫자형으로 테스트
/* ========================================================================== */
--켜져있으면 끄놓구
set statistics io off
set statistics profile off

--테스트용 샘플 넣구
set nocount on
declare @i int
set @i = 0
while (@i < 100000)
begin
insert a values(@i+1, @i)
set @i = @i + 2
end
set nocount off
select count(*) from a  --50000건

--테스트를 위해 켜두고
set statistics io on
set statistics profile on
go

 


 

 

--아무런 제약조건, 인덱스 없는 상태에서 그냥
select num from a

|--Table Scan(OBJECT:([tempdb].[dbo].[A]))
--논리적 읽기 수 137 (전체 페이지수가 137page임)
 

--먼저 시작일 + 종료일자로 pk 잡아주고
alter table a add constraint pk_a primary key nonclustered(sdt, edt)


--저 앞에 있는놈을 보면
select num from a where 90 between sdt and edt

|--Table Scan(OBJECT:([tempdb].[dbo].[A]), WHERE:(Convert([@1])>=[A].[SDT]
                                                AND Convert([@2])<=[A].[EDT]))
-- 논리적 읽기 수 137
 

-- 저 뒤에 있는 놈을 보면
select num from a where 90000 between sdt and edt

|--Table Scan(OBJECT:([tempdb].[dbo].[A]), WHERE:(Convert([@1])>=[A].[SDT]
                                                AND Convert([@2])<=[A].[EDT]))
--논리적 읽기 수 137
 

-- 강제로 pk를 타라고 힌트를 주면 어떨까 ?
select num from a (index=pk_a) where 90000 between sdt and edt
  |--Bookmark Lookup(BOOKMARK:([Bmk1000]), OBJECT:([tempdb].[dbo].[A]))

       |--Index Seek(OBJECT:([tempdb].[dbo].[A].[PK_A]), SEEK:([A].[SDT] <= 90000),
                                                         WHERE:(90000<=[A].[EDT])
--논리적 읽기 수 108
 

--그럼, top 1 을 주고 인덱스를 타라고 힌트를 주면 어떨까 ?
select top 1 num from a (index=pk_a) where 90000 between sdt and edt

  |--Top(1)
       |--Bookmark Lookup(BOOKMARK:([Bmk1000]), OBJECT:([tempdb].[dbo].[A]))
            |--Index Seek(OBJECT:([tempdb].[dbo].[A].[PK_A]), SEEK:([A].[SDT] <= 90000),
                          WHERE:(9000<=[A].[EDT]) ORDERED FORWARD)

--논리적 읽기 수 108
 

--일단, pk 지우고
alter table a drop constraint pk_a
 

--이번에는 종료일 + 시작일루 pk 잡아주고
alter table a add constraint pk_a primary key nonclustered(edt, sdt)
 

--저 앞에 있는놈을 보면
select num from a where 90 between sdt and edt

  |--Table Scan(OBJECT:([tempdb].[dbo].[A]), WHERE:(Convert([@1])>=[A].[SDT]
                                                AND Convert([@2])<=[A].[EDT]))
--논리적 읽기 수 137
 

--그럼, pk를 타라구 힌트를 주면
select num from a (index(pk_a))
where 90 between sdt and edt
select num from a (index(pk_a))  where 90 between sdt and edt

  |--Bookmark Lookup(BOOKMARK:([Bmk1000]), OBJECT:([tempdb].[dbo].[A]))
       |--Index Seek(OBJECT:([tempdb].[dbo].[A].[PK_A]), SEEK:([A].[EDT] >= 90), 
                             WHERE:(90>=[A].[SDT]) ORDERED FORWARD)
--논리적 읽기 수 120
 

--요번에는 top 1 까지 주면
select top 1 num from a (index(pk_a))
where 90 between sdt and edt

  |--Top(1)
       |--Bookmark Lookup(BOOKMARK:([Bmk1000]), OBJECT:([tempdb].[dbo].[A]))
            |--Index Seek(OBJECT:([tempdb].[dbo].[A].[PK_A]), SEEK:([A].[EDT] >= 90), 
                                  WHERE:(90>=[A].[SDT]) ORDERED FORWARD)
--논리적 읽기 수 3
 

-- 요번엔 저뒤에 있는놈을 보면
select num from a where 90000 between sdt and edt

  |--Table Scan(OBJECT:([tempdb].[dbo].[A]), WHERE:(Convert([@1])>=[A].[SDT]
                                               AND Convert([@2])<=[A].[EDT]))
--논리적 읽기 수 137

--이번에두 힌트사용
select num from a (index(pk_a)) where 90000 between sdt and edt

  |--Bookmark Lookup(BOOKMARK:([Bmk1000]), OBJECT:([tempdb].[dbo].[A]))
       |--Index Seek(OBJECT:([tempdb].[dbo].[A].[PK_A]), SEEK:([A].[EDT] >= 90000), 
                             WHERE:(90000>=[A].[SDT]) ORDERED FORWARD)
--논리적 읽기 수 15
 

--그럼, top 1 을 주면
select top 1 num from a (index(pk_a)) where 90000 between sdt and edt

  |--Top(1)
       |--Bookmark Lookup(BOOKMARK:([Bmk1000]), OBJECT:([tempdb].[dbo].[A]))
            |--Index Seek(OBJECT:([tempdb].[dbo].[A].[PK_A]), SEEK:([A].[EDT] >= 90000), 
                          WHERE:(90000>=[A].[SDT]) ORDERED FORWARD)
--논리적 읽기 수 3
 

-- 통계정보를 만들어 주면 좀더 낳지 않을까(역시 동일한 결과) - 각자 해보세요...
CREATE STATISTICS ust_a_edt
   ON test..a (edt)
   WITH FULLSCAN
CREATE STATISTICS ust_a_sdt
   ON test..a (sdt)
   WITH FULLSCAN
UPDATE STATISTICS a(pk_a)
   WITH SAMPLE 100 PERCENT

 


결론 : sql서버에서 --> 상수값 between 시작값 and 종료값 조인을 할경우
1. 시작일 + 종료일 인덱스일경우
  1) 시작일만을 사용하므로 해당조건의 선택도가 항상동일함에도 불구하고,

     시작일 <= 상수값 의 조건만을 활용한다.

       |--Index Seek(OBJECT:([test].[dbo].[A].[PK_A]), SEEK:([A].[SDT] <= 90), 
                     WHERE:(90<=[A].[EDT]) ORDERED FORWARD)

  2) 시작일 <= 상수값의 조건으로 인덱스를 스캔하면서 종료일 >= 상수값의 조건으로

     필터처리한다.(무식한놈)


2. 종료일 + 시작일 인덱스일경우
  1) 종료일만을 사용하므로 해당조건의 선택도가 항상동일함에도 불구하고,

     종료일 >= 상수값 의 조건만을 활용한다.

        |--Index Seek(OBJECT:([test].[dbo].[A].[PK_A]), SEEK:([A].[EDT] >= 90000), 
                      WHERE:(90000>=[A].[SDT]) ORDERED FORWARD)

  2) 종료일 >= 상수값의 조건으로 인덱스를 스캔하면서 시작일 <= 상수값의 조건으로

     필터처리한다.(무식한놈)


3. 결론
  1) 종료일 + 시작일 순으로 인덱스를 만들고나서

  2) SELECT 절에 TOP 1 을 추가하고

  3) INDEX(인덱스명) 힌트를 사용하면 항상 동일한 결과를 나타낸다.

  ※ 시작일 + 종료일 순으로 인덱스를 만들면 위와같이(1번에서 3번까지) 해도 비효율적인 스캔을 한다.

 


누군가가 딴지를 걸어주기를 기다렸는데 아무두 안걸어주어서 내가 딴지건다.

딴지) 뭐땜시, 복잡하게 종료일자 + 시작일자로 인덱스를 만드나, 그냥 시작일자 하나만 있어두 되던데...

 

--켜져있으면 끄놓구
set statistics io off
set statistics profile off
go

--일단, pk 지우고
alter table a
drop constraint pk_a

--이번에는 종료일 + 시작일루 pk 잡아주고
alter table a add constraint pk_a primary key nonclustered(sdt)

--테스트를 위해 다시 켜두고
set statistics io on
set statistics profile on
go

select top 1 num from a where sdt <= 90 order by sdt desc

  |--Top(1)
       |--Bookmark Lookup(BOOKMARK:([Bmk1000]), OBJECT:([tempdb].[dbo].[a]))
            |--Index Seek(OBJECT:([tempdb].[dbo].[a].[pk_a]),
                     SEEK:([a].[sdt] <= 90) ORDERED BACKWARD)
--논리적 읽기 수 3

select top 1 num from a where sdt <= 9000 order by sdt desc

  |--Top(1)
       |--Bookmark Lookup(BOOKMARK:([Bmk1000]), OBJECT:([tempdb].[dbo].[a]))
            |--Index Seek(OBJECT:([tempdb].[dbo].[a].[pk_a]),
                     SEEK:([a].[sdt] <= 9000) ORDERED BACKWARD)
--논리적 읽기 수 3

 

두경우 모두 논리적 읽기 수가 3이다. 이것이 더 좋지 않느냐, 쿼리도 깔끔하구..
 


그럼, sample을 조금 바꿔서 다른경우를 살펴보자.

 

--켜져있으면 끄놓구
set statistics io off
set statistics profile off


--drop table k1
create table k1(상품코드 int not null, 상품명 varchar(100))
go
alter table k1
   add constraint pk_k1 primary key nonclustered(상품코드)
go
set nocount on
declare @i int
set @i = 1
while(@i <= 1000)
begin
   insert into k1 values(@i, 'HDD ' +convert(varchar(10),@i)+'G')
   set @i = @i + 1
end
set nocount off
go

--drop table k2
create table k2(적용일자 datetime not null, 종료일자 datetime not null, 단가 int)
go
set nocount on
declare @i datetime
set @i = '2000-01-01'
while(@i <= '2003-04-30')
begin
    insert into k2 values(@i, dateadd(dd,1,@i), convert(int, rand()*1000))
    set @i = dateadd(dd, 1, @i)
end
set nocount off

--drop table k3
create table k3(상품코드 int not null, 적용일자 datetime not null,
                종료일자 datetime not null, 단가 int)
go
alter table k3
   add constraint pk_k3 primary key nonclustered(상품코드, 적용일자)
go
set nocount on
insert into k3
   select k1.상품코드, k2.적용일자, k2.종료일자, k2.단가
   from k1 cross join k2
set nocount off

--drop table k4
create table k4(상품코드 int not null, 적용일자 datetime not null,
                종료일자 datetime not null, 단가 int)
go
alter table k4
   add constraint pk_k4 primary key nonclustered(상품코드, 종료일자, 적용일자)
go
set nocount on
insert into k4
   select k1.상품코드, k2.적용일자, k2.종료일자, k2.단가
   from k1 cross join k2
set nocount off

 


 


 

 

--다시 테스트를 위하여 켜놓구
set statistics io on
set statistics profile on
go

select count(*) from k3 --1216000
'k3' 테이블. 스캔 수 1, 논리적 읽기 수 3458

select count(*) from k4 --1216000
'k4' 테이블. 스캔 수 1, 논리적 읽기 수 4662

 

인덱스size 차이로 인하여 k4가 page수가 더 많다.

 

--1. 1번 상품의 '2003-03'월 단가변동내역이 필요하다면 ?

select 적용일자, 단가
from k3
where 적용일자 <= '2003-03-31' and 종료일자 >= '2003-03-01'
and 상품코드=1


|--Table Scan(OBJECT:([tempdb].[dbo].[k3]),
           WHERE:(([k3].[적용일자]<=Convert([@1]) AND [k3].[종료일자]>=Convert([@2]))
           AND [k3].[상품코드]=Convert([@3])))

'k3' 테이블. 스캔 수 1, 논리적 읽기 수 5197

 

적용일자가 2003-03-31일 보다 작은것이 대부분이기 때문에 table scan을 선택했다.

 

 

/* ========================================================================== */

select 적용일자, 단가
from k4
where 적용일자 <= '2003-03-31' and 종료일자 >= '2003-03-01'
and 상품코드=1

  |--Bookmark Lookup(BOOKMARK:([Bmk1000]), OBJECT:([tempdb].[dbo].[k4]))
       |--Index Seek(OBJECT:([tempdb].[dbo].[k4].[pk_k4]),
                SEEK:([k4].[상품코드]=Convert([@3]) AND [k4].[종료일자] >= Convert([@2])), 
                WHERE:([k4].[적용일자]<=Convert([@1])) ORDERED FORWARD)

'k4' 테이블. 스캔 수 1, 논리적 읽기 수 36

 

인덱스만을 읽어서 범위를 줄인후에 테이블을 읽어서 단가를 가져왔다.

논리적읽기수를 비교해봐라 엄청난 차이다.


물론, 상황에 따라서 적절한 인덱스 설계를 해야하겠지만

일반적으로 이력모델에서는 종료일자 + 시작일자 의 복합인덱스가 유리한 경우가 많다.

 


김연욱(Vichang)님의 Inside Query Performance - (2)

 

저작권 : 이 홈페이지의 내용은 모두 자유롭게 사용하실 수 있습니다.

728x90

김연욱(Vichang)님의 Inside Query Performance - (1)

 

안녕하세요. 김대우 입니다. 더운 여름 잘 지내고 계신지요?

이번에 소개해 드릴 내용은 김연욱님이 이끌어 주시는 Inside Query Performance 입니다.

아주 좋은 SQL 쿼리 성능에 대한 연재글을 올려 주고 계시며 SQLER에 많은 도움을 주시는 분이지요.

이하 내용은 김연욱님이 SQLER 자유게시판에 올려주신 항목을 정리한 내용입니다.

언제나 많은 도움 주시는 김연욱님 감사 드리구요. 앞으로도 좋은... 더운 여름 더더욱 열나게 하는 ^_^;;;

좋은 글 부탁 드립니다. 감사합니다.

이하 김연욱님의 글입니다.


 

 

1. 문자컬럼에다가 숫자컬럼을 비교하면 인덱스를 타는가 ?

2. 숫자컬럼에다가 문자컬럼을 비교하면 인덱스를 타는가 ?

3. 문자컬럼에다가 datetime컬럼을 비교하면 인덱스를 타는가 ?

4. datetime컬럼에다가 문자컬럼을 비교하면 인덱스를 타는가 ?

 

밑에 결과 보지말구 그냥 5초만 생각보세요.

중수이상 되시는 분은 빨랑 백스페이스를 눌러서 나가주세용.

 

--tempdb에서 테스트
use tempdb
go


--있으면 지우고
drop table a
go


--테스트용 테이블 하나 만들고
create table a(id int identity, cha varchar(10))
go
 

--역시나 테스트용 데이터 넣구
set nocount on
go
declare @i int
set @i = 1
while (@i <= 10000)
begin
insert into a(cha) values (convert(varchar(10), @i))
set @i = @i + 1
end
go
set nocount off
go


--인덱스 만들구
create index idx on a (cha)
create index idx2 on a (id)
go

--테스트를 위해 io량을 보고, 실행계획을 보기위해 설정
set statistics io on
set statistics profile on
go

 


 

 

--어떤경우에 인덱스를 쓸까 ?
--1. 그냥한번 풀로 읽어보면
select * from a

 

|--Table Scan(OBJECT:([tempdb].[dbo].[a]))

'a' 테이블. 스캔 수 1, 논리적 읽기 수 28
 

 

--2. 정상적으로 컬럼형식을 맞춰서 검색하면 ?
select * from a where id = 1

 


|--Bookmark Lookup(BOOKMARK:([Bmk1000]), OBJECT:([tempdb].[dbo].[a]))
       |--Index Seek(OBJECT:([tempdb].[dbo].[a].[idx2]), SEEK:([a].[id]=Convert([@1])) ORDERED FORWARD)

'a' 테이블. 스캔 수 1, 논리적 읽기 수 3

 

 

select * from a where cha='1'

 

|--Bookmark Lookup(BOOKMARK:([Bmk1000]), OBJECT:([tempdb].[dbo].[a]))
       |--Index Seek(OBJECT:([tempdb].[dbo].[a].[idx]), SEEK:([a].[cha]='1') ORDERED FORWARD)

'a' 테이블. 스캔 수 1, 논리적 읽기 수 3
--제대로 3페이지만 인덱스를 사용하여 검색

 

 

--3. 그럼 숫자컬럼에다가 문자를 비교하면 ?
select * from a where id = '1'

 


|--Bookmark Lookup(BOOKMARK:([Bmk1000]), OBJECT:([tempdb].[dbo].[a]))
       |--Index Seek(OBJECT:([tempdb].[dbo].[a].[idx2]), SEEK:([a].[id]=Convert([@1])) ORDERED FORWARD)

'a' 테이블. 스캔 수 1, 논리적 읽기 수 3

 

 

--4. 문자컬럼에다가 숫자를 비교하면 ?
select * from a where cha=1

 


  |--Table Scan(OBJECT:([tempdb].[dbo].[a]), WHERE:(Convert([a].[cha])=Convert([@1])))

'a' 테이블. 스캔 수 1, 논리적 읽기 수 28

--웬 테이블 스캔....

생각하신대로 결과가 나왔나요 ?


그럼 이번에는 많이 사용하는 datetime 컬럼을 테스트해보자.

 

--다른 샘플을 만들기 위해 잠시 off시키고
set statistics io off
set statistics profile off
go
 

--있으면 지우고
drop table b
go
 

--테스트용 테이블 하나 만들고
create table b(dt1 datetime, dt2 char(8))
go
 

--역시나 테스트용 데이터 넣구
set nocount on
go
declare @i datetime
set @i = '2000-01-01'
while (@i <= '2003-12-31')
begin
insert into b values (@i, convert(varchar(8), @i, 112))
set @i = dateadd(dd, 1, @i)
end
go
set nocount off
go
 

--인덱스 만들구
create index idx on b (dt1)
create index idx2 on b (dt2)
go

--테스트를 위해 io량을 보고, 실행계획을 보기위해 설정
set statistics io on
set statistics profile on
go

 



 

 

--어떤경우에 인덱스를 쓸까 ?
--1. 그냥한번 풀로 읽어보면
select * from b

 


|--Table Scan(OBJECT:([tempdb].[dbo].[b]))

'b' 테이블. 스캔 수 1, 논리적 읽기 수 5

 

 

--2. 정상적으로 컬럼형식을 맞춰서 검색하면 ?
declare @dt1 datetime
set @dt1 = '2003-05-05'
select * from b where dt1 = @dt1

 


|--Bookmark Lookup(BOOKMARK:([Bmk1000]), OBJECT:([tempdb].[dbo].[b]))
       |--Index Seek(OBJECT:([tempdb].[dbo].[b].[idx]), SEEK:([b].[dt1]=[@dt1]) ORDERED FORWARD)

'b' 테이블. 스캔 수 1, 논리적 읽기 수 3

 

 

declare @dt2 char(8)
set @dt2 = '20030505'
select * from b where dt2 = @dt2

 


|--Bookmark Lookup(BOOKMARK:([Bmk1000]), OBJECT:([tempdb].[dbo].[b]))
       |--Index Seek(OBJECT:([tempdb].[dbo].[b].[idx2]), SEEK:([b].[dt2]=[@dt2]) ORDERED FORWARD)

'b' 테이블. 스캔 수 1, 논리적 읽기 수 3

 

 

--3. datetime 컬럼에다가 문자를 비교하면 ?
declare @dt2 char(8)
set @dt2 = '20030505'
select * from b where dt1 = @dt2

 


|--Bookmark Lookup(BOOKMARK:([Bmk1000]), OBJECT:([tempdb].[dbo].[b]))
       |--Index Seek(OBJECT:([tempdb].[dbo].[b].[idx]), SEEK:([b].[dt1]=Convert([@dt2])) ORDERED FORWARD)

'b' 테이블. 스캔 수 1, 논리적 읽기 수 3

 

 

--4. 문자컬럼에다가 datetime을 비교하면 ?
declare @dt1 datetime
set @dt1 = '2003-05-05'
select * from b where dt2 = @dt1

 


|--Table Scan(OBJECT:([tempdb].[dbo].[b]), WHERE:(Convert([b].[dt2])=[@dt1]))

'b' 테이블. 스캔 수 1, 논리적 읽기 수 5


--웬 테이블 스캔....

 

생각하신대로 결과가 나왔나요 ?
 



결론, 문자대 숫자의 비교는 무조건 문자를 숫자로 변환후, 또는 문자를 datetime형으로

변환후 비교할려구 한다.(자동형변환에 의하야 - 무조건 형식을 맟춰서 사용해야하겠네요.)

프로그램 개발시 주의하세요 !!!

난이의 밥벌이에 대해서 좀더 알아 보도록 하겠습니다. ^_^

 


김연욱(Vichang)님의 Inside Query Performance - (1)

 

저작권 : 이 홈페이지의 내용은 모두 자유롭게 사용하실 수 있습니다.

+ Recent posts