분류 전체보기 (328)
.NET (111)
S/W tip (35)
etc (63)
DB (34)
HOT item~! (48)
Disign pettens (4)
UX (6)
나의 S/W (2)
개발관련 이슈 (16)
Diary (1)
웹플러스 (1)
calendar
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
tags
archive
link
ColorSwitch 00 01 02
▣  DB - 해당되는 글 34건

▣  SQL 정리 - DB - 2012. 2. 22. 20:12
출처 :  http://blog.outsider.ne.kr/22


S
ELECT

SELECT * FROM 테이블명 WHERE 필드='조건'  AND 필드2='조건2'
SELECT 필드1, 필드2, 필드3 FROM 테이블명
SELECT * FROM TAB; 존재하는 모든 테이블 보기
SELECT DISTINCT 필드 1 FROM 테이블명; 중복제거하기
SELECT 필드1 FROM 테이블 WHERE 필드2 BETWEEN 00 AND 00; 00부터 00사이에서
SELECT 필드1 FROM 테이블 WHERE 필드2 IN(00,00); 00과 00중에서
SELECT 필드1 FROM 테이블 WHERE 필드2 LIKE '%단어%'
SELECT 필드1 FROM 테이블 WHERE 필드2 IS NULL; or NoT IS NULL


UPDATE

UPDATE 테이블명 SET 필드='값', 필드2='값' WHERE 필드 LIKE '조건' 

INSERT

INSERT INTO 테이블명(필드1, 필드2) VALUSE('값','값')

비교연산자 : >, >=, <, <=, = <>(NOT EQUAL)

DROP TABLE 테이블명;

CREATE TABLE 테이블명(
필드명 타입 조건,
ID VARCHAR2(15) PRIMARY KEY,
PASS VARCHAR(15) NOT NULL,
NO NUMBER(5)
)

시퀀스 생성 : CREATE SEQUENCE 시퀀스이름
                   INCREMENT BY1
                   START WITH 1
                   NOMAXVALUE
                   NOCYCLE
                   NOCACHE;

외래키 생성 : ALTER TABLE 테이블명 ADD (FOREGN KEY (필드) REFERENCES 테이블명2);

컬럼 수정 : ALTER TABLE 테이블명 MODIFY(필드 타입);

컬럼 추가 : ALTER TABLE 테이블명 ADD (필드이름 필드타입);

컬럼 리네임 : ALTER TABLE 테이블명 RENAME COLUMN 컬럼이름 TO 새이름;

Null 값 처리하기 : ALTER TABLE 테이블명 RENAME COLUMN 컬럼이름 TO 새이름;

Null값 처리하기 : SELECT isNULL(필드, '0') FROM 테이블

방금 인서트한 자동 증가값 ID 가져오기 : SELECT @@IDENTITY(현재 세션에서만 사용 가능)
현재 테이블의 가장 큰 값 가져오기 : SELECT IDENT_CURRENT('테이블명')
수행된 SQL문에 의해 영향받은 행의 수 : SELECT @@ROWCOUNT

날짜 계산하기 : SELECT * FROM 테이블명 WHERE DATEADD(DAY, CONVERTT(INT, 컬럼명), 날짜컬럼) > GETDATE()

테이블 정보보기 : EXEC SP_HELP '테이블'
컬럼 정보보기 : EXEC SP_COLUMNS '테이블'

앨리어스 사용 : SELECT 필드 AS A FROM 테이블(별칭에 특수문자 있으면 [] 사용)

주석 : -- OR /* */

변수의 선언 : DECLARE @변수명 자료형, @변수명 자료형
변수 할당 : SET @변수명 = 값
변수값 가져오기 : SELECT @변수명

일부분만 가져오기 : SELECT TOP5 컬럼 FROM 테이블

범위정하기 SELECT 컬럼 FROM 테이블 WHERE 컬럼 BETWEEN 10 AND 20
                SELECT 컬럼 FROM 테이블 WHERE 컬럼 IN('AAA','BBB','CCC')

패턴매칭 : SELECT 컬럼 FROM 테이블 WHERE 컬럼 LIKE '%제목%'
              _ : 어떤것이든 한 문자
              %: 없거나 아무 글자오거나
              []: []안에 있는 글자들 - [B-F]  
             [^]: ^다름에 있는 글자를 제외한 다른 것

중복제거 : SELECT DISTINCT 컬럼 FROM 테이블

GROUP BY : SELECT 컬럼1, sum(컬럼2), FROM 테이블 WHERE 조건 GROUP BY 컬럼1 HAVING sum(컬럼2) >= 30
(계산함수가 반드시 있어야 한다. GROUP BY ALL을 사용하면 WHERE제외된 것도 포함)

이너조인(내부조인) : SELECT 컬럼 FROM 테이블 INNER JOIN 테이블 ON 컬럼=컬럼
   SELECT <열 목록> FROM 첫 번째 테이블 INNER JOIN <두 번째 테이블> ON <조인될 조건> WHERE 검색조건
  첫 번째 테이블의 검색조건을 바탕으로 두 번째 테이블의 데이터 추출

이너조인 3개 조인
SELECT 컬럼 FROM <첫번째 테이블 > INNER JOIN 두번째 테이블 ON <조인될 조건> INNER JOIN <세번째 테이블> ON <조인될 조건>

아웃터조인(외부조인) : 조인의 조건에 만족되지 않는 행까지도 포함시키는 것 (LEFT JOIN이라면 왼쪽의 테이블의 데이타가 모두 출력)

                                SELECT 컬럼 FROM 테이블 LEFT OUTER JOIN 테이블 ON 컬럼=컬럼(어느 한쪽의 데이터를 모두 가져온다.)
                                SELECT <열 목록> FROM <첫 번째 테이블> <LEFT | RIGHT | FULL> OUTER JOIN <두 번째 테이블(RIGHT 테이블)> ON <조인될 조건> WHERE 검색조건 

IN : SELECT 컬럼 FROM 테이블 WHERE 컬럼2 IN (SELECT 컬럼2 FROM 테이블)
(IN안의 하위질의는 DISTINCT가 붙은 것으로 동작)
EXISTS : SELECT 컬럼1 FROM 테이블 WHERE EXISTS(SELECT * FROM 테이블 WHERE 조건)(EXISTS는 하위쿼리가 어떤 결과라도 돌려지면 참)

임시테이블 만들기 : SELECT INTO SELECT * INTO 새테이블명 FROM 테이블(다른 세션에서도 가능, DROP해야 삭제됨)
                            SELECT INTO SELECT * INTO #새테이블명 FROM 테이블(해당세션에서만 가능, 세션 끊어지면 삭제됨)
                            SELECT INTO SELECT * INTO ##새테이블명 FROM 테이블(다른세션에서도 가능, 세션 끊어지면 삭제됨)

UNION : SELECT 컬럼1, 컬럼2 FROM 테이블 UNION SELECT 컬럼1, 컬럼2 FROM 테이블
 - UNION은 컬럼의 자료형과 순서가 맞아야 한다. 중복데이터는 제거되며 UNION ALL을 하면 중복데이터도 가져온다.

컬럼 추가 : ALTER TABLE 테이블명 ADD 새컬럼 VARCHAR(10) NULL
컬럼 병경 : ALTER TABLE 테이블명 ALTER COLUMN 컬럼 VARCHAR(10) NULL
컬럼 삭제 : ALTER TABLE 테이블명 DROP COLUMN 컬럼

삭제 : DELETE FROM 테이블명 WHERE 조건

삭제2 : TRUNCATE TABLE 테이블명
--리소스를 적게 차지한다. 

업데이트 : UPDATE 테이블 SET 컬럼 = 값 WHERE 조건

트랜잭션 : BEGIN TRAN
               ROLLBACK
               COMMIT


테이블 복사 쿼리
1. 테이블을 생성하면서 테이블의 데이터 복사
SELECT * INTO [생성될 테이블 명] FROM [원본 테이블 명]
2. 테이블 구조만 복사
SELECT * INTO [생성될 테이블 명] FROM [원본 테이블 명] WHERE 1 = 2
3. 테이블이 이미 생성되어 있는 경우 데이터만 복사
SELECT INTO [복사 될 테이블 명] SELECT * FROM [원본 테이블 명]
4. 특정 데이타만 복사할 경우
SELECT INTO [복사 될 테이블 명] SELECT * FROM [원본 테이블 명] WHERE 검색조건



 



▣  임시 테이블 만들기 - DB - 2012. 1. 31. 16:53
임시 테이블 만들기

  USE MyDB

GO

CREATE TABLE #MyTempTable

user_id int,
user_name char(20)
)

#을 붙여 만든 테이블은 연결된 세션이 종료되면 자동으로 사라지며, 세션이 연결된 상태라 하더라도 다른 세션에서는 사용 할 수 없습니다.
만일 세션이 연결된 동안 다른 세션에서도 이 임시 테이블을 사용하게 하려면 #대신 ## 붙여 만들면 된다.

저장 프로시저에서 만들어진 지역 임시 테이블은 저장 프로시가 완료될 때 자동으로 삭제됨

테이블은 해당 테이블을 만든 저장 프로시저에 의해 실행되는 모든 중첩된 저장 프로시저에 의해 참조될 수 있으며, 테이블은 테이블을 만든 저장 프로시저에 의해 호출된 프로세스에 의해서는 참조될 수 없습니다.



▣  안전하게 Delete, update 하는 법 - DB - 2012. 1. 17. 15:46

begin tran

delete문

--commit
-- rollback

end tran이 없기 때문에 트랜잭션에 걸려있는 상태이기때문에 원하는 결과로 수행이 되었다면 commit하면 된다./ 또는 rollback 

▣  ER-WIN 거꾸로 그리기 - DB - 2012. 1. 16. 15:54


1. ER-win

Tools -> reverse Engineer 선택

Infer는 선택하지 않는다!!!

 - 끝 -


 http://blog.daum.net/onnarabiz/5921584 

http://www.jinwoo.info/tt/37 

http://joke00.tistory.com/113 
 

▣  SQL 백업 및 복원, 연결 - DB - 2012. 1. 16. 11:06
1. 스크립트로 테이블 구조 복사하기

 -  해당 DB의 태스크 -> 스크립트 생성 

스크립트 생성 마법사 시작 

선택한 데이터베이스의 모든 개체 스크립트 체크하여 전부 만들거나

 원하는 항목만 만들거나 하여 생성 후 타 서버에서 만들어진 스크립트를 돌리면 된다.

2.  데이타 베이스 백업 및 복원

3. 데이타 베이스 MDF파일 이용하기

 - 먼저 해당 DB의 MDF생성 URL을 확인 후

DB를 분리한다.(태스크 -> 분리)

분리 후 해당 파일을 (MDF, LDF)파일을 복사하여 원하는 서버의 DB에 연결한다.


#MDF로 데이터 베이스 연결 시 읽기 전용 나타날 때
읽고자 하는 MDF파일의 속성 -> 보안 -> 사용자 계정에서 사용하는 계정을 추가해야 한다.

이름이 검색이 안 될때 고급옵션에 가보면 있다.

bak 파일을 복원하는 방법 #1


데이터베이스 복원에서 데이터 베이스 이름을 지정해 주면 된다.(새로 데이터 베이스 만들어서 할 필요 없음)

 



▣  MS Sql 성능향상을 위한 튜닝의 정석 - DB - 2011. 7. 18. 11:29

출처 : http://blog.pages.kr/114

코리아폴리스쿨(이하 폴리스쿨) 전산실 조한두 씨의 개발 입문은 웹 개발 부문이었고, 폴리스쿨에 입사하면서 기업의 전산 인프라 관리 업무를 본격적으로 맡게 되었다. 조한두 씨에게 새로운 도전으로 다가온 미션이 있다면 바로 DB 운영관리. 폴리스쿨의 시스템의 주요 활용은, 본사 및 20여 개의 학원 홈페이지 관리는 물론이고, 학원관리 프로그램과 웹 기반 e러닝 데이터 관리 부문이다. 특히 e러닝의 경우 하루 4000여 명이 접속하고 있어 데이터가 급속도로 증가중이고 e러닝의 데이터 통계 시스템에서 에러가 발생하고 쿼리 실행 속도가 급격히 떨어졌다. 이러한 문제에 봉착한 조한두 씨의 요청을 받은 DB D&A는 먼저 사전진단을 요청한 후 결과 분석을 토대로 시스템 구성 점검과 샘플 악성쿼리 튜닝 작업을 함께 진행하였다.

일시: 2007 6 15

장소: 코리아폴리스쿨 HQ(www.koreapolyschool.com)

신청자: 전산실 프로그래머 조한두(hando0712)

전문가: SQL Server Academy 성대중 책임 컨설턴트

 

요청사항

1. 데이터베이스 성능 진단 - 데이터베이스가 느려지고, 쿼리 실행시 오랜 시간이 걸림

2. 데이터베이스 튜닝, 인덱스 생성 - 쿼리 인덱스 생성 방법 습득 쿼리 실행 시간 단축

 

1 시스템 진단

<전문가> 현재 사용하는 애플리케이션 개발 환경은 어떠한가?

<신청자>  ASP, MS SQL Server, PHP 스크립팅 언어 간단히 쓰고 있다.

<전문가> 현재 SQL Server 버전은 무엇이며, 최신 서비스팩은 설치되어 있는가?

<신청자> 현재 Windows 2003 Standard Edition SP1 SQL Server 2000 Standard Edition SP3(8.00.760) 사용하고 SP3까지는 서비스팩이 설치되어 있다.

<전문가> SQL Server 2000 Standard Edition 최대 메모리가 2 GB까지로 제한되어 있다. 스탠다드 에디션을 선택한 특별한 이유가 있는가?

<신청자> 1 6개월 , 구입할 당시에는 지금과 같은 상황을 예측하지 못했고 서버 사양도 지금보다 낮았기 때문에 서버를 바꿀 당시에는 몰라서 보편적인 것으로 선택한다고 생각했다.

<전문가> 향후 하드웨어 추가, 증가할 때는 계획을 세워야 같다. 현재 SQL Server 에디션이 메모리가 2 GB 제한되어서 상당히 부족한 상태이다. 현재 하드에 장착된 메모리가 4GB이고, 최대 사용할 있는 메모리는 1.9GB 정도 있다. 이상 메모리를 늘릴 없는 한계가 있다. 따라서 현재 상태에서는 메모리를 가장 많이 점유하고 있는 쿼리를 찾아 튜닝하는 것이 최적의 방법이 것이다.

또한 향후 업그레이드 계획이 있다면, Windows 2003 Standard Edition 설치된 SQL Server 2005 Standard Edition 경우, 32GB까지 메모리를 지원하기 때문에 SQL Server 2005로의 업그레이드를 고려할 있다. 그리고 스탠다드 에디션의 라이선스도 비즈니스에 활용하시는 거라면 라이선스도 거기에 맞게 고려하는 것이 필요하다. 

<전문가> 그리고 최신 서비스팩과 핫픽스의 적용이 필요한 상태이다. 한가지 주의사항은, 서비스팩의 적용 HotFix 적용은 기존 어플리케이션의 동작에 영향을 미칠 있기 때문에 운영환경에 적용하기 전에 반드시 테스트 환경에서 테스트되어야 한다는 것이다. 폴리스쿨의 경우 특별한 문제가 없었기 때문에 HotFix 적용을 권한다. 

개선사항>> 최근 서비스팩과 Rollup 패키지인 2187 버전의 HotFix 추가로 설치하셔야 하는 상태이며, 아래의 주소를 참조하시면 다운로드 받아서 설치할 있다.

서비스팩

http://www.microsoft.com/downloads/details.aspx?displaylang=ko&FamilyID=8e2dfc8d-c20e-4446-99a9-b7f0213f8bc5

핫픽스

http://www.microsoft.com/downloads/details.aspx?displaylang=ko&FamilyID=9c9ab140-bdee-44df-b7a3-e6849297754a

--확인방법

select @@version

--실행결과

2007-05-31 20:43:59.28 server Microsoft SQL Server  2000 - 8.00.760 (Intel X86)

Dec 17 2002 14:22:05

Copyright (c) 1988-2003 Microsoft Corporation

Standard Edition on Windows NT 5.2 (Build 3790: Service Pack 1)

 

시스템 하드웨어 구성

<전문가> 시스템의 구성된 하드웨어 정보는 어떻게 되는가.

<신청자> 현재 CPU x86 기반 CPU * 2EA 장착되어 있으며, HyperThreading 기능이 활성화되어 있어, 논리적인 CPU 4개로 표시된다. 메모리는 4GB 장착되어 있다. 앞에서 설명했듯이 중에서 SQL Sever 2GB까지 사용할 있다.    

개선사항>> 향후 시스템의 리소스가 부족하게 되면 메모리는 추가할 필요가 있는 상태라고 판단된다. 



 

<전문가> 디스크 서브시스템의 구성 여유공간은 어떻게 되는가?

<신청자> C 드라이브에는 전체 데이터베이스 데이터 파일 로그 파일이 있다. D 드라이브는 백업용도로 사용하고 있고 별도 추가 없이 계속 사용할 것이다. C 드라이브는 현재 10% 정도 사용하고 있다.

<전문가> 확인해 보니, 디스크 서브시스템은 현재 C, D 드라이브가 136 GB SCSI 디스크 1 장으로 구성되어 있고, C 드라이브의 여유공간은 120 GB 정도이고, D 드라이브에는 33 GB 정도가 남아 있다. 혹시 RAID 구성은 되어 있는가?

<신청자> 구성되어 있지 않다.

<전문가> 하드 디스크에 이슈가 있다. 현재 악성쿼리로 인해 디스크에 디스크 읽기/쓰기 병목현상이 발생하고 있다. 가장 시급하게 대용량 IO 발생시키는 악성쿼리에 대한 튜닝 작업이 수행되어야 한다. 또한, 현재의 디스크 구성 디스크에 장애가 발생하게 되면, 백업을 복원해서만 장애복구 있는 상태이기 때문에, 장애조치용 RAID 구성이 필요한 상태이다. 또한, 향후 시스템에 대해서 업그레이드 작업을 계획할 때는 성능 측면을 고려하여, RAID 1+0 구성할 것을 권장한다.

개선사항>> 현재 시스템 볼륨(운영체제가 설치된 볼륨) C 드라이브에 데이터베이스 파일과 로그 파일이 함께 저장되어 있는데, 이를 별도의 드라이브로 분산시키면 디스크의 병목현상을 해결할 있다. 가능하다면, 데이터베이스 파일과 로그 파일도 분리하는 것이 바람직하다.

--확인방법

exec master..xp_fixeddrives

--실행결과

C : 122 GB

D : 33 GB

<전문가> 사용자 데이터베이스의 정보 옵션설정을 살펴보도록 하자. 가장 용량이 많은 Teams 데이터베이스의 복구모델이 SIMPLE 이다. ‘SIMPLE’ 정확히 의미하는 바를 알고 설정한 것인가?

<신청자> 로그를 쌓을 최대한 쌓지 않는 모델이라 판단하여 선택했다.

<전문가> SIMPLE 정확한 의미를 알고 있어야 한다. SIMPLE 복구모델을 사용하면, 장애발생시 마지막 전체 백업 시점으로만 복원할 있다. 이럴 발생하는 문제는, 마지막 전체 백업 이후 데이터는 손실될 가능성이 있다. 따라서 운영중이고 중요한 데이터를 저장하는 데이터베이스는 전체 복구모델 사용하는 것이 바람직하다. , 전체 복구모델을 사용하는 경우, 트랜잭션 로그 백업을 주기적으로 수행하여야만, 트랜잭션 로그 파일의 크기가 계속 증가하는 것을 방지할 있다.

--확인방법

sp_helpdb

<신청자> 최근에 KPIS DB SIMPLE 모델이 속도가 빨라진다고 해서 그렇게 설정했다가 그래도 로그를 남겨야겠다는 생각에 대량로그(Bulk Load) 설정하였다.

<전문가> 대량로그 모델은 대량 로그 작업이 없다면 굳이 사용할 필요는 없다. 대량로그는 변경된 데이터 페이지까지 로그에 기록해야 하기 때문에 이슈가 수도 있다. 따라서 대량 작업이 많이 없으면 전체 복구모델을 쓰는 가장 합리적이다.

<신청자> 복구모델에 따라 파일 크기가 차이가 나는데, 속도 차이가 나지 않는가?

<전문가> 흔히 SIMPLE 빠를 것이라고 생각하는데, 복구모델에 따른 속도 차이는 없다. 기본 구조는 모든 모델이 동일하기 때문에 로그 기록 작업은 어느 복구모델을 사용하더라도 반드시 수행하는 것이다. 어느 모델이나 하는 것이므로 로그 파일들이 커지는 것을 최소화 있는데 속도에는 크게 차이가 없다. 전체 복구모델을 설정할 경우 로그파일을 정기적으로 받아줘야 하는 것이 차이가 있다. 나중에 복구 모델이 무엇이고, 복구 모델을 어떻게 관리해야 하는지에 대해서는 관리자 교육을 한번 받으시면 이해하실 있을 것이다.

<전문가> 데이터베이스의 실제 사용량은?

<신청자> 실제로 사용되는 DB teams KPIS이다. teams 데이터베이스는 E-learning 솔루션( 4GB), KPIS 데이터베이스의 경우 학원관리 홈페이지 관리( 2GB) 사용되고 있다.   

<전문가> 일단 데이터베이스의 주요 대용량 테이블을 살펴보면 다음과 같다 

db_name

data

data_used

data_free

log

log_used

log_free

Teams

3398.9

3274.8

124.1

555.6

20.6

535

KPIS

1460.4

1138.1

322.3

82.5

12.5

70



2회 성능 카운터 진단 

<전문가> 사전에 성능카운터를 보고 부문별로 그래프를 그려 왔다. 데이터는 하루 데이터를 기준으로 정리한 것이다.  

<전문가> CPU 사용률부터 살펴보자. 평균 CPU 사용률은 1 평균 20%, 주요 업무시간 평균 35% 정도로 비교적 안정적이지만, 악성쿼리로 인한 CPU 스파이크(Spike) 현상이 지속적으로 발생하고 있다. 평균 사용률이 높더라도 중간에 튀는 현상이 없으면 안정적으로 사용할 있는 것이다. 특히, 야간에 통계작업을 위한 배치작업의 쿼리는 상당한 부하를 발생시키고 있다. 악성쿼리에 대한 튜닝이 필요한 상태이다.

 

<전문가> 디스크를 보고 깜짝 놀랐다. 디스크 병목현상을 봐야겠다. 현재 드라이브가 136GB 디스크 1장으로 구성되어 있어, 필요한 디스크 IO 대역폭을 감당하지 못하고 병목현상이 발생되고 있다. 특히, 읽기(파란색) 비해 쓰기(빨간색) 대한 디스크 대기가 문제가 되고 있다. 보통 적정 임계치는 그래프에서 0.02(20Ms) 정도여야 하는데, 그래프를 보면 0.5 훨씬 넘어가는 것들이 많다.

이것은 디스크가 RAID 구성되어 있지 않고 하나를 통째로 사용하고 있기 때문에 대량 데이터를 읽거나 이슈가 발생하고 있다. 특히 쓰기에서 이슈가 크다. temp DB 많이 사용하고 있는데, 인덱스 부분의 이슈로 불필요한 소트나 해싱 작업이 발생하고 있다. 이를 해결하기 위해, 대량 IO 유발하는 악성쿼리에 대한 튜닝과, 디스크 서브 시스템에 대한 확장이 필요하다.

 

<신청자> 그럼 디스크를 사야 되는가? 사면 디스크 하나를 사는 것인가?

<전문가> 디스크 2 이상을 스트라이프 해서 RAID 시스템으로 묶어서 쓰는 것을 의미한다. 지금은 C라는 디스크 하나에 엑세스를 하고 있기 때문에 디스크가 망가지면 끝이라는 것이다. 그래서 데이터 미러(mirror) 기본이고, 다음에 장애조치를 위해서 미러를 한다면 성능을 위해서는 스트라이프를 하는 것이다. 같은 146GB라도 통으로 쓰는 것보다 36GB짜리 4 묶어 146GB 쓰는 것이 빠르다. RAID 시스템을 물리적으로 하면 볼륨을 위로 하나를 묶어 C 사용할 있다. 거듭 이야기하지만 디스크를 추가하여 보완할 있겠지만, 이전에 이렇게 만드는 악성 쿼리 튜닝이 가장 우선한다.

<전문가> 메모리 병목현상을 가장 간단히 있는 버퍼 적중률 자료 중심으로 설명하겠다. 현재 1.7 GB 메모리를 SQL Server에서 사용하고 있으나, 버퍼 적중률이 평균 89% 매우 낮은 상태이며, 대량 IO 발생시키는 악성쿼리 통계계산을 위한 야간 배치작업 쿼리로 인해, 70% 미만으로 떨어지는 경우가 자주 발생하고 있다. 메모리 부족 현상도 보이고 있다. 역시 악성쿼리 튜닝이 필요한 상태이다.

 

<전문가> 배치요청과 컴파일 비율 현재 비교적 많지 않은 초당 평균 20 정도의 쿼리가 실행되고 있다. 많으면 초당 70 정도의 쿼리가 있는데 무거운 시스템은 아니다. 이에 비해 대부분이 Ad Hoc 쿼리로 작성되어 있어서 대부분의 쿼리가 컴파일 되어 실행되고 있다. Ad Hoc 쿼리를 저장 프로시저로 변경하면 실행계획을 재사용하도록 유도할 있다. 그러면 컴파일 비율을 떨어뜨리고 CPU 사용률을 줄일 있어 전체적인 성능 향상에 도움을 있다.

 

<전문가> 배치요청과 잠금요청수에 관한 것이다. 배치요청(빨간색, 왼쪽단위) 비해 잠금요청수(파란색, 오른쪽단위) 상당히 높은 상태이다. 특히 배치당 잠금요청을 계산해 보면, 야간의 통계계산을 위한 배치작업은 매우 높은 잠금요청을 발생시키고 있다. 사용자가 많은 시간에 실행되는 쿼리는 차단현상도 발생되고 있으며, 많은 것은 아니지만 간혹 데드락 현상도 발생되고 있다. 이를 해결하기 위해 격리수준 조정 인덱스 전략의 검토, 트랜잭션의 지속시간을 최소화하고, 트랜잭션 내에서 접근하는 테이블의 순서를 일치시키는 등의 권고사항을 준수할 있도록 쿼리를 수정해야 한다.

 

<신청자> 보통 얼마나 되어야 적절한 것인가?

<전문가> 원래는 낮으면 낮을수록 좋고 상당히 높은 편이다. 정확히 짚을 수는 없지만 단위 정도는 낮춰야 것이다.

MS-SQL을 사용하는 DB서버의 속도가 느려진다거나 성능 저하가 느껴질때
물리적인 서버의 성능을 테스트하여 성능향상을 고려할수 있습니다. 논리적인부분 배제함

1. CPU
성능모니터링을 이용한 CPU 모니터링
% Processor Time : 80% 이하가 정상
% Privileged Time : 80% 이하가 정상
% User Time : 80% 이하가 정상
Processor Queue Length : 2 이하가 정상
비정상 수치의 대책 : 프로세서 추가, 더빠른 프로세서로 교체, 보다많은 캐쉬 확보

2. 메모리
성능모니터링을 이용한 메모리 모니터링
Available Bytes : 4 메가 이상이 정상
Pages/sec (paging in/out) : 0~20일 때 정상
Page Reads/sec : 5 이하가 정상
Page Writes/sec : 5 이하가 정상
Buffer Cache Hit Ratio : 90% 이상이 정상
비정상 수치의 대책 : 메모리 추가

3. 디스크
성능모니터링을 이용한 디스크 모니터링
검사항목
% Disk Read Time : 40% 이하가 정상
% Disk Write Time : 40% 이하가 정상
% Disk Time : 40% 이하가 정상
Avg. Disk Queue Length : 2 이하가 정상
비정상 수치의 대책 : 디스크 추가 , RAID 고려, 서버교체를 통한 고속의 신형HDD로의 교체


3회 쿼리 분석 및 악성쿼리 튜닝

하드웨어적인 부분은 이와 같고, 다음으로 쿼리가 너무 많아서 문제인지, 학원에서 쓰는 량에 비해 시스템이 작기 때문인가 등을 분석해 필요가 있다. 실제로 시스템 사이즈가 작아서 그런 것은 아니다. 따라서 가지 분석 툴을 통해 나온 구체적인 쿼리 분석 결과를 살펴보자.  

 

<전문가> 전체 쿼리 요약 통계를 살펴보겠다. 30 동안 수집한 쿼리 중에서 유효한 쿼리를 정리한 결과가 54,322 건의 쿼리가 수행되었다. 초당 30개의 쿼리가 수행되고 있고 쿼리의 평균 응답시간은 18ms이다.

 

<전문가> 응답시간, CPU, 읽기I/O 관점에서 쿼리를 분석해 있다. 특히 개의 그래프 중에서 읽기I/O 보면은, 논리적 읽기 기준 1000 페이지 이상의 쿼리가 건수 기준으로 전체 쿼리 대비 12.49%이며, 전체 읽기 기준으로는 전체의 90.9% 읽기를 발생시키고 있다.

<신청자> 결국은 쿼리를 잘못해서 그런가?

<전문가> 물론 쿼리 때문이고, 부분이 가장 문제임을 있다. 이는 소수의 악성쿼리로 인해 대부분의 시스템 리소스를 사용하고 있음을 의미하며, 소수의 악성쿼리에 대한 튜닝으로 인해서, 시스템의 성능을 개선할 있다는 것을 나타낸다.

 

 

<신청자> 잘못된 쿼리는 얼마나 되는가?

<전문가> 악성쿼리 분포도를 보면 바로 있다. 구별되는 쿼리를 보면 결코 여러 개가 아니라는 것을 있다. 현재 14 페이지의 IO 발생시키는 악성쿼리 1위의 쿼리가 그래프의 상단에 군집되어 나타나며, 외에도 3 페이지의 IO 유발시키는 악성쿼리 2위의 쿼리 등이 군집되어 나타내고 있음을 있습니다. 악성쿼리 1위는 당장 해결해야 한다. 쿼리 튜닝만 해줘도 버퍼 점유율, 메모리 사용률 등이 확실히 줄어들 것이다.

 

<신청자> 악성쿼리가 많은 것도 아니고 쿼리가 그렇다는 것인가?

<전문가> 그렇다. 특정 쿼리가 계속 돌고 있다. 쿼리는 읽기가 적을수록, 쿼리 응답시간이 짧을수록 바람직한 영역이 되기 때문에, 가능한 그래프의 원점으로 수렴될 있도록 악성쿼리를 튜닝해야 한다. 세부적인 악성쿼리 목록은 보면 자세히 있다.

<전문가> 악성쿼리 목록을 찾아내기 위해서는, Microsoft에서 지원하는 Read80Trace라는 도구를 통해 Duration, CPU, Reads 기준 상위 15 쿼리를 찾아내야 한다. Read80Trace 도구를 통해 분석한 자료를 참고하자.

*참고) Read80Trace 도구는 다음의 링크에서 다운로드 받을 있다.

http://www.microsoft.com/downloads/details.aspx?familyid=5691ab53-893a-4aaf-b4a6-9a8bb9669a8b&displaylang=en

*참고) 전체 쿼리 목록은 별첨 Read80Trace분석.xls 파일을 참조 가능하다.

 

악성쿼리 튜닝하기

<전문가> 아까 분석한 결과에서 악성쿼리 1위를 샘플로 튜닝할 것이다.  

<신청자> 악성쿼리 1위를 보고 깜짝 놀랐다. 오래 전에 만든 쿼리인데 까맣게 잊고 있던 것이다. 거의 80~90% 직접 개발한 것들이라 매우 가슴이 아프다. 좀더 DB 대한 교육을 받아야겠다는 생각이 든다.

<전문가> 문제를 찾아내는 과정은 반복되어야 하고 가장 중요하다. 그럼 시작하겠다.  

사례: 읽기기준 1

SELECT… 중간생략

FROM

(    SELECT T2.USER_ID  , T2.USER_NAME  , T2.CODE_USR , T2.USER_SEQ , T2.USER_EMAIL

    , T1.USERSTATE_HOMEPAGE  ,T1.CODE_STA

     FROM  COM_MEM_USERSTATE T1 ,HOM_MEM_USER T2 

    WHERE T1.ACADEMY_SEQ='0601010'  AND T1.USER_SEQ=T2.USER_SEQ 

    AND REPLACE(T2.USER_ID,' ','')='bpm065' AND REPLACE(T2.USER_PWD,' ','')='5857' ) T1…생략

 

문제원인

HOM_MEM_USER 테이블의 검색조건이 되는 USER_ID 칼럼에 대한 가공으로 인덱스를 사용하지 못하고 클러스터 인덱스 스캔 발생

REPLACE(T2.USER_ID,' ','')='bpm065' AND REPLACE(T2.USER_PWD,' ','') ='5857'

 

문제해결

INSERT 명령 실행할 , USER_ID 칼럼에 공백문자(‘ ‘) 포함되지 않도록 강제하고, 실제로 검색조건에서는 REPLACE 함수를 사용하지 않도록 수정

T2.USER_ID='bpm065' AND T2.USER_PWD='5857'

 

개선효과

변경전 평균 14 페이지의 IO 발생 è 변경후 평균 1000페이지 미만의 IO 발생

10,000% 성능향상

 

추가사항

HOM_MEM_USER 테이블의 USER_SEQ 칼럼에 인덱스 생성

create index IX_USER_SEQ ON HOM_MEM_USER(USER_SEQ)

 

추가 튜닝 포인트

·     잠금 문제 해결

-   모든 SELECT 쿼리에 격리수준 조정(테이블명 with (readuncommitted))

·     높은 컴파일 문제 해결

-   Ad Hoc 쿼리를 저장 프로시저로 변경

·     tbl_quiz_info_gclass PK 변경

-   USER_ID 대한 인덱스 필요

-   TestSetCode 대한 인덱스 필요

-   기존 제약조건을 유지하기 위해 UNIQUE 제약조건 필요(PK)

·     tbl_quiz_test t.testyy+ t.testmm + t.testdd 해당하는 칼럼 추가

-   1000 페이지 이상 쿼리 대부분의 쿼리에서 칼럼을 연산하여 날짜 비교

-   이를 개선하기 위해 t.testyy+ t.testmm + t.testdd 해당하는 칼럼 추가

-   추가한 칼럼을 클러스터형 인덱스로 만들고, 기존 TestCode 비클러스터형(Non-Clustered) 기본키로 변경

-   반드시 수정되어야

·     SELECT 절에 사용자 정의 함수 사용 금지

-   쿼리분석기에서는 사용자 정의 함수에 대한 IO 표시되지 않음

-   반드시 프로필러를 통해 확인해야

·     칼럼에 대한 가공 금지

-   LEFT(T1.COUNSEL_DATE,6) = '2007' + '06' (X) à T1.COUNSEL_DATE like '200706%' (O)

-   SUBSTRING(X1.RECEIPT_DATE, 1, 6)='2007'+'06'(X) à X1.RECEIPT_DATE like '200706%'(O)

-   REPLACE(T2.USER_ID,' ','')='06011078' (X) à AND T2.USER_ID='06011078' (O)

·     데드락 문제에 대한 해결

-   데드락의 원인을 찾기 위해 1204 추적 플래그 설정(DBCC TRACEON(1204,-1))

-   데드락이 발생하면 SQL 에러로그에 기록됨

-   원인 규명 1204 추적 플래그 설정해제(DBCC TRACEOFF(1204))

 

추가 점검이 필요한 사항

·     성능 측면

-   악성쿼리의 튜닝

-   인덱스 전략의 검토

·     어플리케이션 측면의 진단

-   Data Access Layer 코드의 진단

-   서버측 커서를 사용하는 코드의 진단

-   Iosys 프로그램의 진단

·     모델링 측면의 진단

-   칼럼 길이의 합의 8000 바이트를 넘는 테이블

-   클러스터형 인덱스의 길이 너무 테이블

-   Ex. tbl_quiz_info_gclass CX 키가 270 바이트

-   Page Split 현상의 발생

·     운영 측면의 진단

-   백업 복원 정책

-   인덱스 최적화 정책

-   통계 업데이트 정책

 

전문가 총평

DB D&A 프로그램의 기본취지는 현재 시스템에 대한 상태를 점검하고, 이에 대한 개선방향 가이드라인을 제시하는 것에 있다.

현재 시스템에 대한 진단 분석 결과, 시스템 자원 메모리와 디스크에 병목현상이 발생하고 있으며, 잠금과 관련한 문제가 발생하고 있다는 것을 확인할 있었다. 문제의 원인은 하드웨어의 용량부족도 일부 원인이 있지만, 보다 근본적인 원인은 소수의 악성쿼리로 인해 서버 리소스의 대부분이 사용되고 있기 때문이다. 그러므로 악성쿼리에 대한 튜닝작업이 시급하게 진행되어야 하는 상태로 판단된다.

현장 방문을 통해 권고한 튜닝 적용안에 대한 적용 어플리케이션 코드 수정 작업이 진행되어야 것이다. 외에 추가적인 상세진단을 통해, 어플리케이션, 모델링, 운영 측면에 대한 점검이 필요한 상태로 판단된다. 현재 존재하는 악성쿼리에 대한 문제에 대해 상세한 수준의 튜닝작업을 위해서는 전문 튜닝 컨설팅을 받는 것도 고려해 있겠다. 또한 SQL Server 전문교육을 통해 담당 엔지니어에 대한 교육훈련이 수반되면 더욱 안정적인 DB 운영을 기대할 있을 것이다.


top
:

▣  초보자도 알아야 할 MySql 튜닝 18가지 - DB - 2011. 7. 18. 11:27

출처 : http://www.leopit.com/Leophp/board/lecture_board/view.php?id=46&board_mode=mysql

1.반드시 컴파일 하라! 10-30% 속도 향상 !

  소스를 가지고 컴파일 하세요. MySQL 메뉴얼에 따르면 10-30% 속도가 빠르다고 합니다. 
  RPM 이나 바이너리 설치를 하지 마세요 !


1-2.최신 버전을 사용하라

  최신 버전이 좋은 점은 자동 튜닝 하는 것 입니다.
  버그를 수정 하구요. 되도록 이면 최신 버전을 사용하세요 !!
  현재 3.23.49 입니다.


2. HEAP 테이블이 가장 빠르다!

  일반적으로 가장 많이 쓰이는 테이블 타입은 MyISAM 타입 입니다.
  MyISAM 타입은 무자게 빠르며, 대용량에도 강합니다. 그러나 트랜잭션은 지원되지 않습니다.
  이노디비(InnoDB) 는 트랜잭션이 지원 됩니다. 쇼핑몰에서는 반드시 사용해야 합니다 ^^

  HEAP 테이블 타입은 가장 빠르며, 단점은 메모리에 있기 때문에, MySQL에 중지 될 경우 모두 날아 갑니다. 
  검색을 하고 재검색을 다시 하는 경우, 임시 검색 테이블을 만들어 놓는 것도 좋은 방법입니다.

  HEAP 테이블 메뉴얼 !
   http://www.mysql.com/doc/H/E/HEAP.html 

  HEAP 테이블 만들기 !
  mysql>CREATE TABLE email_addresses TYPE=HEAP (
     ->email char(55) NOT NULL,
     ->name char(30) NOT NULL,
     ->PRIMARY KEY(email) );


3.mysql 서버 top 보기

  mysql 서버의 메모리 상황을 보여 주는 프로그램 입니다.
  리눅스나 유닉스의 top 기능을 mysql 에서 가능하게 한것 입니다.

  top 정보는 튜닝의 기본 이기 땜시 자주 자주 보아야 합니다. ^^
   http://public.yahoo.com/~jzawodn/mytop/ 

  PHP 소스 자료실에 파일 다운 로드 하시면 됩니다.


4.mysql_connect Vs mysql_pconnect 

  서버 메모리가 최소 2G 이상일 경우 mysql_pconnect 를 추천 함다 !
  연결을 계속 하지 않기 땜시 빠릅니다. !

  그러나 메모리가 2G 이하 일 경우는 mysql_connect 사용하세요 !


5.int,smallint,tinyint 데이터형 ! 

  int 는 굉장히 큰수 입니다. 4바이트를 차지 하구요.
  tinyint 는 몇백 까지만 됩니다. 1바이트 구요.

  쓸데 없이 int 를 사용하지 마세용 !!
  4바이트와 1바이트는 4배 차이 입니다.조그만것 1개 1개가 모여 서버 부하를 일으 킵니다.!!

  데이터 량이 얼마만큼 들어가는지 체크 하고 데이터형을 선택 하세요 ^^
  만약 쓸데없는 데이터 형이다 싶으면 alter table 로 데이터 형을 바꾸세요 !


6.인덱스의 사용 

  인덱스는 반드시 필요한 곳에만 넣으세요 !
  인덱스를 줄 경우 하드 용량을 더 차지 하기 때문에 속도를 떨어 뜨릴 수 있습니다.

  모든 칼럼에 인덱스를 주는 것은 절대 추천 하지 않습니다.
  1개의 테이블에 주키외에 2-3 개 이상의 인덱스는 주지 마세요!

  주키는 당근 인덱스 입니다. ^^

  CREATE TABLE albums (
     id     INTEGER    NOT NULL AUTO_INCREMENT PRIMARY KEY,
     title   VARCHAR(80)NOT NULL,
   
     INDEX title_idx (title)
  );

  ☞Alter Table 로 인덱스 추가 
  ALTER TABLE albums ADD INDEX title_idx (title)  

  결합 인덱스의 경우 너무 많은 인덱스를 사용할 경우 CPU 오버헤드나 하드 오버헤드를 불러 일으 킵니다.
  적당히 사용하세요 ^^

   http://www.mysql.com/doc/I/n/Indexes.html 
   http://www.mysql.com/doc/M/u/Multiple-column_indexes.html 
   http://www.mysql.com/doc/O/R/ORDER_BY_optimisation.html 


6-1. 바보 같은 인덱스의 사용 ? 

  인덱스는 %$search% 가 먹지 않습니다.
  그런디 게시판 제목(Subject) 에 인덱스 걸어 놓고 , 검색을 %$search% 이렇게 하면 될까요?
  인덱스 거나 안거나 똑같습니다. !!

  $search% 이렇게 사용하세요. 그런디.. $search% 사용하면 제목 처음 단어 밖에 검색이 안됩니다.
  그렇다면 ? 다른 검색 방법은 ?


7.UDF의 사용 

  MySQL은 스토어드 프로시져 같은 개념이 존재 하지 않습니다.
  그대신 C 언어로 만든 함수를 사용할 수 있습니다.

  조금더 빠른 쿼리를 원한다면 UDF 를 사용해보세요 !

  UDF 함수 보기 
   http://empyrean.lib.ndsu.nodak.edu/~nem/mysql/udf/ 

   http://www.mysql.com/doc/A/d/Adding_functions.html 

  스토어드 프로시져가 먼뎅?
  스토어드 프로시져는 쉽게 말해 MS-SQL 함수 입니다.
  오라클에도 아마 있을검당..^^

  그러니까 게시판에서 내용을 넘길때나 불러 올때
  mysql 쿼리가 3-4 번 정도 이루어 집니다. 또는 ms- sql 쿼리가 이루어지죵..
  3-4 번 정도 쿼리가 되면..그만큼 디비 접속이 잦아 지기 때문에..
  속도가 느려 집니다.

  많게는 10번 정도의 insert into 와 update 가 이루어집니다.

  그래서 ms - sql 자체 내에 인서트 함수 나 목록 보기 함수를 만들어 놓습니다.
  글구 1번의 ms-sql 접속만 해서 인서트 함수를 불러서 처리하는 것입니다.

  그렇기 때문에 2-3 번의 쿼리가 절약 되서 빠르다는 것이죵..ㅋㅋㅋ
  또는 10번의 쿼리 할것을 MS-SQL 스토어드 프로시져를 1번만 호출 함으로 해서 디비 접속이 절약이 되죵..ㅌㅌ


  UDF 를 꼭 사용해야 하는가?

  안해도 됩니다.만... 사용하면 좋은점 많습니다. 새로운 함수를 추가 할 수 있으므로 ^^
  MS-SQL의 스토어드 프로시져 기능 비스므리 하게 사용할 수 도 있구요...

  UDF 나 MS-SQL 스토어드 프로시져의 사용법을 익히기 보다는 캐슁을 연구하세용 ^^
  동적인 PHP 를 정적인 HTML 로 만드는 방법을요...
  또는 UDF 에서 MS-SQL 스토어드 프로시져 처럼 사용이 가능 합니다. 그 부분을 연구하세요.

  www.zdnet.co.kr 이나 www.zdnet.com 가시면 기사 파일이 1000,29920,2892.html 파일 이란것을 보게 됩니다.
  어키 구현된것일까요? zdnet 게네 들은 강좌를 원래 부터 HTML 로 만들어서 올리는 것일까용??
  HTML 로 만드는 부분도 많이 생각 해야 합니다. 강좌가 1만개 라면, 1만개의 파일이 생성 됩니다.
  zdnet 의 경우는 조회수가 10만-20만을 넘는 초대형 사이트 이기 때문에 HTML 로 만드는 것이 퍼포먼스가 좋습니다.

  UDF 배워 두면..남주지 않습니다. 


8.조인보다는 쿼리를 나누어라! 

  조인(Join)하는 것보다 쿼리를 2개로 나누는 것이 속도가 빠릅니다.
  조인을 생각 하기 이전에 쿼리를 나누는 것을 생각 하세요 ^^

  어쩔 수 없는 경우는 당근 조인 해야죠.

  글고 서브쿼리는 아직 지원 안됩니다.
  Ms-SQL이나 오라클에서 서브쿼리 보다는 서브쿼리를 하지 않는 방향의 데이터 정규화를 하세요 ^^

  조인 보다 서브쿼리가 느리다.
  서브 쿼리 보다는 조인을 사용하세요 ^^


9.full text index와 search 

  3.23.23 부터 mysql 에서는 full text index 를 지원 합니다.  자세한 사항은 아래 ! 

   http://www.mysql.com/documentation/mysql/bychapter/manual_Reference.html#Fulltext_Search 
   http://www.mysql.com/doc/F/u/Fulltext_Fine-tuning.html 


10. SELECT * FROM sometable 

  SELECT * FROM sometable 에서 * 모든을 사용하는 것은 무식한 방법 입니다.
  모든 칼럼을 불러오는 경우는 드물거든요.

  SELECT code,tadate,see FROM sometable

  사용할 것만 불러 오세요 ^^


11.데이터베이스 정규화 

  테이블을 아무렇게나 만들면 안됩니다.
  데이터베이스 정규화 원칙에 의거, 테이블을 나눌것은 나누고 만드시는 것이 좋습니다.
  제1 정규화, 제2 정규화 정도는 사용하셔야 합니다.

  게시판을 만들때 아직도 테이블 1개에 만드시나요? 
  온라인 폴 만들때 , 테이블 1개에 만드시나요?


12.REPLACE INTO문 사용하기 

  REPLACE INTO albums VALUES (6, "tood.net")   
  insert 문대신에 replace 문을 사용해보세요.
  메뉴얼 보시고 연구하세요 ^^

  주키일 경우 사용하시면 됩니다.



13. explain 사용하기 

  explain 를 사용하여 테이블의 키 값이 얼마나 잘 활용 되는지 알 수 있습니다.
  EXPLAIN SELECT, SHOW VARIABLES, SHOW STATUS, SHOW PROCESSLIST 

   http://www.mysql.com/doc/E/X/EXPLAIN.html 


17.BLOB과 TEXT는 분리하라

  BLOB과 TEXT 칼럼은 테이블을 분리 하는 것이 좋다. 다른 칼럼의 내용 보다 크기 때문이다 !

  OPTIMIZE TABLE 명령을 자주 사용해라 !
  Not null 로 지정 하는 것이 빠르다.
  varchar 보다 char 이 훨빠르다.


참고: 게시판의 특성  

  일반 게시판들 보세요. 여기 질문 게시판 만 봐도 알 수 있구요.

  읽기(select) 가 무자게 많습니다. 쓰기(insert into) 보다 30-40 배 정도 많죠.
  하룻동안 올라온 글이 30 개 라면, 하룻동안 글읽기 조회수 총 합은 최소 100 에서 900 까지 갑니다.
  물론 더 엄청날 수 있구요.

  그렇기 때문에 게시판 목록 보기와 글읽기는 HTML 파일로 만들어 놓는 것이 좋습니다.
  또는 MySQL 에서 튜닝 시에 insert 보다는 select 가 빨리 되도록 튜닝 하는 것이 좋구요 ^^


  [참고사이트]
   http://www.mysql.com/documentation/mysql/bychapter/manual_Table_types.html#SEC457 
   http://www.mysql.com/documentation/mysql/bychapter/manual_Reference.html#Fulltext_Search  

  from:
   http://www.javasun.net/board/Bread.jsp?CMD=400000&lnum=19&linenum=15&maxnum=0&keykind=null&keyword=&total=0&NCNT=1&ACMD=10&BCODE=001723
top
:

▣  동적 SQL의 축복과 저주 - DB - 2011. 7. 18. 10:01

출처 : http://hoons.kr/board.aspx?Name=AspNetPds&BoardIdx=6190&Page=1&Mode=2

원문 : SQL Server MVP Erland SommarskogSQL Text
번역 : quest, ASP MVP (microsoft.public.kr.asp.qna)

Microsoft SQL 서버에 대한 여러 뉴스그룹에서 왜 다음 쿼리가 불가능한지를 문의하는 사람들을 종종 보게 된다.:

   SELECT * FROM @tablename
   SELECT @colname FROM tbl
   SELECT * FROM tbl WHERE x IN (@list)

많은 경우에 간략한 예와 함께 동적 SQL을 사용하세요 라는 답변이 달리곤 하지만, 답변하는 사람들조차 동적 SQL 문의 사용시에 주의해야 할 사항에 대한 언급을 잊는 경우가 많다.

이 기사에서는 MS SQL Server의 저장 프로시저에서 동적 SQL의 사용에 대해 살펴보고, 조심스럽게 다루어야할 여러 강력한 특징들에 대해 얘기하고자 한다. 그런 특징들에 대해 설명하기 전에, 우선 왜 저장프로시저를 사용해야 하는지에 대해 논의할 것이다. 그 다음에 저장 프로시저를 사용함으로써 얻는 장점과 동적 SQL 효과간에 충돌에 대해 얘기해볼 것이다. SQL Injection이라고 알려진 일반적인 보안 문제에 대해 언급하고, 몇가지 좋은 코딩 습관에 대해 알아보고자 한다. 마지막으로, 동적 SQL이 좋은 해결책으로 활용되는 경우와 그렇지 못한 경우에 대해 살펴볼 것이며, 후자의 경우에는 대신 사용가능한 방법을 제안하고자 한다.

목 차:

   왜 저장 프로시저를 사용하는가?
   EXEC()와 sp_executesql
      EXEC()
      sp_executesql
      어느 것을 사용해야 할까?
      커서(Cursors)와 동적 SQL
   동적 SQL과 저장 프로시저
   SQL Injection - 심각한 보안 문제
   좋은 코딩 습관과 동적 SQL
   동적 SQL을 사용(하지 말아야)하는 일반적인 경우
      select * from @tablename
      select * from sales + @yymm
      update tbl set @colname = @value where keycol = @keyval
      select * from @dbname + ''..tbl''
      select * from tbl where col in (@list)
      select * from tbl where @condition
      동적 검색 조건 (Dynamic Search Conditions)
      select * from table order by @col
      select top @n from table order by @col
      create table @tbl
      링크드 서버 (Linked servers)
      오픈쿼리 (OPENQUERY)
      열의 너비를 동적으로 변화시키고자 할 때 (Dynamic Column Widths)
   감사의 글


왜 저장 프로시저를 사용하는가?

동적 SQL이 실제로 무엇인가를 살펴보기 전에, 왜 저장 프로시저를 사용해야 하는지를 먼저 알아볼 필요가 있다. 저장 프로시저를 사용하지 않고 클라이언트(※ 역주: 여기서의 클라이언트는 SQL 서버에 대한 클라이언트를 의미합니다. 웹프로그램 개발의 경우 웹서버가 여기에 해당하며, 클라이언트 코드는 ASP와 같은 Server Side Script를 의미합니다.) 혹은 (COM+와 같은) 중간층(middle layer)에서 직접 SQL 문으로 이루어진 명령문을 내보내는 복잡한 응용프로그램을 작성할 수도 있다. 세련되어 보이기 때문에 저장 프로시저를 사용하는 것은 아니며, 이에는 분명 장점이 존재한다.

1. 권한 체계 (The Permission System)

저장 프로시저는 사용자들에게 데이타에 대한 제한적인 접근을 허용케하는 전통적인 수단이다. 쿼리 분석기와 같은 도구를 이용할 경우 원하는 어떠한 작업도 수행가능하므로, 사용자들은 SELECT, INSERT, UPDATE 혹은 DELETE 같은 문장을 직접 실행할 수 있는 권한을 가져서는 안된다. 예를 들어, 권한을 가진 사용자가 직원 데이타베이스에 접근하게 될 경우, 쉽게 봉급을 인상시킬 수 있다. 저장 프로시저를 사용하면, 해당 프로시저의 실행은 프로시저 소유자의 권한을 이용하게 되므로, 사용자들은 테이블에 대한 직접적인 권한을 필요로 하지 않게 된다.

요즘은 이 상황에 대해 몇가지 선택 가능한 사항이 더 존재한다. 사용자 계정에 직접 권한을 부여하기 보다는, 비밀번호가 필요한 고정 서버 역할(application role)에 접근권한을 부여할 수 있으며, 그런 비밀번호를 응용프로그램 안에 숨겨둘 수 있다. SQL 서버에서 고정 서버 역할은 7.0 버전부터 지원되었으며, COM+와 같은 중간층을 사용하면 사용자가 SQL 서버에 직접 접근할 필요가 없다는 점에서 더 안전하다.

하지만 고정 서버 역할이나 COM+ 등을 사용하지 않는다면, SQL 서버 보안 측면에서 저장 프로시저는 여전히 중요한 의미를 가진다.

2. 실행계획 캐쉬 (Caching Query Plans)

저장 프로시저를 사용하는 다른 중요한 이유는 성능을 향상시키기 위해서이다. 저장 프로시저가 최초로 실행되면, SQL 서버는 해당 프로시저에 대한 실행계획을 생성시키며, 이 실행계획이 캐쉬에 저장된다. 해당 저장프로시저가 재실행 요청을 받으면, SQL 서버는 저장된 실행계획을 재사용한다. 실행계획이 만료되거나 혹은 SQL 서버가 새로운 실행계획을 생성시켜야 할 이유가 생길 때까지 해당 실행계획은 캐쉬에 유지된다. (이 과정은 프로시저가 실행되는 동안에 일어나는데, 여기에 대한 논의는 이 기사의 범위를 범어난다.)

SQL 서버는 저장 프로시저 외부에서 실행되는 SQL 문장들에 대한 실행계획도 캐쉬한다. 자동 매개 변수화 과정(auto-parameterization) 또한 수행되어, 만약 다음과 같은 문을 실행시킬 경우, :

    SELECT * FROM pubs..authors WHERE state = ''CA''
    go
    SELECT * FROM pubs..authors WHERE state = ''WI''

SQL 서버가 쿼리를 다음과 같이 캐쉬하므로, 2번째 Select 문장은 첫번째 문장의 실행계획을 재사용한다.

   SELECT * FROM pubs..authors WHERE state = @1

SQL 문장이 좀더 복잡해 질 경우에는, SQL 서버가 자동 매개 변수화에 실패할 수도 있다. 심지어 유사한 쿼리문에서 공백 문자(white space)의 차이로 인해 SQL 서버가 캐쉬에서 해당 문장을 찾는데 실패하는 경우를 본 적도 있다. 결론은 SQL 서버가 순수 SQL 문장을 캐쉬하는데에도 좋은 성능을 발휘하지만, 저장 프로시저를 사용하는 경우 쿼리 실행계획의 재사용 확률이 더 높다는 것이다.

작고 짧은 실행시간을 가지며 자주 실행되는 SQL문 혹은 저장 프로시저의 경우에 캐쉬는 더욱 중요한 의미를 가진다. 실행계획을 만드는데 500ms 가 소요되면 상당한 과부하가 될 수도 있기 때문이다. 반면에, 20분동안 실행되는 프로시저의 실행계획을 세우는데 3초가 소요되는 것은 대단한 문제가 아니다. 만약, 아직도 SQL 6.5를 사용하고 있다면, 해당 버전의 SQL 서버는 순수한 SQL 문에 대한 캐쉬를 하지 않으며, 저장 프로시저에 대한 실행계획만을 캐쉬에 저장한다는 사실을 알아야 한다.

3. 네트워크 소통량 최소화 (Minimizing Network Traffic)

이것 역시 성능문제이다. 50줄 이상의 복잡한 Select 문장이 있으며, 각 실행시마다 Where 절 조건문이 조금씩 변한다고 가정해보자. 이 문장을 저장 프로시저에 넣을 경우, 네트워크를 통해 전달되는 데이타 소통량이 상당히 감소하게 되며, 해당 프로시저가 자주 실행될 수록 성능향상 효과가 증대된다.

서로 연관성을 가진 여러개의 SELECT / INSERT / UPDATE 문장으로 구성된 업무규칙이 존재할 경우 이러한 효과는 더욱 커진다. 저장 프로시저를 사용하면, 서버내부에서 이동하는 모든 데이타를 임시 테이블 혹은 변수에 저장할 수 있게 된다. 만약 순수 SQL 문장을 이용한다면, 서버와 클라이언트 혹은 중간층 사이에 데이타를 이동시켜야 한다. (사실, 이것이 전적으로 맞는 말은 아니다. SQL 문만을 사용해서도 임시 테이블을 생성시키고 거기에 데이타를 저장할 수 있다. 하지만 Connection pooling과 disconnected record sets에 대한 주의를 기울여야 한다.)

4. 출력매개변수 사용 (Using Output Parameters)

단일 행을 반환하는 SQL 문장을 실행시키고 싶을 경우에, SQL 문만 사용하면 결과 집합(record set)을 레코드셋으로 반환받아야 한다. 하지만, 저장 프로시저를 사용할 경우에는 성능이 월등한 출력매개변수의 사용이 가능하다. 단일 쿼리에 대한 실행시간의 차이는 무시할 수 있겠지만, 만약 다음과 같은 작업을 SQL 서버에 수천번 해야 한다면, 결과 집합으로 값을 받는 것에 비해 @key를 출력매개변수로 반환받는 경우의 이점은 엄청나게 커지게 된다.:

    INSERT tbl (...) VALUES (...)
    SET @key = @@identity

5. 업무규칙 모듈화 (Encapsulating Logic)

이것은 보안 혹은 성능향상과 관련된 것은 아니지만, 코드를 모듈화하는 방법의 하나이다. 저장 프로시저를 사용하면, SQL 문을 만들어내기 위해 클라이언트 코드와 씨름할 필요가 없다. 하지만, 이러한 이유때문에 저장 프로시저를 사용해야 한다고 말할 수는 없다. (SQL 코드가 클라이언트측 주요 언어의 문법에 묻혀 버리기는 하겠지만) 여전히 여러 매개변수들로부터 SQL 문을 조합하는 것이 가능하다.

좀 특별한 경우를 예로 들어 보겠다: 만약 쿼리 분석기 외의 다른 응용프로그램이 없다면, 관리자들을 위한 저장 프로시저를 작성하게 된다는 말이며, 이런 경우에는 저장 프로시저가 업무규칙 모듈화를 위한 유일한 수단이 된다.

6. 의존성 파악 (Keeping Track of what Is Used)

수백개의 테이블이 존재하는 복잡한 시스템에서, 간혹 어디에서 어떤 테이블 혹은 칼럼이 참조되었는지 알고 싶을 때가 있다. 이를테면, 만약 칼럼을 변경할 경우에 어떤 일이 일어날지 알고 싶은 경우가 있을 수 있다. 만약 모든 코드가 저장 프로시저에 보관되어 있다면, 참조된 개체를 찾기 위해 저장 프로시저의 코드만 살펴보면 된다. 또는 간단히 변경하고자 하는 칼럼 혹은 테이블을 누락시킨 데이타베이스를 생성시켜 어떤 일이 일어나는지 관찰할 수도 있다. 시스템 테이블 sysdepends와 시스템 저장 프로시저 sp_depends를 이런 목적에 사용할 수도 있지만, sysdepends내에 보관된 정보를 온전히 정확하게 유지시키기는 어렵다.

응용프로그램에서도 순수 SQL 문을 사용가능하도록 허용 한다면, 문제는 더욱 심각해진다. 훨씬 많은 양의 코드를 살펴봐야 하며, status와 같은 일반적인 이름을 가진 칼럼들은 놓치기도 쉽다. 그리고 sysdepends는 완전히 무의미하게 된다.


EXEC()와 sp_executesql

MS SQL 서버에서 동적 SQL을 실행시키는 2가지 방법은 EXEC()와 sp_executesql이다.

EXEC()

EXEC()는 다음 예제와 같이 그 사용법이 아주 간단하다.:

   SELECT @table = ''sales'' + @year + @month
   EXEC(''SELECT * FROM '' + @table)
비록 위의 예가 상당히 단순해 보이지만, 여기에는 놓쳐서는 안될 중요한 점이 존재한다. 첫번째 중요한 점은 비록 해당 문장이 저장 프로시저 내에 존재하더라도 현재 사용자의 권한으로 실행된다는 것이다. 두번째로 중요한 것은 EXEC()가 저장 프로시저의 실행을 위한 EXEC와 매우 유사하다는 점이다. 하지만 이 예제에서는 저장 프로시저를 호출하는 대신에, 단일 SQL 문을 일괄실행시켰다. 마치 저장 프로시저를 호출할 때처럼, 해당 일괄실행문은 호출하는 저장 프로시저와는 다른 실행범위(scope)를 가지게 된다. 여기에는 몇가지 중요한 의미가 내포되어 있다.:
  • SQL 일괄실행문 안에서는 호출하는 저장 프로시저의 지역변수 혹은 매개변수에 접근할 수 없다.
  • USE 문의 사용이 호출하는 저장 프로시저에 영향을 미치지 않는다.
  • SQL 일괄실행문에서 생성된 임시 테이블은 일괄실행문이 종료되면 삭제(drop)되기 때문에, 마치 저장프로시저가 종료된 경우와 마찬가지로, 호출하는 저장 프로시저에서 접근할 수 없다. 그러나, 일괄실행문 내부에서는 호출하는 저장 프로시저에서 생성된 테이블에 접근가능하다.
  • SQL 일괄실행문 내에서 SET 문장을 사용하면, SET 문장의 영향력은 일괄실행문 내부에서만 유지된다.
  • SQL 일괄실행문의 실행계획은 호출하는 저장 프로시저의 실행계획의 일부가 아니다. 해당 쿼리문의 캐쉬여부는 클라이언트 프로그램에서 SQL 문장만을 사용하는 경우와 동일하다.
  • SQL 일괄실행문이 (트리거내의 Rollback처럼) 일괄실행을 종료시키는 결과를 낳았을 때는, 동적 SQL의 일괄처리가 종료될 뿐만 아니라, 호출하는 저장 프로시저 (그리고 해당 프로시저를 호출한 다른 저장 프로시저도) 역시 종료된다.

정상적으로 저장 프로시저를 호출할 때와는 달리, EXEC()문에서는 매개변수 혹은 반환값을 사용할 수 없다. @@error 값은 일괄실행문의 마지막 문장의 실행결과에 관계가 있다. 그러므로, EXEC()내부에서 에러가 발생하더라도 뒤따르는 명령문이 성공적으로 수행되었다면, @@error는 0의 값을 가지게 된다.

EXEC()는 SQL 6.0에서 도입되었다.

EXEC(@sql)와 EXEC @sp를 혼돈하지 말아라. 후자는 이름이 @sp인 저장 프로시저를 실행시킨다.

sp_executesql

sp_executesql는 SQL 7에서 도입되었으며, 동적 SQL 문자열 내부로 입력과 출력을 위한 매개변수를 전달할 수 있다는 장점이 있다. 출력 매개변수를 사용하는 간단한 예는 다음과 같다. (※ 역주 : sysname은 nvarchar(128)과 같은 기능의 시스템 제공 사용자 정의 데이타 형식으로 데이타베이스 개체 이름을 참조할 때 사용됩니다.).:

   DECLARE @sql nvarchar(4000),
                 @col sysname,
                 @min varchar(20)
   SELECT @col = N''au_fname''
   SELECT @sql = N''SELECT @min = convert(varchar(20), MIN('' + @col + N'')) FROM authors''
   EXEC sp_executesql @sql, N''@min varchar(20) OUTPUT'', @min OUTPUT
   SELECT @min 

이 장점으로 인해, 동적 SQL 문을 사용할 때 EXEC()에 비하여 sp_executesql을 사용하면 지역변수로 값을 받아내기가 훨씬 쉬워졌다. (EXEC()에서도 INSERT EXEC()를 이용하여 동일한 작업을 수행할 수 있지만, 수월하지는 않다.)

sp_executesql의 첫번째 인자는 SQL 문(Unicode 문자열)으로, SQL 문법상 변수가 허용되는 곳에는 매개변수를 사용할 수 있다. (그러므로, 여전히 칼럼이름 혹은 테이블이름에 변수를 사용할 수는 없다). 매개변수의 데이타 형은 ntext이어야 하므로, nvarchar 형식의 변수를 사용하여야 한다. SQL 문이 상수로 전달되려면, Unicode 형식임을 나타내기 위해 N 접두어를 인용부호 앞에 붙여야 한다. SQL 문에는 @로 시작하는 매개변수가 포함될 수 있는데, 여기에 사용되는 매개변수들은 동적 SQL문의 외부에서 사용된 변수와는 전혀 별개의 변수이다. sp_executesql에서 사용되는 SQL 문에는 다른 곳에서 사용되는 SQL 문과 마찬가지의 문법이 적용되므로 여전히 칼럼명 혹은 테이블명에는 변수를 사용할 수는 없으므로, 동적으로 결정되는 경우에는 조합시킬 문자열 내에 포함시켜야 한다.

sp_executesql의 두번째 인자는 저장 프로시저 선언부와 마찬가지의 문법으로 매개변수 및 기본값이 정의된 매개변수 정의 목록이다. (온라인 도움말에는 매개변수에 대한 설명이 누락되어 있다.) 매개변수 정의 목록 역시 ntext 데이타 형이다. SQL 문장에서 사용된 모든 변수는 매개변수 선언목록에 명시되어야 한다.

sp_executesql의 나머지 인자는 매개변수 선언목록에서 선언된 것들로, 이들은 선언된 순서로 사용되거나 혹은 변수 이름과 같이 사용가능하다.

sp_executesql는 여러가지 이유로 인해 EXEC()보다 선호된다. sp_executesql 를 사용하면, 사용자가 직접 매개변수를 제공할 수 있기 때문에, SQL 서버가 매개변수 자동화를 수행하도록 의존할 필요가 없다는 것도 그 이유중의 하나이다. 그러므로, sp_executesql를 사용하면 캐쉬 사용의 가능성이 더 높아진다. (그래도 여전히 공백 문자의 사용은 주의를 기울여야 한다.) SQL injection좋은 코딩 습관에 대한 얘기를 하면서 sp_executesql의 다른 장점에 대해 다시 언급할 것이다.

EXEC()에 대해 얘기되었던 특징들은 sp_executesql에도 마찬가지로 적용된다.:

  • SQL 코드는 고유한 실행범위(scope)를 가지므로, 호출한 저장 프로시저에서 변수에 접근할 수 없다.
  • 현재 사용자의 권한이 적용된다.
  • USE 문장의 사용이 호출한 저장 프로시저에 영향을 미치지 않는다.
  • 호출한 저장 프로시저에서 SQL 일괄실행문에서 생성된 임시 테이블을 사용할 수 없다.
  • SQL 일괄실행문내에서 사용된 SET 문장은 일괄실행문 내에서 영향력을 가지지만, 호출한 저장 프로시저에는 영향을 미치지 않는다.
  • sp_executesql에 의해 실행된 일괄실행문이 종료되면 호출한 저장 프로시저도 종료된다.
  • @@error는 동적 SQL 코드 내부의 최종 실행문의 상태를 반영한다.

온라인 도움말(Books Online)에 의하면, sp_executesql은 성공했을 경우 0, 실패했을 경우 1의 반환값을 가지지만, 최소한 SQL 2000에서 반환값은 @@error의 값과 같다.

sp_executesql에 대한 더욱 자세한 정보는 온라인 도움말을 참조하기 바란다. KB Article 262499은 출력매개변수의 특징에 대해 기술하고 있다.

어느 것을 사용해야 할까? (Which to Use)

동적 SQL을 규칙적으로 사용하는 경우에는, sp_executesql이 최선의 선택이다. sp_executesql의 사용시에는, 실행계획이 재사용될 가능성이 높고, 매개변수를 사용할 수 있기 때문이다. 아직 SQL 6.5를 사용하고 있는 경우를 제외하면, EXEC()를 사용해야 하는 경우는 동적 SQL 문이 nvarchar(4000)의 범위를 넘어서는 경우 뿐이다. 다음과 같이 사용가능하다. :

    EXEC(@sql1 + @sql2)

T-SQL에서 저장 프로시저를 호출할 때 매개변수로 연산식을 사용할 수 없는 것처럼, sp_executesql를 호출할 때도 하나의 변수만 매개변수로 사용할 수 있다. 만약, 반드시 분리된 쿼리문을 써야 한다면, sp_executesql를 EXEC()내에 포함시켜 사용할 수 있다.:

   DECLARE @sql1 nvarchar(4000),
                 @sql2 nvarchar(4000),
                 @state char(2)
   SELECT @state = ''CA''
   SELECT @sql1 = N''SELECT COUNT(*)''
   SELECT @sql2 = N''FROM authors WHERE state = @state''
   EXEC(''EXEC sp_executesql N'''''' + @sql1 + @sql2 + '''''',
                                         N''''@state char(2)'''',
                                         @state = '''''' + @state + '''''''')

이런 경우에 인용부호('')를 여러번 겹쳐 사용하는 것이 혼란스럽다면 EXEC()만 사용할 수도 있다. (뒷부분에 제시될 사용자 정의함수(UDF; User Defined Function)인 quotestring()을 사용하면 이런 문제점을 해소할 수 있다.)

커서(Cursors)와 동적 SQL

커서는 자주 사용될 뿐만 아니라, 동적 SQL에서의 커서 사용에 대한 질문도 자주 접하게 되므로, 완성도 측면에서 예를 들도록 하겠다. DECLARE CURSOR EXEC()와 같이 사용할 수는 없지만, Declare Cursor문 전체를 동적 SQL에 포함시켜 사용하는 것은 가능하다.:

   SELECT @sql = ''DECLARE my_cur CURSOR FOR SELECT col1, col2, col3 FROM '' + @table
   EXEC sp_executesql @sql

위 쿼리의 실행에는 로컬 커서(local cursor)를 사용할 수 없다는 것에 주목하라.(로컬 커서는 EXEC(@sql) 문이 종료되는 시점에서 접근불가능하게 되어 버리기 때문이다.) Anthony Faull이 다음 예제에서와 같이 사용할 경우, 로컬 커서를 동적 SQL과 함께 사용할 수 있다고 지적해 주었다.

   DECLARE @my_cur CURSOR
   EXEC sp_executesql
            N''SET @my_cur = CURSOR FOR SELECT name FROM dbo.sysobjects; OPEN @my_cur'',
            N''@my_cur cursor OUTPUT'', @my_cur OUTPUT
   FETCH NEXT FROM @my_cur

명명된 커서(Named Cursors)처럼 커서 변수에 접근가능하며, 예제에서 보이는 바와 같이 매개변수로 전달가능하다.


동적 SQL과 저장 프로시저

저장 프로시저를 사용하는 이유와 동적 SQL을 사용할 때 어떤 일이 일어나는지에 대해 살펴보자. 다음과 같은 프로시저를 사용하는 것으로 시작한다.:

   CREATE PROCEDURE general_select @tblname nvarchar(127),
                                                     @key        key_type AS -- key_type is char(3)
       EXEC(''SELECT col1, col2, col3
                FROM '' + @tblname + ''
                WHERE keycol = '''''' + @key + '''''''')

앞으로 살펴 보겠지만, 이것은 전혀 의미없는 프로시저이다. 왜냐하면, 저장프로시저 사용시에 얻을 수 있는 거의 모든 장점을 살리지 못하기 되기 때문이다. 클라이언트 코드에서도 Select 문을 조합해서 SQL 서버에 바로 질의하는 것이 가능하다.

1. 권한 (Permissions)

사용자가 테이블에 직접 접근할 수 있는 권한이 없다면, 동적 SQL을 사용할 수 없다는 것은 너무나도 당연하다. 사용자가 Select 권한을 가지고 있는 환경도 있을 수 있다. 하지만, 권한이 문제되지 않는다는 것을 확신하지 못할 경우에는, 영구 테이블(permanent tables)에 대한 Insert, Update 및 Delete 문을 동적 SQL문에 사용해서는 안된다. 임시테이블을 사용하는 경우에는 아무런 권한 문제가 발생하지 않는다.

사용자들이 데이타베이스에 대한 직접적인 연결을 하지 않고, 고정 서버 역할(application roles)을 활용하거나 COM+와 같은 중간층을 사용하는 경우에는 이 문제에 대해 심각하게 생각할 필요는 없다. 하지만, SQL injection 절에서 여전히 고려해야할 다른 보안 문제에 대해 살펴볼 것이다.

Sysadmin 고정서버역할을 가진 사용자가 사용할 코드를 작성할 경우에는, 당연히 권한문제에 대해 걱정할 필요가 없다.

2. 실행계획 캐쉬 (Caching Query Plans)

살펴본 바와 같이, SQL 서버는 순수 SQL 문과 저장 프로시저 모두에 대해 실행계획을 캐쉬하지만, 저장 프로시저에 대한 실행계획을 재사용할 때 다소 더 정확하다. SQL 6.5 에서는 매실행시마다 재컴파일 되었기 때문에, 동적 SQL이 더 느리다고 확실히 말할수 있었으나, 그 이후 버전에서는 상황이 그렇게 명확하지는 않다.

앞에서 나왔던 general_select 프로시저를 보자. @tblname을 다르게 줄 경우, 실행계획은 캐쉬되고, @tblname에 대한 매개변수 자동화 과정이 일어난다. 이것은 클라이언트 코드에서 SQL 문을 생성시킨 경우에도 마찬가지이다.

이것이 의미하는 바는, 동적 SQL을 현명하게 사용한다면, 성능향상 효과를 얻을 수 있다는 것이다. 예를 들어, 저장 프로시저 내부에 복잡한 쿼리가 있고, 선호되는 실행계획이 실행 당시의 테이블에 있는 데이타에 의존한다고 가정하자. 해당 쿼리를 동적 SQL로 작성하고, SQL 서버가 충분히 똑똑해서 캐쉬된 정보를 전혀 쓰지 않기를 바랄 수 있다 (임시테이블이 사용되었다면, SQL서버가 똑똑하게 처리하지 못할 것이다.). 한편, 그 복잡한 쿼리를 각각의 목적에 맞는 저장 프로시저로 나누어서 같은 결과를 얻을 수도 있겠지만, 모든 로직이 한 군데에 위치한다면 코드는 훨씬 읽기 쉬울 것이다. 위의 내용은 사용자가 동적 SQL을 실행시킬 수 있도록 권한구성이 되어있을 경우를 가정하였다.(?)

3. 네트워크 소통량 최소화 (Minimizing Network Traffic)

앞의 두 절에서 저장프로시저내의 동적 SQL은 클라이언트에서의 평범한 SQL문에 비해 장점이 없다고 하였는데, 네트워크 소통량 문제에 대해서는 그렇지 않다. 저장 프로시저 내에 동적 SQL을 사용하면, 네트워크 비용이 들지 않는다. 예제 프로시져인 general_select의 사용시에는, 이러한 장점이 거의 없으며, 순수 SQL 코드의 크기와 저장 프로시져를 호출하기 위한 실행문의 크기가 거의 비슷하다.

그러나, 복잡한 조건에 따라 6개의 테이블을 조인시키는 복잡한 쿼리를 생각해 보자. 사용자가 필요로 하는 자료의 기간정보에 따라 필요한 테이블은 sales0101이 될 수도, sales0102 가 될 수도 있다. 사실 이런 테이블 디자인은 좋지 못한데, 이에 대해서는 다시 살펴보기로 하고, 아무튼 여러분이 이런 상황에 처해있다고 가정해 보자. 이런 문제를 해결하기 위해 동적 SQL과 저장프로시저내를 활용한다면, 전체 쿼리를 매번 질의하지 않고 기간정보만 매개변수로 전달하면 된다. 만약 쿼리가 시간당 한번 질의된다면, 이득은 무시할만 하겠지만 네트워크 사정이 그렇게 좋지 못한 환경에서 15초마다 한번씩 쿼리를 보내야 한다면 차이점을 느낄 수 있을 것이다.

4. 출력매개변수 사용 (Using Output Parameters)

출력매개변수를 얻을 목적만으로 저장프로시저를 사용하는 경우 동적 SQL의 사용과 별다른 관련성이 없다. 다른 말로, 클라이언트에서 직접 sp_executesql문을 사용할 수 있으므로, 저장 프로시저없이도 출력매개변수의 사용이 가능하다.

5. 업무규칙 모듈화 (Encapsulating Logic)

저장프로시저에 대한 이전 주제에서 다루어진 것들 외에 특별히 더할 내용은 없다. 그럼에도 불구하고 저장 프로시저를 사용하기로 결정한다면 SQL에 관련된 모든 숨겨야할 내용을 저장프로시저내에 포함시킬 수 있다는 점은 지적하고 싶다. 이러한 의미에서 general_select의 예에서 테이블 이름을 매개변수로 직접 전달하는 것은 좋지 못한 생각이다. (sysadmin 고정 서버 역할의 구성원을 위한 응용프로그램의 경우는 예외이다.)

6. 의존성 파악 (Keeping Track of what Is Used)

동적 SQL은 이 목적에 위배된다. 동적 SQL은 sysdepends를 사용하지 않으므로 참조되는 개체파악이 힘들어지며, 데이타베이스 내에 참조되는 개체가 존재하지 않는 경우에도 알아차리기 어렵다. 테이블이름 혹은 칼럼 이름을 매개변수로 사용하지 않을 경우에는, 어떤 테이블이 사용되었는지를 파악하려면 최소한 SQL 코드를 뒤져보는 작업을 해야 할 것이다. 그러므로, 동적 SQL을 사용할 때에는 테이블 이름과 칼럼 이름을 프로시켜 코드에 제한해서 사용하라.


SQL Injection - 심각한 보안 문제

SQL injection 은 공격자로 하여금 개발자가 의도하지 않은 SQL 문을 실행시킬 수 있게 하는 기술이다. 사용자가 입력한 값이 직접 SQL 코드로 전달될 때 (저장 프로시저에서 동적 SQL을 사용하거나 혹은 클라이언트 쪽에서 SQL문을 생성시키거나), SQL injection의 위험이 존재한다. 이 공격방법은 MS SQL 서버뿐만 아니라, 모든 관계형 데이타베이스 관리시스템(RDBMS)에 적용된다.

다음과 같은 저장 프로시저에 대해 생각해 보자.:

   CREATE PROCEDURE search_orders @custname varchar(60) = NULL,
                                                       @prodname varchar(60) = NULL AS
   DECLARE @sql nvarchar(4000)
   SELECT @sql = ''SELECT * FROM orders WHERE 1 = 1 ''
   IF @custname IS NOT NULL
      SELECT @sql = @sql + '' AND custname LIKE '''''' + @custname + ''''''''
   IF @prodname IS NOT NULL
      SELECT @sql = @sql + '' AND prodname LIKE '''''' + @prodname + ''''''''
   EXEC(@sql)

매개변수 @custname와 @prodname 에 대한 입력은 사용자 입력필드로부터 직접 전달되는 값이다. 여기서, 심술궂은 사용자가 @custname에 전달된 입력필드에 다음과 같은 값을 전달한다고 가정해보자.

   '' DROP TABLE orders --
결과로 만들어지는 SQL문은 다음과 같다.:
      SELECT * FROM orders WHERE 1 = 1  AND custname LIKE '''' DROP TABLE orders --''

붉은 색으로 표시된 문장이 보이는가? 이런 공격유형의 성공여부는 상황에 따라 다르다. SQL 서버에 직접 접속한 평범한 사용자가 테이블을 삭제(drop)할 수 있는 권한을 가진다고 보기는 어렵지만, 만약 그 사용자가 웹에서 접속한 사용자이고, 웹서버가 SQL서버에 관리자 권한으로 연결되어 있다면, 해당 공격은 성공하게 될 것이다. 이러한 정밀한 공격에 필요한 권한을 가지고 있지 않더라도, 공격자는 여전히 원하는 명령을 내릴 수 있는 수단을 갖게 된다.

공격자는 먼저 입력필드에 작은 따옴표('')를 넣었을 때 어떤 일이 일어나는지 살펴본다. 만약 문법 오류 (syntax error)가 발생한다면, 공격자는 취약점이 존재한다는 것을 알게 된다. 그런 다음, 공격자는 쿼리를 종료시킬 수 있는 다른 수단을 찾아내고, 결국 자기 자신이 작성한 SQL 명령을 더할 수 있게 된다. 마지막으로 공격자는 SQL 문장의 나머지를 무시하고 문법 오류를 피하기 위하여 주석 문자를 사용한다. 공격자가 세미콜론(;)과 같은 문자를 사용할 수도 있다. SQL 7 이후 버전에서는 세미콜론이 T-SQL 문장을 분리하기 위한 선택사항으로 사용된다. 세미콜론을 사용하여 오류가 발생한다면, 공격자는 general_select에서와 같은 문제점이 존재한다는 것을 알아차리게 된다. 만약 사용자가 입력한 값이 직접 매개변수 @tablename에 전달된다면, 다음과 같은 문장을 전달하는 것도 가능하다:

   some_table WHERE keycol = ''ABC'' DELETE orders

사용자가 직접 값을 넣을 수 있는 입력필드만 공격에 이용되는 것이 아니라는 것을 기억해야 한다. 저장 프로시저에 직접 전달되는 값이 URL에 포함되어 있다면, 공격자가 이것을 이용할 수도 있다.

이런 공격에는 기술도 필요하겠지만, 운도 많이 작용할 거라고 생각할 수도 있을 것이다. 하지만, 인터넷에는 시간이 널널한 수많은 공격자가 존재한다는 걸 기억해야 한다. SQL injection은 심각한 보안문제이며, 이에 대항하기 위한 방법을 알아야 하다. 이를 위한 2가지 방법이 존재한다.

  • 사용자에게 SQL 서버에서 필요한 권한 이상을 부여하지 마라. 응용프로그램이 중간층(middle layer)을 이용하여 SQL 서버에 접속한다면, 테이블에 대한 Select 권한만을 가지는 평범한 사용자 계정으로 접속케 해라. 경험이 적거나, 적당히 얼버무리는 개발자들이 SQL injection이 가능케하는 헛점을 만들 수 있기 때문이다.
  • 간단히 적용가능한 코딩 습관이 있는데, 다음 절 "동적 SQL을 위한 코딩 습관"에서 이에 대해 살펴볼 것이다.

SQL injection 문제는 저장 프로시저에 제한된 문제만은 아니라는 것을 강조해야 겠다. 문자열 변수는 종종 제한없이 사용가능하므로, 클라이언트 코드에서 SQL 명령문을 생성시켜 전달할 때 더 큰 취약점이 존재할 수도 있다. 저장 프로시저를 사용하는 경우에도, 호출하기 위해 EXEC문을 텍스트로 전달해야 한다는 것을 기억해야 한다. 여기에 SQL injection 공격이 가능한 취약점이 존재한다.


좋은 코딩 습관과 동적 SQL

동적 SQL을 사용하는 것이 어렵지 않게 보이겠지만, 작성한 코드에 대한 통제를 잃는 경우를 피하기 위한 규칙들이 존재한다. 주의를 기울이지 않는다면, 작성해 놓은 코드가 지저분해지거나 읽기 어렵게 되고, 문제해결을 위한 시도나 유지보수가 어렵게 된다. 무시무시한 프로시져 general_select을 다시 살펴보자:

   CREATE PROCEDURE general_select @tblname nvarchar(127),
                                                        @key       key_type AS -- key_type is char(3)
       EXEC(''SELECT col1, col2, col3
                 FROM '' + @tblname + ''
                 WHERE keycol = '''''' + @key + '''''''')

여기서 사용된 중복된 인용부호를 보고 이게 도대체 무슨 뜻이지?라며 자문할 수도 있을 것이다. SQL은 문자열 제한자를 문자열에 포함시키기 위해 해당 리터럴을 겹쳐써야 하는 언어중 하나이다. 따라서, 위의 네개의 작은 따옴표('''''''')는 하나의 작은 따옴표('')를 표현하기 위한 문자열 리터럴이다. 위에서는 간단한 예를 들었지만, 상황은 더 나빠질 수도 있다.

쉽게 저지르게 되는 다음과 같은 에러가 있다.:

    EXEC(''SELECT col1, col2, col3
              FROM'' + @tblname + ''
              WHERE keycol = '''''' + @key + '''''''')

FROM 다음에 공백이 생략된 것이 보이는가? 해당 프로시저를 컴파일 할때는 에러메시지가 보이지 않지만, 실행시키려로 하면 열 이름 ''col1''이(가) 잘못되었습니다., 열 이름 ''col2''이(가) 잘못되었습니다., 열 이름 ''col3''이(가) 잘못되었습니다., 열 이름 ''keycol''이(가) 잘못되었습니다.라는 에러메세지를 접하게 된다. 그러면, 입력된 테이블 이름이 정확하므로 칼럼이름이 잘못된 것으로 오해하게 되어, 혼란이 가중된다. 아래는 매개변수가 fooabc일때 실제로 생성된 코드이다.:

   SELECT col1, col2, col3 FROMfoo WHERE keycol = ''abc''
FROMfoocol3 칼럼에 대한 별칭(alias)으로 해석되므로, 문법적인 오류가 아니다.

앞서 테이블 이름이나 칼럼 이름을 매개변수로 사용해서는 안된다는 의견을 제시했었다. 그러나 여기는 좋은 코딩 습관에 관한 절이므로, 한번 더 강조하겠다. 저장 프로시저를 작성하면, 해당 프로시저는 SQL 개체를 참조하는 독점적인 공간이 된다. (save stored procedures that is! ?) 그럼에도 불구하고, 아래에 동적 SQL에 대한 좋은 코딩 습관의 장점을 보여주도록 general_select을 개선해 보았다.:

   CREATE PROCEDURE general_select @tblname nvarchar(127),
                                                        @key key_type,
                                                        @debug bit = 0 AS
   DECLARE @sql nvarchar(4000)
   SET @sql = ''SELECT col1, col2, col3
                      FROM '' + quotename(@tblname) + ''
                      WHERE keycol = @key''
   IF @debug = 1 PRINT @sql
   EXEC sp_executesql @sql, N''@key key_type'', @key = @key

보시는 바와 같이, 몇가지를 수정하였다:

  • @tblname이 SQL injection에 쓰이는 걸 방지하기 위해 quotename()을 사용하였다. quotename()에 대한 자세한 내용은 아래를 참조하라.
  • 매개변수 @debug를 첨가해서, 예기치 못한 에러가 나타나는 경우에는 @key를 사용하여 SQL 코드가 어떻게 작성되었는지 쉽게 나타낼 수 있다.
  • 문자열내에 @key 값을 포함시키지 않고, sp_executesql를 사용하고 @key를 매개변수로 전달하였다. 이런 방식은 SQL injection에 대한 대비도 된다.

quotename()은 SQL 7에서 처음 도입된 내장함수(built-in function)이다. 해당 함수는 구분 식별자가 되도록 추가된 구분 기호와 함께 유니코드 문자열을 반환한다. 이 함수의 제공목적이 원래 개체 이름을 인용하기 위한 것이므로, 기본 구분자는 각괄호(squeare brackets; [])이지만, 작은 따옴표('') 혹은 큰 따옴표(")로 지정할 수도 있다. 그러므로, EXEC()를 사용해야 할 때는 SQL injection을 막기 위해 quotename()을 사용할 수 있다. SQL injection절에서 예로 들었던 search_orders 프로시저에서 몇줄을 다음과 같이 수정할 수 있다.:

   IF @custname IS NOT NULL
      SELECT @sql = @sql + '' AND custname LIKE '' + quotename(@custname, '''''''')

quotename() 함수에 눈여겨 볼만한 점이 하나 더 있다.: quotename()의 입력인자는 nvarchar(129)이므로, 긴 문자열을 대상으로는 사용하지 못한다. SQL 2000에서는 다음과 같은 사용자 정의 함수를 사용할 수 있다.:

   CREATE FUNCTION quotestring(@str nvarchar(1998)) RETURNS nvarchar(4000) AS
   BEGIN
      DECLARE @ret nvarchar(4000),
                    @sq  char(1)
      SELECT  @sq = ''''''''
      SELECT  @ret = replace(@str, @sq, @sq + @sq)
      RETURN(@sq + @ret + @sq)
   END
사용법은 아래와 같다.:
   IF @custname IS NOT NULL
      SELECT @sql = @sql + '' AND custname LIKE '' + dbo.quotestring(@custname)

SQL 7에서는, 사용자정의 함수가 제공되지 않으므로, quotestring을 저장 프로시져로 만들어야 한다. SQL 6.5에서는 replace() 함수가 제공되지 않으므로, 별 다른 대안이 없다. (SQL 서버 MVP인 Steve Kass가 quotename() 혹은 사용자 정의함수에 대해 제안해주었음을 밝힌다.)

중복된 인용부호로 인해 야기되는 지저분함을 피하기 위한 다른 대안은, T-SQL이 큰 따옴표(")를 지원한다는 사실을 이용하는 것이다. QUOTED_IDENTIFIER를 OFF로 설정하면, 문자열 구분자로 큰 따옴표("))를 쓸 수 있다. 이 설정에 대한 가본값은 컨텐스트에 좌우되는데, 선호되는 설정값은 ON으로, 인덱스된 뷰(Indexed Views)와 계산된 열(computed column)에 대한 인덱스를 사용하기 위해서는 이 값이 반드시 ON으로 설정되어야 한다. 그러므로, 이 방법이 가장 좋은 해결책은 아니지만, 경고메시지를 보는게 싫을 경우에는, 다음과 같이 사용 가능하다.:

   CREATE PROCEDURE general_select @tblname nvarchar(127),
                                                        @key       key_type,
                                                        @debug   bit = 0 AS
   DECLARE @sql nvarchar(4000)

   SET @sql = ''SET QUOTED_IDENTIFIER OFF
                      SELECT col1, col2, col3
                      FROM '' + @tblname + ''
                      WHERE keycol = "'' + @key + ''"''
   IF @debug = 1 PRINT @sql
   EXEC(@sql)

두가지 다른 인용 부호가 쓰였으므로, 해당 코드의 가독성이 높아진다. SQL 문장을 위해 작은 따옴표가 쓰였고, 포함된 문자열 리터럴로 큰 따옴표가 사용되었다.

SQL injection에 대해 보호되지 못하므로, 이 방식은 sp_executesqlquotename()을 쓰는것 보다는 좋지 못한 방법이다. 그러나 sysadmin 을 위한 작업인 관계로 SQL injection이 문제되지 않을 경우에는 사용가능하며, SQL 6.5 환경에서는 아마도 최선의 방법일 것이다.

이 절에서 제시된 가이드라인을 따르더라도, SQL 코드에 동적 SQL을 사용함으로써 야기되는 복잡성은 상당하다. 따라서, 사용하기 전에 반드시 시용해야 하는지 재고해보기 바란다는 말로 끝을 맺겠다.


동적 SQL을 사용(하지 말아야)하는 일반적인 경우 (Common Cases when to (Not) Use Dynamic SQL)

SQL 서버에 관한 여러 뉴스그룹에서, 거의 매일 간단한 예와 함께 동적 SQL을 사용하라는 답변을 받는 사람들이 종종 있다. 하지만, 답변하는 사람들 조차도 권한 및 캐싱에 관련된 숨겨진 의미를 말해주는 것을 잊곤 한다. 많은 경우에 이런 질문들에 대해 동적 SQL이 유일한 해법이기는 하지만, 실제로는 완전히 다른 – 그러고 훨씬 더 좋은 – 해결책이 존재하는 경우도 있다.

이 절에서는 동적 SQL을 사용할 수 있는 몇가지 경우와, 동적 SQL이 적절한 해결책인 경우에 대해 살펴볼 것이다. 그리고, 다른 한편으로 동적 SQL이 좋지 못한 선택인 경우에 대해서도 살펴볼 것이다.

select * from @tablename

일반적인 질문은 왜 다음 쿼리가 작동하지 않는지에 관한 것이다:

   CREATE PROCEDURE my_proc @tablename sysname AS
      SELECT * FROM @tablename

이러한 경우에 동적 SQL을 이용하여 해결할 수 있다는 것을 이미 알고 있지만, 이런 식의 저장 프로시저의 사용은 의미없는 일이라는 것도 또한 알고 있다. 만약 SQL 프로그래밍을 이렇게 한다면, 저장 프로시저를 사용하기 위해 골치아파할 필요가 전혀 없다.

사람들이 이러한 작업을 하고 싶어하는데는 몇가지 이유가 있어 보인다. C++, VB등 다른 프로그래밍 언어에서의 경험이 있으나 SQL 프로그래밍이 처음인 사람들이 보통 이런 식으로 작업을 많이한다. (※ 역주 : ASP개발자들도 마찬가지죠 ^^;) 테이블 이름을 매개변수로 사용하는 것은 재사용 가능한 범용 코드(Generic Code)를 만들기 위해서, 그리고 유지보수 편의성을 높힌다는 측면에서 환영할만한 방식이다.

그러나 데이타베이스 개체에 대해서는, 이 오래된 진리가 통하지 않는다. 개발자는 각각의 테이블과 칼럼들을 유일하고 고정적인 개체로 보아야 한다. 왜 그럴까? 실행계획을 세울 때, 각각의 테이블은 고유의 통계값과 추정치를 가지고 있으며, SQL 서버에서 이러한 값은 상호 교환 가능한 값이 아니다. 복잡한 데이타 모델에서는, 현재 무엇이 사용되고 있는 가를 파악하는 것이 중요하다. 테이블 이름과 칼럼이름을 매개변수로 사용한다면, 이러한 관계를 파악하기기 어려워 진다.

코딩하면서 타이프하는 수고를 덜기 위한 목적으로 이런 식의 작업이 하고 싶다면(SELECT * 같은 코드는 실제 생산환경에서 사용되어서는 안된다는 것을 기억하라), 그것은 잘못된 선택이다. 이런 경우에는 서로 유사하더라도, 이름이 다른 10개 혹은 20개의 저장 프로시저를 작성하는 것이 훨씬 더 좋은 방법이다.

만약 SQL 문이 너무 복잡해서, 서로 다른 테이블들이 사용되더라도 한 군데서 관리하는 것이 유지보수 측면에 상당한 장점이 있다면, 고려될 수 있는 다른 실용적인 방법이 있다 : C/C++과 같은 전처리기를 사용하는 것이다. 테이블당 하나의 프로시저가 존재하더라도, 코드는 하나의 파일로 만들 수 있다.

select * from sales + @yymm

앞에서 든 예의 변형에 해당한다. 차이점은 앞 절에서는 유한한 갯수의 테이블이 있는 것으로 가정했다는 것이다. 만약 테이블들이 동적으로 생성되는 시스템이라면 어떻게 할까? 예를 들어 판매 자료를 위한 테이블이 매달 생성된다면? 이런 경우에, 테이블당 하나의 저장 프로시저를 생성시킨다는 것은 전처리기를 사용하더라도 사실상 불가능하게 된다.

그렇다면, 다른 대안이 없으므로 그냥 동적 SQL을 사용해야 할까? 아니다. 되돌아가서 이 상황을 다시한번 살펴보자. 사실 처음부터 잘못된 접근법이 사용되었다. 데이타 모델에 명백한 결함이 존재하는데, 월별로 하나의 테이블을 사용하는 것은 Access를 사용하는 시스템 혹은 파일 데이타 시스템에서 성능을 향상시키기 위해 사용가능한 방법이다. SQL 서버 혹은 기타 고급 RDBMS에서 이렇게 해야할 이유는 거의 존재하지 않는다. SQL 서버 혹은 그 경쟁제품은 막대한 양의 데이타를 처리하고 그 데이타를 키를 이용하여 효율적으로 관리하기 위해 고안되었다. 연(year) 혹은 월(month)은 sales 테이블의 PK(Primary Key)를 구성하는 요소일 뿐이다.

만약, 선임자로부터 이러한 시스템을 인계받은 경우에는, 리모델링을 위해 막대한 비용이 필요할 경우도 있다. (하지만 동적 SQL을 사용하므로써 발생되는 복잡한 코드에 소요되는 비용 또한 무시하지 못한다.) 만약 새로운 시스템을 개발하고 있다면, 동적으로 생성되는 테이블에 대해서는 잊어버려라. 그러한 테이블에 접근하거나 업데이트하기 위한 코드가 상당히 지저분해 질 것이다. 이를 테면 전자상거래 시스템에서 각 장바구니당 하나의 테이블을 생성시키는 것처럼 이러한 테이블을 자주 생성시킨다면, 시스템 테이블에 핫 스폿(※ 역주 : Hot Spot은 많은 Query들이 동시에 동일한 영역의 디스크에 데이터를 읽거나 쓰려고 하는 경우에 발생합니다. 이는 하드 디스크가 동시에 처리할 수 있는 것보다 많은 디스크 I/O 요청들을 받게 되기 때문에, 디스크 I/O 병목현상(Bottleneck)을 유발하게 됩니다. 참고: KB 601427)을 유도해 성능에 악영향을 미칠 수도 있다.

수백만개의 데이타가 있는데, 한 테이블에 모든 데이타를 저장해두면, 데이타베이스가 작동하지 않을꺼야 라며 아직도 수긍하지 못하고 궁시렁거릴 독자가 있을 것이다. 좋다. 테이블에 정말로 많은 행(rows)이 존재한다고 치자. 신경쓸 일이 많지? 그치만, 그건 수백만개의 데이타때문이 아니라, SQL 서버 관리를 위해 매일 해야 하는 당연한 업무이다. (인덱스가 현명하게 정의되었다고 가정한 것이다.) 일억개 이상의 행이 존재한다면, 고려해야할 다른 문제가 생긴다. 이런 목적을 위해, SQL 2000은 분할 뷰(partitioned views) 혹은 분산분할 뷰(distributed partitioned views)와 같은 몇가지 특성을 지원한다. 분할 뷰 혹은 분산분할 뷰를 이용하면, 큰 데이타 집합을 몇개의 테이블로 나눌 수 있고, 마치 하나의 테이블처럼 접근할 수 있게 해준다. (주의 : 정확한 표현을 위해서는 행의 갯수가 아닌 테이블의 크기(total size)에 대해 언급해야 한다. 물론 테이블의 크기는 행의 평균 크기와 밀접한 관계가 있다.)

update tbl set @colname = @value where keycol = @keyval

이 경우는, 실행시간에 선택되는 칼럼에 대한 update가 필요한 경우이다. 위의 T-SQL은 문법에 어긋나지 않지만, 실제 일어나는 일은 테이블에서 keycol의 값이 @keyval인 행들의 @value값이 변수 @colname에 대입되는 것 뿐이다.(※ 역주 : 실제로 실행시켜보면 에러메시지가 표시되지는 않지만, 테이블의 해당 레코드에 대한 update가 수행되는 것이 아니라, @colname 변수에 할당된 값이 update될 뿐입니다.)

이 경우에 동적 SQL을 사용하려면 사용자는 테이블에 대한 Update 권한을 갖고 있을 것이 요구된다. 이런 권한 설정은 가볍게 볼 수 있는 문제가 아니며 가능하면 피해야 하는 구성이다. 여기에는 상당히 간단한 해결책이 존재한다.:

   UPDATE tbl
   SET    col1 = CASE @colname WHEN ''col1'' THEN @value ELSE col1 END,
             col2 = CASE @colname WHEN ''col2'' THEN @value ELSE col2 END,
             ...
Case에 익숙하지 않다면, 온라인 도움말을 참조하기 바란다. Case는 SQL의 상당히 강력한 특징중 하나이다.

여기서 왜 사람들이 이런 식의 작업을 하고 싶어하는지 살펴보자. 아마도 테이블이 다음과 같은 구조를 갖고 있어서일 것이다.:

    CREATE TABLE products (prodid   prodid_type NOT NULL,
                           prodname name_type   NOT NULL,
                           ...
                           sales_1  money       NULL,
                           sales_2  money       NULL,
                           ...
                           sales_12 money       NULL,
                           PRIMARY KEY (prodid))

이 경우에는 테이블을 분리하여 자식 테이블의 sales_n 칼럼을 이용하는 것이 보다 합리적이다.:

    CREATE TABLE product_sales (prodid prodid_type NOT NULL,
                                                month  tinyint         NOT NULL,
                                                sales   money         NOT NULL,
                                                PRIMARY KEY (prodid, month))

select * from @dbname + ''..tbl''

이 경우는 테이블이 동적으로 결정되는 다른 데이타베이스에 있는 경우이다. 이런 작업방식에는 여러가지 이유가 있으며, 왜 이렇게 작업해야 하는가 하는 이유에 따라 해결책이 다르다.

다른 데이타베이스에서의 데이타 획득 만약 응용프로그램에서 사용하는 데이타가 어떤 이유로 2개이상의 데이타베이스에 분산되어 있다면, 데이타베이스 이름을 코드에 직접 참조시켜 고생할 필요가 없다. 왜냐하면, 테스트 환경에서 같은 서버에 존재하는 데이타베이스 이름이 실제 환경에서는 다른 서버에 존재할 수도 있기 때문이다. 이런 경우에는 설정 테이블에 다른 데이타베이스의 이름을 넣어두고 동적 SQL을 활용하는 것도 좋은 아이디어이지만, 다른 해결책 역시 존재한다. 만약 다른 데이타베이스에 대한 작업이 해당 저장프로시저 내에서 가능하다면, 다음과 같은 할 수 있다:

   SET @sp = @dbname + ''..some_sp''
   EXEC @ret = @sp @par1, @par2...

저장 프로시저의 이름이 변수 @sp 값에 들어있다.

모든 데이타베이스를 대상으로 작업 이 경우는 아마도 sysadmin 고정서버 역할에 속한 사용자가 수행하는 작업일 것이다. 이런 경우에는 권한문제 혹은 캐쉬에 신경쓸 필요가 없기 때문에, 대개의 경우에 동적 SQL은 적절한 선택이다. 그럼에도 불구하고 다음 예제에서의 sp_MSforeachdb 같은 대안이 존재한다.:

    sp_MSforeachdb ''SELECT ''''?'''', COUNT(*) FROM sysobjects''

추측하는 바와 같이, sp_MSforeachdb는 동적 SQL 을 내부적으로 사용하므로, 개발자가 일일이 루프(loop) 코드를 작성하지 않아도 되는 장점이 있다. 덧붙여 말하고 싶은 것은, sp_MSforeachdb가 온라인 도움말에서 누락된 함수라는 점이다. 온라인 도움말에서 누락된 함수를 사용했을 때 문제가 발생하면 Microsoft로부터의 기술지원을 받을 수 없다.

"마스터" 데이타베이스 간혹, 동일한 테이블 구조를 가진 여러개의 데이타베이스를 관리하는 경우를 볼 수 있다. ASP 서비스(provider service)를 제공하거나, 혹은 고객 각각에 대해 별도의 데이타베이스가 존재하는 경우에 해당하며, 사업적인 이유로 모든 고객에 대한 데이타를 하나의 데이타베이스에 두는 것이 불가능하다. 이런 경우에 관리자들은 모든 데이타베이스에 대한 유지보수가 쉽지 않다고 느끼게 되며, 결과적으로 필요한 모든 프로시저를 저장해둘 "마스터" 데이타베이스가 필요하게 된다. 그러나, "마스터" 데이타베이스에 존재하는 저장프로시저는 동적 SQL을 필요로 하며, 또다른 끔찍한 유지보수 문제를 낳게 된다.

2가지 방법이 있는데, 하나는 SQL 서버에 기본 제공되는 Master 데이타베이스를 이용하여 사용자가 작성한 프로시저를 시스템 프로시저로 설치하는 것이다. (※ 역주: master 데이타베이스에 접두어 "sp_"로 시작하는 사용자 저장프로시저를 작성해 두면, 마치 시스템 저장프로시저처럼 다른 데이타베이스에서 사용가능하게 됩니다.) 하지만, 이렇게 하면 Microsoft에서 기술지원을 기대할 수 없으며, 보안 측면에서의 문제점도 존재하므로, 권장하고 싶은 방법은 아니다.

다른 방법? 저장 프로시저를 각각의 데이타베이스에 설치하고, SQL 개체에 대한 배포 루틴(Rollout Routines)을 개발하는 것이다. 테이블을 변경해야 할 필요가 생길 것으므로, 결국에는 이 방법이 필요하게 될 것이다. 게다가 개별 데이타베이스에 저장 프로시저를 만들어두면, 새로운 버젼으로 업그레이드하기를 꺼리는 고약한 고객들에 대한 대응도 가능해지며, 까다로운 고객을 위해 특정 목적에 맞는 프로시저를 제작하는 것이 가능해진다. 배포 루틴을 적용하는 방법은 설징 유지관리(configuration management)에 관한 주제까지 다루어야 하며, 이 기사의 범위를 벗어난다. 이에 대해서는 2가지 단서만 제공하겠다. SQL Server Resource Kit에 들어있는 Stored Procedure Builder를 사용하면, Visual SourceSafe를 이용하여 SQL 개체를 설치하는 것이 가능해진다. 내 경우에는 고객들에 대한 기술지원을 제공하기 위해 AbaPerls라는 툴을 몇년 동안 개발했으며, http://www.abaris.se/abaperls/에서 구할 수 있다. 이 툴은 프리웨어이다.

select * from tbl where col in (@list)

매우 흔한 질문으로, 동적 SQL을 사용하세요가 또한 일반적인 답변이다. 하지만 이 질문에 대해 동적 SQL을 사용하라는 것은 분명히 잘못된 답변으로, 이런 종류의 Select 권한이 필요하지도 않으며, @list에 많은 요소가 포함될 경우에는, 동적 SQL을 사용할 경우 틀림없이 성능저하를 경험하게 된다.

대안? 사용자 정의함수 혹은 저장프로시저를 사용해서 입력되는 문자열을 테이블에 나누어 저장해라. 이 기사에는 적당한 예가 포함되어 있지 않지만, 또다른 기사인 Arrays and Lists in SQL Server에서 이러한 문제를 다루는 방법과 여러 방법론들의 성능차이에 대한 자료를 제시하겠다. (제시된 방법들중 동적 SQL이 가장 하위에 있다!) 해당 기사는 매우 긴 분량으로, 기사의 첫부분에 각 SQL 버전에 적합한 내용으로 분기할 수 있는 링크를 제시하였다.)

select * from tbl where @condition

다음과 같은 프로시저를 작성하려 한다고 가정해 보자.

   CREATE PROCEDURE search_sp @condition varchar(8000) AS
      SELECT * FROM tbl WHERE @condition

그냥 관둬라. 이런 작업을 하고 있다면 저장 프로시저를 어중간한 상태로 사용하고 있는 것으로, 개발자가 여전히 클라이언트에서 SQL 코드를 조합하고 있는 것이다. 이 예는 다음 주제와 관련있다.

동적 검색 조건 (Dynamic Search Conditions)

사용자가 광범위한 매개변수로 부터 데이타를 검색하는 것은 드문 경우가 아니다. 입력 매개변수 각각의 조합에 대하여 최적화된 쿼리를 작성해내는 정적인 해결책을 만들어내는 것이 불가능하다는 데에는 논쟁의 여지가 없다. 그리고, 대부분의 프로그래머들이 모든 조건들을 "똑똑한" SQL을 이용하여 하나의 쿼리로 묶어낸 경우에 좋은 효율을 보여주리라고 기대하지도 않는다.

이런 유형의 문제에는 동적 SQL이 분명 더 좋은 해결책이다. 권한 문제만 해결할 수 있다면, 동적 SQL을 사용하는 것이 성능 및 유지비용 보수면에서 더 낫다. 분리된 다른 기사 동적 검색 조건 (Dynamic Search Conditions) 에서 동적 SQL을 사용하는 방법과 사용하지 않고 구현하는 방법에 대한 예를 들어보겠다.

select * from table order by @col

이 경우는 동적 SQL을 사용하지 않고 다음과 같이 처리하는 것이 가능하다:

     SELECT col1, col2, col3
     FROM   tbl
     ORDER  BY CASE @col1
                           WHEN ''col1'' THEN col1
                           WHEN ''col2'' THEN col2
                           WHEN ''col3'' THEN col3
                       END

다시 한번 말하지만, 이해가 가지 않는다면 온라인 도움말의 Case 표현을 참조하라.

열(column)의 데이타 형태가 다른 경우에는 하나의 Case 표현식으로 나타낼 수 없다는 점을 기억하라. 이런 경우에는 다음과 같이 처리할 수 있다.:

     SELECT col1, col2, col3
     FROM   tbl
     ORDER  BY CASE @col1 WHEN ''col1'' THEN col1 ELSE NULL END,
                      CASE @col1 WHEN ''col2'' THEN col2 ELSE NULL END,
                      CASE @col1 WHEN ''col3'' THEN col3 ELSE NULL END

이 주제에 대해 SQL Server MVP인 Itzik Ben-Gan이 SQL Server Magazine 2001년 3월호에서 투고한 좋은 기사에서 다른 해결책을 제시하였다.

select top @n from table order by @col

동적 SQL을 사용하지 않는 간단한 방법이 아래에 나와있다.:

     CREATE PROCEDURE get_first_n @var int WITH RECOMPILE AS
     SET ROWCOUNT @var
     SELECT *
     FROM   authors
     ORDER  BY au_id
     SET ROWCOUNT 0

SQL 옵티마이저는 SET ROWCOUNT 옵션 설정값을 무시한다고 배웠을 수 있다. TOP이 제공되지 않아 다른 대안이 존재하지 않았던 SQL 6.5에서는 이 말이 사실이었다. 하지만 SQL 7과 SQL 2000에서는 그렇지 않다. 그러므로, SET ROWCOUNT의 입력값으로 사용하기 위해 매개변수(지역변수가 아닌)를 주의해서 사용하지 않는다면, 옵티마이저는 그 값을 인식하지 못하고 테이블 스캔을 하려고 할 것이다.

해당 프로시저 내의 다른 SQL문에도 영향을 미치게 되므로, Select 문 다음에 SET ROWCOUNT 0을 사용해야 한다는 점도 주의하라.

온라인 도움말에 보면 SET ROWCOUNT에 대한 참고 사항이 나와있는데, SET ROWCOUNT를 Delete, Insert 및 Update 문과 함께 사용하는 것을 권장하지 않는다. 정확히 왜 그런지는 잘 모르겠으나, SET ROWCOUNT 옵션이 설정된 상태에서 임시 테이블에 대한 INSERT는 괜찮다는 것을 제안하고 싶다.(?) ROWCOUNT 옵션 설정값은 트리거에도 영향을 미치기 때문에, 트리거가 걸린 테이블에 대한 Insert는 엉뚱한 결과를 일으킬 수 있다.

왜 이 기능을 사용해야 하는지 살펴보는 것은 의미있는 일이 될 것이다. 만약 웹페이지에 출력할 경우, 한번에 500개의 행을 읽어들여서 전체 데이타베이스를 뒤지지 않게 하는 것은 좋은 전략이다. 사용자는 다음 화면을 볼 때 "Next" 버튼을 클릭하면 된다. (개인적으로는, 한번에 보여지는 결과를 10-20개로 제한해놓은 웹사이트를 좋아하지 않는다.)

create table @tbl

이 경우는 권한 혹은 캐쉬와 관련된 문제가 없으며(동적 SQL을 사용하지 않더라도, 저장 프로시저에서 사용자가 테이블 생성권한을 필요로 하기 때문이다.) 의존성 문제와도 관련이 없다. 이 목적으로 동적 SQL을 사용하는 것에 대한 별다른 논쟁거리는 없다.

그럼에도 불구하고 여전히 다음과 같은 질문은 남아있다: 왜? 왜 이런 식으로 작업해야 하는가? 서로 유사한 일련의 테이블을 생성시킬 필요가 있는 관리자용의 스크립트 제작을 위한 작업인 경우는 의미있다. 하지만 응용프로그램에서 실시간으로 테이블을 생성시켜야 한다면, 이것은 데이타베이스 디자인의 기본사항을 망각한 것이다. 관계형 데이타베이스에서 테이블 및 칼럼은 고정적인 개체인 것으로 가정된다. 새로운 버전의 설치시에는 변경될 수 있겠지만, 실행시간에 변경되어서는 안된다. select * from sales + @yymm에서 이 주제에 대해 살펴보라.

간혹 임시로 사용되는 테이블의 이름을 유일하게(unique) 주기 위해 이런 작업방식을 사용하는 사람을 보는 경우도 있는데, 이것은 SQL 서버에서 기본적으로 제공되는 기능으로, 불필요한 작업이다. 다음과 같이 사용하면 된다:

   CREATE TABLE #nisse (a int NOT NULL)

실제 테이블 이름은 보여지는 것보다 훨씬 긴 이름이 사용되며, 다른 사용자는 #nisse의 인스턴스를 들여다볼 수 없다.

Disconnected record sets를 사용하거나 혹은 임시 테이블을 사용할 수 없는 경우, 연결(connection)에 대해 유일한 테이블을 사용하고 싶다면 모든 클라이언트가 공유할 수 있고, 각각의 클라이언트를 의미하는 키값을 가지는 칼럼을 추가한 영구 테이블을 사용하는 편이 더 낫다.

Linked servers

이것은 데이타베이스 이름을 변수로 사용하고자 하는 문제와 유사하지만, 해결책은 다르다. 연결된 서버에 저장 프로시저를 정의하는 것이 가능하다면, 저장 프로시저의 이름을 동적으로 사용하는 것 또한 가능하다.:

   SET @sp = @server + ''db.dbo.some_sp''
   EXEC @ret = @sp @par1, @par2...

로컬에 존재하는 테이블과, 연결된 서버의 유동적으로 결정되는 원격 테이블에 대해 조인작업을 수행하고 싶다면, 동적 SQL이 아마도 가장 좋은 방법일 것이다.

비록 특정 환경에서 사용가능한 방법이긴 하지만 여기에도 여전히 대안이 존재한다. 다음에서 인용된 예와 같이, sp_addlinkedserver에서 별칭을 만들 수 있다.:

   EXEC sp_addlinkedserver MYSRV, @srvproduct=''Any'',
                                                    @provider=''SQLOLEDB'', @datasrc=@@SERVERNAME
   go
   CREATE PROCEDURE linksrv_demo_inner WITH RECOMPILE AS
      SELECT * FROM MYSRV.master.dbo.sysdatabases
   go
   EXEC sp_dropserver MYSRV
   go
   CREATE PROCEDURE linksrv_demo @server sysname AS
      IF EXISTS (SELECT * FROM master..sysservers WHERE srvname = ''MYSRV'')
         EXEC sp_dropserver MYSRV
      EXEC sp_addlinkedserver MYSRV, @srvproduct=''Any'',
                                                       @provider=''SQLOLEDB'', @datasrc=@server
      EXEC linksrv_demo_inner 
      EXEC sp_dropserver MYSRV
   go
   EXEC linksrv_demo ''Server1''
   EXEC linksrv_demo ''Server2''

2개의 프로시저가 사용되었는데, 내부의 프로시저는 실행시간에 조회하려고 하는 연결된 서버의 별칭으로 MYSRV를 사용하였고, 작업이 완료되면 별칭을 없앤다. 내부의 프로시저에 링크드 서버에 실제로 접속하기 위한 코드가 포함되어 있다. 다른 서버를 가리키는 실행계획이 필요하지 않다는 것을 확실히 하기 위해 WITH RECOMPILE 옵션을 사용하였다.

위의 예는 다음과 같은 조건에서만 사용가능하다.:

  • 프로시저는 연결된 서버를 구성할 수 있는 권한을 가진 사용자에 의해 실행되어야 한다. 일반적으로 sysadmin 혹은 setupadmin 고정서버역할이 이런 권한을 갖고 있다. 그러므로, 일반 사용자에게는 적용되지 않는다.
  • 서버에 영향을 미치는 정의를 변경하는 것이므로, 실행되는 프로시져에 대한 여러개의 인스턴스를 실행시킬 수 없다. (말할 필요도 없이, 해당 별칭은 이 프로시져 내부에서만 사용하여야 한다.)(?)

주의: If you test are likely to find that it works without WITH RECOMPILE.. You may get it work to have the call to sp_addlinkedserver in the same procedure as the reference to the linked server, but if the linked server is not defined when SQL Server needs to build a query plan for the procedure, the procedure will fail..

OPENQUERY

행집합(row-set)을 반환하는 함수인 Openquery와 Openrowset을 사용할 때는 종종 동적 SQL이 필요하다. 해당 함수들의 2번째 매개변수는 SQL문으로, 변수를 사용할 수 없다. 작은 따옴표를 여러번 사용해야 하는 문제로 종종 혼란스럽기도 한데, 이전에 제시한 quotestring()을 사용하면 많은 도움이 될 것이다.:

   DECLARE @remotesql nvarchar(4000),
                 @localsql  nvarchar(4000),
                 @state     char(2)

   SELECT @state = ''CA''
   SELECT @remotesql = ''SELECT * FROM pubs.dbo.authors WHERE state = '' +
                                     dbo.quotestring(@state)
   SELECT @localsql  = ''SELECT * FROM OPENQUERY(MYSRV, '' +
                                   dbo.quotestring(@remotesql) + '')'',

   PRINT @localsql
   EXEC (@localsql)

SQL문의 길이가 입력한계인 129자를 넘는 경우가 많기 때문에, 내장함수인 quotename()은 쓰이지 못하는 경우가 많다.

열의 너비를 동적으로 변화시키고자 할 때 (Dynamic Column Widths)

쿼리 분석기에서 실행되며, 어떤 종류의 데이타 출력을 위한 저장 프로시저를 생각해보자 (아마도 대부분 관리자용 프로시져일 것이다.) 결과를 잘 볼 수 있게 하려면 데이타가 짤려서 보이지 않도록 열의 너비가 충분해야 하지만, 필요이상의 공간이 보여질 필요도 없다. 이런 경우 동적 SQL을 활용하여 해결할 수 있다. 대개 이런 작업은 임시테이블을 사용하게 되는데, 권한 문제를 신경쓰지 않아도 무방하기 때문이다.

여기서 예제를 제시하지는 않았지만, 온라인 도움말에 나오지는 않지만 많이 알려져 있는 시스템 프로시저 sp_who2가 가장 접하기 쉬운 예이다. exec master..sp_helptext sp_who2 쿼리를 실행시키거나, 쿼리분석기 혹은 EM의 개체 브라우저를 사용하여 코드를 직접 볼 수 있다.

aba_lockinfo에서도 다른 예를 찾아볼 수 있다.


감사의 글

이 기사를 쓰기 위해 유용한 제안과 정보를 제공해준 Pankul Verma, Marcus Hansfeldt, Jeremy Lubich, 그리고 SQL Server MVP인 Tibor Karaszi, Keith Kratochvil, Steve Kass, Umachandar Jaychandran, Hal Berenson에게 감사드리고 싶다.

기사의 내용, 언어, 형식에 대한 첨가 혹은 수정제안은, esquel@sommarskog.se로 메일을 보내주기 바란다. 기술적인 질문에 답변해줄 사람을 찾는다면, microsoft.public.sqlserver.programming 혹은 comp.databases.ms-sqlserver와 같은 뉴스그룹을 이용하실 것을 권해 드린다.(※ 역주 : 한글 뉴스그룹 주소는 microsoft.public.kr.sql입니다.)

원본 최종 수정시간 03-12-02
번역본 최종 수정시간 04-08-04


top
:

▣  DB 생성시 - DB - 2011. 3. 16. 11:00

※  데이터베이스를 생성할 때 (or 생성하고 난 뒤 property 볼 때) ARITHABORT, ANSI_NULLS, QUOTED_IDENTIFIER, ANSI_WARNINGS 등은 모두 켜주도록 하자. 아니면 DB 생성코드에 다음과 같이 명시할 것.

  1. ALTER DATABASE [CAMarket0] SET ARITHABORT ON
    GO
    ALTER DATABASE [CAMarket0] SET ANSI_PADDING ON
    GO
    ALTER DATABASE [CAMarket0] SET ANSI_WARNINGS ON
    GO
    ALTER DATABASE [CAMarket0] SET ANSI_NULLS ON
    GO
    ALTER DATABASE [CAMarket0] SET QUOTED_IDENTIFIER ON
    GO
    ALTER DATABASE [CAMarket0] SET ANSI_WARNINGS ON
    GO

top
:

▣  저장 프로시저 사용법 - DB - 2011. 3. 9. 21:56
CREATE PROC 보너스 지급
AS
SELECT * FROM Employee
!- 먼저 SELECT문이 이상 없는지 확인 후 CREATE한다.(오류를 줄일 수 있는 방법)

매개변수 없는 프로시져

USE pubs
IF EXISTS(SELET name FROM sysobjects

WHERE name = 'au_info_all' AND type = 'P')
DROP PROCEDURE au_info_all
GO

!-  저장 프로시져를 만들기 전에 만들어져 있는 프로시져를 확인하여 지우는 부분
해석 - 프로시져도 하나의 개체이므로 SYSOBJECTS테이블 안에 기록되어 있다.

부가적 설명
SET NOCOUNT ON
쿼리문으로 영향을 받는 레코드 수는 필요 없다(불필요한 비용을 발생시키지 않기 위하여)http://msdn.microsoft.com/ko-kr/library/ms189837.aspx
DECLARE
SQL문을 작성하면서 임시적으로 변수를 사용할 경우
선언문
DECLARE @A INT = 정수형 변수 @A를 선언
SET
변수에 값을 치환할때
SET @ID int = @SIZE * @NUM
ISNULL
- 변수를 받아 그 값이 NULL인지를 테스트하며, 값이 NULL일 경우 미리 지정된 값을 반환하고, 그렇지 않을 경우 원래의 값을 반환.
- ISNULL(구문, '대체문자열')
CASE
CASE '변수'
WHAN '비교값'
THEN '변경값'






top
:


articles
recent replies
recent trackbacks
notice
Admin : New post