/*

정렬의 최적화

*/

 

/* 01.정렬이 발생하는 경우 */

 

------기본 셋팅

USE jwjung;

GO

 

IF OBJECT_ID('상품') IS NOT NULL

             DROP TABLE 상품

GO

IF OBJECT_ID('일별상품가격') IS NOT NULL

             DROP TABLE 일별상품가격

GO

 

CREATE TABLE 상품 (

             상품ID                  INT                      NOT NULL

             ,상품명                 SYSNAME           NOT NULL

             ,입력일시              DATETIME           NULL

             ,상품설명              CHAR(2000)       NULL

);

GO

CREATE TABLE 일별상품가격 (

             상품ID                  INT                      NOT NULL

             ,기준일자              CHAR(8)             NOT NULL

             ,상품가격              NUMERIC(12, 0) NOT NULL

             ,비고                                 VARCHAR(1000) NULL

);

GO

 

--상품 테이블에 데이터 입력

INSERT INTO 상품

SELECT TOP 100

             id, name, crdate, name

FROM master.dbo.sysobjects

ORDER BY id DESC

GO

--(100개 행이 영향을 받음)

 

--일별상품가격 테이블에 데이터 입력

INSERT INTO 일별상품가격

SELECT a.상품ID

             , CONVERT(VARCHAR(8), CONVERT(DATETIME, '20090101', 112) + b.rnum - 1, 112)

             , 1000 - b.rnum

             , '비고 : ' + CONVERT(VARCHAR, b.rnum)

FROM 상품 a

             , (

                           SELECT ROW_NUMBER() OVER(ORDER BY a.OrderID) AS rnum

                           FROM Northwind.dbo.Orders a, Northwind.dbo.Orders b

             ) b

WHERE b.rnum <= 365

GO

--(36500개 행이 영향을 받음)

 

ALTER TABLE 상품

ADD CONSTRAINT 상품_pk PRIMARY KEY NONCLUSTERED (상품ID)

GO

 

ALTER TABLE 일별상품가격

ADD CONSTRAINT 일별상품가격_pk PRIMARY KEY NONCLUSTERED (상품ID, 기준일자)

GO

 

EXEC sp_helpindex '상품';

EXEC sp_helpindex '일별상품가격';

 

SELECT * FROM 상품;

GO

--(100개 행이 영향을 받음)

--테이블 '상품'. 검색 수 1, 논리적 읽기 수 34, 물리적 읽기 수 0, 미리 읽기 수 0, LOB 논리적 읽기 수 0, LOB 물리적 읽기 수 0, LOB 미리 읽기 수 0.

SELECT * FROM 일별상품가격;

GO

--(36500개 행이 영향을 받음)

--테이블 '일별상품가격'. 검색 수 1, 논리적 읽기 수 198, 물리적 읽기 수 0, 미리 읽기 수 0, LOB 논리적 읽기 수 0, LOB 물리적 읽기 수 0, LOB 미리 읽기 수 0.

 

DBCC IND('jwjung', '상품', 1);

DBCC IND('jwjung', '상품', 2);

 

DBCC TRACEON(3604);

DBCC PAGE(jwjung, 1, 151, 3);

DBCC TRACEOFF(3604);

 

SET STATISTICS PROFILE ON

SET STATISTICS TIME ON

SET STATISTICS IO ON

--(1) Order by 사용 시

--쿼리에 기술된 order by 조건과 인덱스의 정렬 순서가 일치하지 않으면 정렬이 발생한다.

--둘의 정렬순서가 일치하면 정렬이 발생하지 않지만, 이때도 order by 조건의 컬럼을 가공하면

--정렬이 발생하므로 주의해야 한다.

 

--//order by 조건과 인덱스가 일치하지 않을 때

SELECT TOP 1 *

FROM 일별상품가격 WITH (INDEX(일별상품가격_pk))

ORDER BY 상품ID, 기준일자 DESC

GO

--Rows   Executes             StmtText

--1         1           SELECT TOP 1 * FROM 일별상품가격 WITH (INDEX(일별상품가격_pk)) ORDER BY 상품ID, 기준일자 DESC

--1         1             |--Top(TOP EXPRESSION:((1)))

--1         1                  |--Parallelism(Gather Streams, ORDER BY:([jwjung].[dbo].[일별상품가격].[상품ID] ASC, [jwjung].[dbo].[일별상품가격].[기준일자] DESC))

--3         4                       |--Sort(TOP 1, ORDER BY:([jwjung].[dbo].[일별상품가격].[상품ID] ASC, [jwjung].[dbo].[일별상품가격].[기준일자] DESC))

--36500 4                           |--Nested Loops(Inner Join, OUTER REFERENCES:([Bmk1000], [Expr1005]) WITH UNORDERED PREFETCH)

--36500 4                                 |--Index Scan(OBJECT:([jwjung].[dbo].[일별상품가격].[일별상품가격_pk]))

--36500 36500                         |--RID Lookup(OBJECT:([jwjung].[dbo].[일별상품가격]), SEEK:([Bmk1000]=[Bmk1000]) LOOKUP ORDERED FORWARD)

 

--(1개 행이 영향을 받음)

--테이블 'Worktable'. 검색 수 0, 논리적 읽기 수 0, 물리적 읽기 수 0, 미리 읽기 수 0, LOB 논리적 읽기 수 0, LOB 물리적 읽기 수 0, LOB 미리 읽기 수 0.

--테이블 '일별상품가격'. 검색 수 4, 논리적 읽기 수 36813, 물리적 읽기 수 0, 미리 읽기 수 0, LOB 논리적 읽기 수 0, LOB 물리적 읽기 수 0, LOB 미리 읽기 수 0.

 

--//적절한 인덱스가 있지만, order by 컬럼을 가공했을 때

--인덱스를 효율적(또는 정상적)으로 액세스할 수 있도록 검색조건을 기술하더라도 order by 컬럼을 가공하면 정렬이 발생한다.

SELECT TOP 1 *

FROM 일별상품가격

WHERE 기준일자 BETWEEN '20090301' AND '20090315'

ORDER BY CONVERT(DATETIME, 기준일자, 112)

GO

--Rows   Executes             StmtText

--1         1           SELECT TOP 1 * FROM 일별상품가격 WHERE 기준일자 BETWEEN '20090301' AND '20090315' ORDER BY CONVERT(DATETIME, 기준일자, 112)

--1         1             |--Sort(TOP 1, ORDER BY:([Expr1004] ASC))

--0         0                  |--Compute Scalar(DEFINE:([Expr1004]=CONVERT(datetime,[jwjung].[dbo].[일별상품가격].[기준일자],112)))

--1500   1                       |--Table Scan(OBJECT:([jwjung].[dbo].[일별상품가격]), WHERE:([jwjung].[dbo].[일별상품가격].[기준일자]>='20090301' AND [jwjung].[dbo].[일별상품가격].[기준일자]<='20090315'))

 

--(1개 행이 영향을 받음)

--테이블 '일별상품가격'. 검색 수 1, 논리적 읽기 수 198, 물리적 읽기 수 0, 미리 읽기 수 0, LOB 논리적 읽기 수 0, LOB 물리적 읽기 수 0, LOB 미리 읽기 수 0.

 

--불필요한 CONVERT 함수 때문에 인덱스의 정렬 순서를 활용하지 못해서 정렬이 발생했다.

--CONVERT 함수 제거 시 논리적 읽기수 참고

SELECT TOP 1 *

FROM 일별상품가격

WHERE 기준일자 BETWEEN '20090301' AND '20090315'

GO

--(1개 행이 영향을 받음)

--테이블 '일별상품가격'. 검색 수 1, 논리적 읽기 수 32, 물리적 읽기 수 0, 미리 읽기 수 0, LOB 논리적 읽기 수 0, LOB 물리적 읽기 수 0, LOB 미리 읽기 수 0.

 

 

 

--//group by 사용 시

--group by 조건에 의해서도 정렬 연산이 발생할 수 있다. 이때는 실행계획에 집계(Aggregate) 연산이 함께 발생한다.

SELECT 기준일자, MAX(상품가격)

FROM 일별상품가격

WHERE 기준일자 BETWEEN '20090301' AND '20090305'

GROUP BY 기준일자

GO

 

--(5개 행이 영향을 받음)

--테이블 '일별상품가격'. 검색 수 1, 논리적 읽기 수 198, 물리적 읽기 수 0, 미리 읽기 수 0, LOB 논리적 읽기 수 0, LOB 물리적 읽기 수 0, LOB 미리 읽기 수 0.

 

--Rows   Executes             StmtText

--5         1           SELECT 기준일자, MAX(상품가격) FROM 일별상품가격 WHERE 기준일자 BETWEEN '20090301' AND '20090305' GROUP BY 기준일자

--5         1             |--Stream Aggregate(GROUP BY:([jwjung].[dbo].[일별상품가격].[기준일자]) DEFINE:([Expr1004]=MAX([jwjung].[dbo].[일별상품가격].[상품가격])))

--500     1                  |--Sort(ORDER BY:([jwjung].[dbo].[일별상품가격].[기준일자] ASC))

--500     1                       |--Table Scan(OBJECT:([jwjung].[dbo].[일별상품가격]), WHERE:([jwjung].[dbo].[일별상품가격].[기준일자]>='20090301' AND [jwjung].[dbo].[일별상품가격].[기준일자]<='20090305'))

 

 

--//distinct 사용 시

--select-list distinct를 사용하면 결과 집합에서 중복된 값이 제거되어야 하므로, 이 과정에서 정렬 연산이 발생할 수 있다.

SELECT DISTINCT 상품명

FROM 상품

WHERE 상품명 LIKE 'all%'

GO

--Rows   Executes             StmtText

--5         1           SELECT DISTINCT 상품명 FROM 상품 WHERE 상품명 LIKE 'all%'

--5         1             |--Sort(DISTINCT ORDER BY:([jwjung].[dbo].[상품].[상품명] ASC))

--5         1                  |--Table Scan(OBJECT:([jwjung].[dbo].[상품]), WHERE:([jwjung].[dbo].[상품].[상품명] like N'all%'))

 

--//union 사용 시

--중복된 로우가 제거되고서 결과 집합이 출력

DECLARE @기준일자1 CHAR(8), @기준일자2 CHAR(8)

SET @기준일자1 = '20090101'

SET @기준일자2 = '20091231'

 

SELECT 상품ID, 기준일자, 상품가격

FROM 일별상품가격

WHERE 기준일자 = @기준일자1

UNION

SELECT 상품ID, 기준일자, 상품가격

FROM 일별상품가격

WHERE 기준일자 = @기준일자2

GO

--Rows   Executes             StmtText

--200     1           SELECT 상품ID, 기준일자, 상품가격 FROM 일별상품가격 WHERE 기준일자 = @기준일자1 UNION SELECT 상품ID, 기준일자, 상품가격 FROM 일별상품가격 WHERE 기준일자 = @기준일자2

--200     1             |--Sort(DISTINCT ORDER BY:([Union1008] ASC, [Union1009] ASC, [Union1010] ASC))

--200     1                  |--Concatenation

--100     1                       |--Table Scan(OBJECT:([jwjung].[dbo].[일별상품가격]), WHERE:([jwjung].[dbo].[일별상품가격].[기준일자]=[@기준일자1]))

--100     1                       |--Table Scan(OBJECT:([jwjung].[dbo].[일별상품가격]), WHERE:([jwjung].[dbo].[일별상품가격].[기준일자]=[@기준일자2]))

 

 

--//순위 창 함수 사용 시

SELECT a.*

             , ROW_NUMBER() OVER(ORDER BY 입력일시 DESC)

FROM 상품 a

WHERE 상품명 LIKE 'a%'

GO

--Rows   Executes             StmtText

--5         1             |--Sequence Project(DEFINE:([Expr1003]=row_number))

--5         1                  |--Segment

--5         1                       |--Sort(ORDER BY:([a].[입력일시] DESC))

--5         1                            |--Table Scan(OBJECT:([jwjung].[dbo].[상품] AS [a]), WHERE:([jwjung].[dbo].[상품].[상품명] as [a].[상품명] like N'a%'))

 

 

--//병합 조인 시

--먼저, 상품 테이블에 [상품명 + 상품ID] 컬럼으로 구성된 상품_x01 인덱스를 추가하고

CREATE NONCLUSTERED INDEX 상품_x01 ON 상품(상품명, 상품ID)

GO

 

SELECT TOP 10 *

FROM 상품 a INNER MERGE JOIN 일별상품가격 b

             ON a.상품ID = b.상품ID AND b.기준일자 BETWEEN '20090301' AND '20090331'

WHERE a.상품명 = 'spt_monitor'

ORDER BY b.상품ID, b.기준일자

GO

--Rows   Executes             StmtText

--10       1           SELECT TOP 10 * FROM 상품 a INNER MERGE JOIN 일별상품가격 b ON a.상품ID = b.상품ID AND b.기준일자 BETWEEN '20090301' AND '20090331' WHERE a.상품명 = 'spt_monitor' ORDER BY b.상품ID, b.기준일자

--10       1             |--Sort(TOP 10, ORDER BY:([a].[상품ID] ASC, [b].[기준일자] ASC))

--31       1                  |--Merge Join(Inner Join, MERGE:([a].[상품ID])=([b].[상품ID]), RESIDUAL:([jwjung].[dbo].[상품].[상품ID] as [a].[상품ID]=[jwjung].[dbo].[일별상품가격].[상품ID] as [b].[상품ID]))

--1         1                       |--Nested Loops(Inner Join, OUTER REFERENCES:([Bmk1000]))

--1         1                       |    |--Index Seek(OBJECT:([jwjung].[dbo].[상품].[상품_x01] AS [a]), SEEK:([a].[상품명]=N'spt_monitor') ORDERED FORWARD)

--1         1                       |    |--RID Lookup(OBJECT:([jwjung].[dbo].[상품] AS [a]), SEEK:([Bmk1000]=[Bmk1000]) LOOKUP ORDERED FORWARD)

--2822   1                       |--Sort(ORDER BY:([b].[상품ID] ASC))

--3100   1                            |--Table Scan(OBJECT:([jwjung].[dbo].[일별상품가격] AS [b]), WHERE:([jwjung].[dbo].[일별상품가격].[기준일자] as [b].[기준일자]>='20090301' AND [jwjung].[dbo].[일별상품가격].[기준일자] as [b].[기준일자]<='20090331'))

 

--(10개 행이 영향을 받음)

--테이블 '일별상품가격'. 검색 수 1, 논리적 읽기 수 198, 물리적 읽기 수 0, 미리 읽기 수 0, LOB 논리적 읽기 수 0, LOB 물리적 읽기 수 0, LOB 미리 읽기 수 0.

--테이블 '상품'. 검색 수 1, 논리적 읽기 수 3, 물리적 읽기 수 0, 미리 읽기 수 0, LOB 논리적 읽기 수 0, LOB 물리적 읽기 수 0, LOB 미리 읽기 수 0.

 

--NL 조인하도록 조인 힌트를 변경하고서 테스트한 결과

SELECT TOP 10 *

FROM 상품 a INNER LOOP JOIN 일별상품가격 b

             ON a.상품ID = b.상품ID AND b.기준일자 BETWEEN '20090301' AND '20090331'

WHERE a.상품명 = 'spt_monitor'

ORDER BY b.상품ID, b.기준일자

GO

 

--Rows   Executes             StmtText

--10       1           SELECT TOP 10 * FROM 상품 a INNER LOOP JOIN 일별상품가격 b ON a.상품ID = b.상품ID AND b.기준일자 BETWEEN '20090301' AND '20090331' WHERE a.상품명 = 'spt_monitor' ORDER BY b.상품ID, b.기준일자

--10       1             |--Top(TOP EXPRESSION:((10)))

--10       1                  |--Nested Loops(Inner Join, OUTER REFERENCES:([Bmk1003], [Expr1006]) WITH ORDERED PREFETCH)

--10       1                       |--Nested Loops(Inner Join, OUTER REFERENCES:([a].[상품ID]))

--1         1                       |    |--Nested Loops(Inner Join, OUTER REFERENCES:([Bmk1000]))

--1         1                       |    |    |--Index Seek(OBJECT:([jwjung].[dbo].[상품].[상품_x01] AS [a]), SEEK:([a].[상품명]=N'spt_monitor') ORDERED FORWARD)

--1         1                      |    |    |--RID Lookup(OBJECT:([jwjung].[dbo].[상품] AS [a]), SEEK:([Bmk1000]=[Bmk1000]) LOOKUP ORDERED FORWARD)

--10       1                       |    |--Index Seek(OBJECT:([jwjung].[dbo].[일별상품가격].[일별상품가격_pk] AS [b]), SEEK:([b].[상품ID]=[jwjung].[dbo].[상품].[상품ID] as [a].[상품ID] AND [b].[기준일자] >= '20090301' AND [b].[기준일자] <= '20090331') ORDERED FORWARD)

--10       10                     |--RID Lookup(OBJECT:([jwjung].[dbo].[일별상품가격] AS [b]), SEEK:([Bmk1003]=[Bmk1003]) LOOKUP ORDERED FORWARD)

 

--(10개 행이 영향을 받음)

--테이블 '일별상품가격'. 검색 수 1, 논리적 읽기 수 12, 물리적 읽기 수 0, 미리 읽기 수 0, LOB 논리적 읽기 수 0, LOB 물리적 읽기 수 0, LOB 미리 읽기 수 0.

--테이블 '상품'. 검색 수 1, 논리적 읽기 수 3, 물리적 읽기 수 0, 미리 읽기 수 0, LOB 논리적 읽기 수 0, LOB 물리적 읽기 수 0, LOB 미리 읽기 수 0.

 

EXEC sp_helpindex '상품';

--index_name      index_description             index_keys

--상품_pk             nonclustered, unique, primary key located on PRIMARY         상품ID

--상품_x01           nonclustered located on PRIMARY 상품명, 상품ID

 

EXEC sp_helpindex '일별상품가격';

--index_name      index_description             index_keys

--일별상품가격_pk nonclustered, unique, primary key located on PRIMARY         상품ID, 기준일자

 

 

SET STATISTICS PROFILE OFF

SET STATISTICS TIME OFF

SET STATISTICS IO OFF

 

'연구개발 > DBA' 카테고리의 다른 글

정렬의 최적화 (03.정렬을 최소화하는 SQL 작성)  (0) 2012.01.25
정렬의 최적화 (02.인덱스 정렬을 제거)  (0) 2012.01.24
조인 순서와 성능  (0) 2012.01.23
조인 join  (0) 2012.01.20
면접질의  (0) 2012.01.20

+ Recent posts