반응형


[출처] Oracle DataBase 문자셋과 언어셋 확인 및 변경하기|작성자 시골사람


A db에서 export 한 것을 B db로 import 하려니 한글이 들어있는 칼럼의 경우에 사이즈가 맞지 않다고 나온다.
디비에 대해서 전혀 고려하지 않고 내리고 올리려고 했던터라 혹시나해서 보니 역시나 디비의 캐릭터가 다르더라.

그나저나 이 경우에는 기존에 데이터가 없어서 그냥 바꿔도 괜찮더라.

잘 정리된 글을 보고 따라했기에 그 글을 올린다.

Oracle DataBase 문자셋과 언어셋 확인 및 변경하기Oracle DataBase 문자셋과 언어셋 확인하기

 
[ 문자셋 확인 ]
SELECT name, value$
FROM sys.props$
WHERE name = 'NLS_CHARACTERSET';

SELECT name, value$
FROM sys.props$
WHERE name = 'NLS_NCHAR_CHARACTERSET';

[ 언어셋 확인 ]
SELECT name, value$
FROM sys.props$
WHERE name = 'NLS_LANGUAGE';

Oracle DataBase 문자셋과 언어셋 변경하기

 
- 문자셋 변경 : Oracle DataBase 관리자로 접속하여 NLS_CHARACTERSET, NCHAR의 CHARACTERSET에 원하는 문자셋을 변경할 수 있다.
- 언어셋 변경 : 문자셋과 마찬가지로 오라클 데이터베이스 관리자로 접속하여 NLS_LANGUAGE에 원하는 언어셋을 변경할 수 있다.

[ 문자셋 변경 ]
UPDATE sys.props$
SET value$ = '[ 변경을 원하는 문자셋 (KO16KSC5601, AL32UTF8 등)]'
WHERE name = 'NLS_CHARACTERSET';

UPDATE sys.props$
SET value$ = '[ 변경을 원하는 문자셋 (KO16KSC5601, AL32UTF8 등)]'
WHERE name = 'NLS_NCHAR_CHARACTERSET';

[ 언어셋 변경 ]
UPDATE sys.props$
SET value$ = '[ 변경을 원하는 문자셋 (AMERICAN_AMERICA.KO16KSC5601, AMERICAN_AMERICA.AL32UTF8등)]'
WHERE name = 'NLS_LANGUAGE'';
Oracle Character set 변경 자세히...............
####################################################
#### .bash_profile 환경변수
export ORACLE_BASE=/home/oracle
export ORACLE_HOME=$ORACLE_BASE/oracle/product/10.2.0/db_1
export ORACLE_SID=ora10
export LD_LIBRARY_PATH=$ORACLE_HOME/lib
#export LD_ASSUME_KERNEL=2.4.19
export PATH=$PATH:$ORACLE_HOME/bin

export NLS_LANG=KOREAN_KOREA.KO16KSC5601 #한글
export NLS_LANG=KOREAN_KOREA.KO16MSWIN949 #한글(추천:지원캐릭터가 더 많음 -뷃,숖..)
export NLS_LANG=AMERICAN_AMERICA.UTF8 #유니코드



####################################################
#### 캐릭터 셋 설정 확인

SELECT NAME,VALUE$ FROM PROPS$ WHERE NAME ='NLS_LANGUAGE' OR NAME ='NLS_TERRITORY' OR NAME ='NLS_CHARACTERSET';


####################################################
#### 오라클 캐릭터 셋 변경

update props$ set VALUE$='UTF-8' where name='NLS_CHARACTERSET';
update props$ set VALUE$='KO16MSWIN949' where name='NLS_CHARACTERSET';
update props$ set VALUE$='KO16KSC5601' where name='NLS_CHARACTERSET';

update props$ set VALUE$='KOREAN' where name='NLS_LANGUAGE';
update props$ set VALUE$='KOREA' where name='NLS_TERRITORY';



####################################################
#### 캐릭터 셋 변경후에 확인 사항(필수)

에러 유형 :
EXP-00008: ORACLE 오류 6552가 발생했습니다
ORA-06552: PL/SQL: Compilation unit analysis terminated
ORA-06553: PLS-553: 알 수 없는 문자 집합 이름입니다


Problem description
===================
You receive the following error when (re)compiling or calling a piece of pl/sql:
ORA-06550: line <num>, column <num>: ....
or
ORA-06552: PL/SQL: Compilation unit analysis terminated
followed by
ORA-06553: PLS-553: character set name is not recognized
에러 원인 : character set 이 섞여있음.



캐릭터셋 확인 쿼리 :
select distinct(nls_charset_name(charsetid)) CHARACTERSET,
decode(type#, 1, decode(charsetform, 1, 'VARCHAR2', 2, 'NVARCHAR2','UNKOWN'),
9, decode(charsetform, 1, 'VARCHAR', 2, 'NCHAR VARYING', 'UNKOWN'),
96, decode(charsetform, 1, 'CHAR', 2, 'NCHAR', 'UNKOWN'),
112, decode(charsetform, 1, 'CLOB', 2, 'NCLOB', 'UNKOWN')) TYPES_USED_IN
from sys.col$ where charsetform in (1,2) and type# in (1, 9, 96, 112);

캐릭터셋 확인 쿼리 결과(잘못된 경우): varchar2가 2개 의 캐릭터셋이 설정되어있음.
CHARACTERSET TYPES_USED_IN
-----------------------------------------------------
AL16UTF16 NCHAR
AL16UTF16 NVARCHAR2
AL16UTF16 NCLOB
US7ASCII CHAR
US7ASCII VARCHAR2
WE8DEC VARCHAR2
US7ASCII CLOB


캐릭터셋 확인 쿼리 결과(정상인 경우): TYPES_USERD_IN 하나당 하나의 캐릭터셋
CHARACTERSET TYPES_USED_IN
-----------------------------------------------------
AL16UTF16 NCHAR
AL16UTF16 NVARCHAR2
AL16UTF16 NCLOB
AL32UTF8 CHAR
AL32UTF8 VARCHAR2
AL32UTF8 CLOB


해결 방법 : 

1. INIT.ORA 안에 있는 parallel_server parameter 가 false 거나 아예 세팅되어있지 않은지 확인.
SQL>show parameter parallel_server

2. sqlplus "/as sysdba"로 다음 쿼리 실행(기존 데이터 백업 필수)
SHUTDOWN IMMEDIATE;
STARTUP MOUNT;
ALTER SYSTEM ENABLE RESTRICTED SESSION;
ALTER SYSTEM SET JOB_QUEUE_PROCESSES=0;
ALTER SYSTEM SET AQ_TM_PROCESSES=0;
ALTER DATABASE OPEN;
COL VALUE NEW_VALUE CHARSET
SELECT VALUE FROM NLS_DATABASE_PARAMETERS WHERE PARAMETER='NLS_CHARACTERSET';
COL VALUE NEW_VALUE NCHARSET
SELECT VALUE FROM NLS_DATABASE_PARAMETERS WHERE PARAMETER='NLS_NCHAR_CHARACTERSET';

-- UTF8로 바꿀 경우(선택)
ALTER DATABASE CHARACTER SET INTERNAL_USE UTF8;
ALTER DATABASE NATIONAL CHARACTER SET INTERNAL_USE AL16UTF16;

-- 한글로 바꿀 경우(선택)
ALTER DATABASE CHARACTER SET INTERNAL_USE KO16MSWIN949
ALTER DATABASE NATIONAL CHARACTER SET INTERNAL_USE AL16UTF16;

-- oracle reboot 2번.
SHUTDOWN IMMEDIATE;
STARTUP;
SHUTDOWN IMMEDIATE;
STARTUP;

3. parallel_server parameter 수정한 경우 원복.




'SQL > Oracle' 카테고리의 다른 글

Oracle 관리시 유용한 쿼리  (1) 2019.02.13
오라클 데이터 백업 및 복원  (0) 2019.02.13
오라클 사용자 생성 및 권한주기  (0) 2018.08.27
반응형

출처: http://fordev.tistory.com/23 [개발자를 위하여...]


sql command line 접속시 sys 로그인 처리


connect sys/oracle as sysdba



1. 계정의 테이블 스페이스 생성

create tablespace [tablespace_name] 
datafile '/home/oracle/oradata/DANBEE/[file_name].dbf' size 500m;

예)
CREATE TABLESPACE ADMIN DATAFILE 'D:\ORACLE\ORADATA\XE\ADMIN.dbf' SIZE  500M ;


2. 오라클 유저 만들기

CREATE USER [user_name] 
IDENTIFIED BY [password]
DEFAULT TABLESPACE [tablespace_name]
TEMPORARY TABLESPACE TEMP;

예)
CREATE USER nextree IDENTIFIED BY nextree DEFAULT TABLESPACE NEXTREE TEMPORARY TABLESPACE TEMP;


3. 생성한 USER에 권한주기

GRANT connect, resource, dba TO [user_name];

예)
grant connect, dba, resource to 유저명; (모든 권한 주기)

GRANT CREATE SESSION TO 유저명         // 데이터베이스에 접근할 수 있는 권한
GRANT CREATE DATABASE LINK TO 유저명
GRANT CREATE MATERIALIZED VIEW TO 유저명
GRANT CREATE PROCEDURE TO 유저명
GRANT CREATE PUBLIC SYNONYM TO 유저명
GRANT CREATE ROLE TO 유저명
GRANT CREATE SEQUENCE TO 유저명
GRANT CREATE SYNONYM TO 유저명
GRANT CREATE TABLE TO 유저명             // 테이블을 생성할 수 있는 권한
GRANT DROP ANY TABLE TO 유저명         // 테이블을 제거할 수 있는 권한
GRANT CREATE TRIGGER TO 유저명 
GRANT CREATE TYPE TO 유저명 
GRANT CREATE VIEW TO 유저명

GRANT  
 CREATE SESSION
,CREATE TABLE
,CREATE SEQUENCE   
,CREATE VIEW
TO 유저명;

4. 생성한 USER로 ORACLE에 접속하기

sqlplus nextree/nextree[@db_sid]


5. 계정 삭제하기

drop user 사용자계정 cascade;

-- 테이블 스페이스 크기 확장해주는 쿼리문
alter database 
datafile 'D:\oracle\oradata\XE\ADMIN.DBF'  resize 900M;

--테이블 스페이스 정보 보는 쿼리문 
SELECT file_name, tablespace_name, bytes, status FROM  DBA_DATA_FILES;

--테이블 명시적 인덱스 생성
--1번째 방법
CREATE INDEX MSID_IDX1 ON TEST(MSID)
--2번째 방법
create index test1_test on test1(test) 
tablespace users 
storage 

initial 10k 
next     10k 
pctincrease 0) 
pctfree 10

--테이블 정보 보는 쿼리
select * from user_constraints-- where table_name = upper('test1');

--ORA-00054: 자원이 사용중이고, NOWAIT가 지정되어 있습니다 해결 방법 
select a.sid, a.serial# 
from v$session a, v$lock b, dba_objects c 
where a.sid=b.sid and 
b.id1=c.object_id and 
b.type='TM' and 
c.object_name='CAR_INFO';

alter system kill session '12, 27846';

-- CAR_INFO에는 있는 값을 DASH_BOARD에 넣기
INSERT INTO DASH_BOARD(CAR_LICEN_NUM)
SELECT CAR_LICEN_NUM FROM CAR_INFO
MINUS
SELECT CAR_LICEN_NUM FROM DASH_BOARD


SYSTEM 계정 패스워드 변경하기
사용자계정 : /as sysdba

alter user system identified by "암호";
일반 스트링은 관계없지만 특수문자가 있을경우 반드시 "" 따옴표로 감싸준다.


1. 전체 단위(오라클 DB 전체)


덤프(익스포트) |

C:\) exp userid=system/비밀번호 file='C:\파일명.dmp' full=y


임포트 |

C:\) imp userid=system/비밀번호 file='C:\파일명.dmp' full=y



2. 사용자 단위(특정 계정/사용자의 DB )


덤프(익스포트) |

C:\) exp userid=계정명/비밀번호 file='C:\파일명.dmp' 


임포트 |

C:\) imp userid=계정/비밀번호 file='C:\파일명.dmp' 



3. 테이블 단위(특정 계정의 특정 테이블만을 추출)


덤프(익스포트) |

C:\) exp userid=계정명/비밀번호 file='C:\파일명.dmp' tables=테이블명


임포트 |

C:\) imp userid=계정명/비밀번호 file='C:\파일명.dmp' 

반응형

티스토리 소스 이쁘게 보려고 했다...


여기 저기 뒤져 보다가 찾아서 하긴 했지만 스킨 바꾸니 또 안된다...


알고 보니 소스 날아가더라....젠장할....


그래서 기록용으로 처리 한다....


참고 할 사람은 잘 참고 하시길....


내용 중 $("#mArticle .area_view pre[mode]") 내부 Object를 찾아야 하니 필히 변경해야 한다.

변경 안하면 안됨


그 외에는 할게 없다.


mode는 종류가 많으니 잘 찾아서 사용하면 되고, xml은 < > 꺽쇠가 있으니 꺽쇠를 일반 텍스트에 입력 후 html로 변환하면 코드로 변환된다.


그리고 해당 코드를 pre로 변경하면 잘 적용된다.


<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.3.3/ace.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.3.3/theme-monokai.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.3.3/mode-xml.js"></script>
<script>
$("#mArticle .area_view pre[mode]").each(function() { 
try{
    var lang = $(this).attr("mode");
    var editor = ace.edit(this);
    editor.setOptions({ maxLines: Infinity });
    editor.setTheme("ace/theme/monokai");
    editor.getSession().setMode("ace/mode/"+ lang);
    editor.setShowInvisibles(true);
    editor.setReadOnly(true);
    editor.setShowPrintMargin(false);
	  editor.session.setUseWrapMode(true);
  }
	catch(ex) { console.info (ex) } })

	document.getElementById('editor').style.fontSize='10px';
</script>


'잡담' 카테고리의 다른 글

Window 11 우 클릭 예전으로  (0) 2024.07.17
Visual Studio Code 동기화  (0) 2020.01.02
윈도우 7 이상 사용자 패스워드 변경방법  (0) 2019.01.25
Sql Server 2012 CPU Full....  (0) 2018.04.03
반응형

# 수정일자 - 2018.05.29


Apache


httpd.conf


Include conf/extra/httpd-mpm.conf


주석처리 해제


# Server-pool management (MPM specific) Include conf/extra/httpd-mpm.conf


#<IfModule mpm_worker_module>
#    StartServers             3
#    MinSpareThreads         75
#    MaxSpareThreads        250
#    ThreadsPerChild         25
#    MaxRequestWorkers      400
#    MaxConnectionsPerChild   0
#</IfModule>

<IfModule mpm_worker_module>
    StartServers             8
    ServerLimit             32
    MaxClients            4096
    MinSpareThreads       1024
    MaxSpareThreads       2048
    ThreadsPerChild        128
    MaxRequestsPerChild      0
</IfModule>


Tomcat 


server.xml


JVM 설정


<Listener className="org.apache.catalina.mbeans.JmxRemoteLifecycleListener" rmiRegistryPortPlatform="9090" rmiServerPortPlatform="9091" />


추가 후 포트 부분 수정


Tomcat Thread 수 수정


<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
maxThread="500"
maxHttpHeaderSize="8192"
emptySessionPath="true"
enableLookups="false"
redirectPort="8443"
URIEncoding="utf-8" />


maxThread : 최대 Thread 수 - LoadBallens를 다수개 걸었을 경우 최대 Thread는 Tomcat 하나당 500개로 하는게 좋다.




'Server' 카테고리의 다른 글

nginx Configure 옵션  (0) 2019.12.16
리눅스 기본 서버 셋팅  (0) 2019.12.12
리눅스의 기본 보안설정  (0) 2019.12.12
Linux 사용시 유용한 명령어  (0) 2019.02.13
Linux Mysql 5.1.73 버전 설치 - 컴파일 버전  (0) 2017.11.23
반응형

프로젝트 진행 중 Mybatis를 사용하는데 SQL 변수를 바인드 할 경우에 사용하는 변수 지정자가


#과 $가 있다.


일반적으로 


#의 동적

$는 정적


쿼리를 사용한다고 한다.


이는 PreparedStatement로 SQL을 호출하느냐

또는

Statement로 SQL을 호출 하느냐에 따라 다르다


이는 SP (Stored Produce)를 호출 할 경우 Mepper에 작성하는 SQL이 다른다


# 바인드 변수의 경우에


CALL "SP 명" 으로 사용하나


$ 바인드 변수의 경우에는


EXEC "SP 명" 으로 사용을 한다.

($의 경우 CALL로 하나 EXEC로 하나 다른차이는 없다)


다만 # 바인드 변수를 활용하여 Mybatis에서 PreparedStatement를 호출 하면 기본적으로 SQL에는


EXEC sp_executesql 사용하여 SQL을 실행하게 된다


이 경우 INDEX를 만들어 놓아도 힌트로 적용하지 않는다면 INDEX는 사용하지 않게 되어 심각할 정도의 속도 저하가 발생한다.


그래서 직접 경험해 본 바로는 $ 바인드 변수를 활용하는 것이 좋으며, 기본적으로 Java에서 Sql Server를 사용할 경우에는 INDEX는 힌트를 꼭 기재 하기 바란다.


SP내부에도 INDEX를 힌트를 적용하여 사용하여야 INDEX를 사용하여 쿼리 결과가 나온다.


동적 쿼리를 실행 해야 할 경우에는 SP를 작성하여 내부에 동적쿼리를 작성 하거나 파라미터로 전달하여 사용하는게 Sql Server에서는 최대한의 퍼포먼스를 낼 수 있다고 본다.


이는 나의 경험에서 나온 부분이나, 실제 다른 프로젝트에서는 동일한 현상이 발생하지 않을 수 있다.


혹여나 동일한 현상이 없는데 나는 잘된다고 하시는 분들의 경우 댓글로 이야기를 해 주시면 저 또한 많은 공부가 될거라 본다.


반응형

Sql Server 2005에서 서비스 하던 사항을 2016로 업그레이드 하고 나니 CPU 풀이 났었다 (2017년 5월 경)


그리고 답이 안나와 Sql Server 2012로 다운그레이드 후 (백업 파일로는 다운그래이드가 되지 않아 별도로 프로그램을 만들어 놓았던걸로

데이터 싱크를 맞추는 작업 하면서 다운그레이드를 완료했다....)


그래도 안정화가 되었으나....


신규로 사이트를 개편하면서...


JAVA + Spring + Mybatis + Sql Server 2012


조합으로 구축을 진행...


서비스 올리자 마자 CPU가 Full 나는 현상이 발생....


다시 롤백 후 원인을 찾으면서 부하 테스트를 진행...


결론은 Java와 Sql Server 조합은 정말 거지같다는 것....


특히 Mybatis를 사용할 경우에는 Preper...Statement로 SQL 및 SP를 호출 하면 INDEX 조차 타지 않는 현상이 발생한다....


그나마 SP에 INDEX를 힌트로 적용하게 되면 INDEX가 타면서 속도가 올라가는 것을 확인하였고


Mybatis에서 SQL에 변수를 줄 경우에 #{변수}와 ${변수}의 차이도 있지만 속도면에서는 크게 느껴지지 않는다...


망할 CPU FULL만 안났으면 좋겠는데 ㅠ


해결 방안을 찾자 !!!


(2018-04-03 작성)

반응형

출처 : itworld

링크 : http://www.itworld.co.kr/print/105792



누구나 더 빠른 데이터베이스 쿼리를 원하며, SQL 개발자와 DBA 모두 이런 목표를 달성하기 위해 오랜 사용으로 효과가 입증된 여러 가지 방법에 의지한다. 하지만 안타깝게도 그 어떤 방법이라도 하나만으로는 완벽하거나 확실하지 않다. 이처럼 모든 쿼리를 미세조정, 즉 튜닝하기 위한 정답이 존재하지는 않지만, 길을 밝히는데 도움이 되는 풍부한 관례가 있다. 몇 가지는 RDMBS에 관련된 사항들이지만, 대부분의 팁은 모든 관계형 데이터베이스에 적용된다.


SQL 서버, 오라클, DB2, 사이베이스, MySQL, 아니면 다른 관계형 데이터베이스 플랫폼 중 어디에서 코딩을 하고 있더라도, 목표는 같다. 데이터베이스가 가능한 빨리 쿼리를 처리하는 동시에 현실적으로 가능한 많은 동시 사용자를 지원하는 것이다. 이는 공간과 자원 관리를 최적화하는 동시에 잠금(Locking), 입출력, 그리고 네트워크 트래픽은 최소화해야 한다는 것을 의미한다.


데이터베이스 튜닝은 기술인 동시에 과학이다. 다음은 사용 중인 데이터베이스를 더 빠르고 더욱 효과적으로 만들어주는 21가지 입증된 규칙이다.


1. 가능하면 커서(Cursor)를 피하라.

커서는 일련의 데이터에 순차적으로 액세스할 때 검색 및 현재 위치를 포함하는 데이터 요소를 말한다. 커서를 피하는 것은 아주 쉬운 결정이다. 커서는 속도 문제를 겪을 뿐 아니라, 다른 작업을 필요 이상 지연시킬 정도로 하나의 작업을 블록(Block)시킬 수도 있다. 이는 시스템의 동시성을 크게 저하시킨다.


2. 커서를 피할 수 없다면, 임시 테이블(temp table)을 사용하라

커서를 사용해야만 할 때가 있다. 그런 경우, 라이브 테이블(Live Table)보다는 임시 테이블에 대한 커서 작업을 수행하는 것이 더 낫다. 훨씬 더 작은 라이브 테이블에 대한 하나의 UPDATE 문이 있을 수 있다. 짧은 시간 동안에만 잠금(Lock)을 유지하게 되어 동시성을 크게 증진시켜 준다.


3. 임시 테이블을 현명하게 사용하라

다른 여러 가지 상황에서도 임시 테이블을 사용할 수 있다. 예를 들어, 어떤 테이블을 더 큰 테이블에 조인(Join) 시켜야만 한다면, 더 큰 테이블에서 필요한 일부 데이터만 임시 테이블로 끌어(Pull)와서 대신 그것과 조인시킴으로써 성능을 개선할 수 있다. 이는 필요한 처리 능력을 크게 줄여주며, 프로시저에 같은 테이블에 대해 유사한 조인을 해야만 하는 여러 개의 쿼리가 있는 경우 유용하다.


4. 데이터를 미리 준비하라

흔히 간과되는 예전의 기법이다. 커다란 테이블에 대해 비슷한 조인 작업을 할 보고서(Report)나 프로시저가 있다면, 미리 테이블을 조인시키고 테이블들을 하나의 테이블에 영속화(Persisted)시킴으로써 데이터를 사전 준비하라. 그렇게 하면, 사전 준비된 해당 테이블에 대한 보고서 작업을 실행할 수 있어서, 대규모 조인 작업을 피할 수 있다.


항상 이 기법을 사용할 수는 없지만, 대부분의 환경에는 늘 조인되는 인기 테이블이 있기 마련이다. 이런 테이블들을 사전에 준비하지 못할 이유가 전혀 없으며, 서버 자원을 절약하기 위한 훌륭한 방법이다.


5. 복합 뷰(Nested View)를 최소화하라

뷰는 엄청난 쿼리를 사용자들로부터 가리는데 훌륭하지만, 하나의 뷰 안에 또 다른 뷰와 내부에 있는 다른 뷰를 (계속해서) 중첩시키다 보면 심각한 성능 저하를 유발할 수 있다. 너무 많은 수의 복합 뷰는 모든 쿼리에 대해 엄청난 양의 데이터가 반환(Return) 되는 결과를 초래해서, 데이터베이스 성능을 말 그대로 기어 다니게 만들 수 있다. 혹은, 더 나가서, 쿼리 최적화기(Optimizer)가 포기해서 아무것도 반환되지 않을 수도 있다.


복합 뷰를 풀어내는 것으로 쿼리 응답 시간을 몇 분에서 몇 초로 줄일 수 있다.


6. UPDATE 문 대신 CASE 문을 사용하라

다음 시나리오를 살펴보자. 임시 테이블에 데이터를 삽입하고 있으며 다른 값이 존재할 경우 해당 데이터가 특정 값을 표시하도록 해야 한다. Customer 테이블에서 데이터를 끌어오고 있으며 주문 액수가 100,000달러 이상인 고객에 대해서 “우대”라는 라벨을 붙이고 싶어한다고 하자. 그래서, 100,000달러 이상의 주문 금액을 보유하고 있는 모든 고객에 대해서 CustomerRank 열에 “우대”라고 설정하기 위해 테이블에 데이터를 삽입하고 UPDATE 문을 실행한다.


논리적으로 보인다, 그렇지 않은가? 문제는 UPDATE 문이 로그된다는 것이다 즉, 테이블에 대한 모든 한 번의 쓰기 작업 당 두 번의 쓰기 작업이 일어난다는 의미이다. 물론, 이 문제를 피하는 방법은 SQL 쿼리 자체에서 인라인(Inline) CASE 문을 사용하는 것이다. 이는 모든 행에 대해 주문량 조건을 확인하고 테이블에 쓰기 전에 “우대” 라벨을 설정한다. 성능 증가는 깜짝 놀랄 정도이다.


7. 스칼라(Scalar) 대신 테이블 반환 함수(Table-Valued Functions)를 사용하라

전문가들이 사용하는 팁이 있다. 쿼리의 SELECT 목록에서 스칼라 함수를 사용할 경우, 그 대신에 쿼리에서 테이블 반환 함수를 사용하고 CROSS APPLY 문을 사용하면 성능을 개선할 수 있다. 이는 쿼리 시간을 절반으로 대폭 줄여줄 수 있다.



8. SQL 서버에서 분할(Partition)을 활용하라

SQL 서버 엔터프라이즈 사용자들은 성능을 가속화하기 위해 데이터 엔진의 자동 분할 기능을 활용할 수 있다. SQL 서버에서는 간단한 테이블조차도 하나의 분할로 생성되며, 사용자는 나중에 그것을 필요에 따라 여러 개의 분할로 쪼갤 수 있다. 테이블 간에 많은 양의 데이터를 옮겨야 할 경우, INSERT와 DELETE 문 대신에 SWITCH 명령을 사용할 수 있다. 테이블 간에 많은 양의 데이터를 삭제하고 삽입하는 대신, 단일 테이블에 대한 메타데이터만 변경하는 것이기 때문에, 실행하는데 몇 초 밖에 걸리지 않는다.


9. 배치 모드로 삭제(Delete)와 갱신(Update) 작업을 하라

거대한 테이블에서 많은 양의 데이터를 삭제하거나 업데이트하는 작업은 악몽일 수 있다. 문제는 이 두 가지 명령문 모두가 하나의 트랜잭션으로 실행되는 것이며, 프로세스를 중지시켜야 한다거나 작업 도중에 어떤 일이 일어진다면, 시스템은 전체 트랜잭션을 복원(Roll Back)시켜야만 한다. 이 작업은 진행 중인 다른 트랜잭션들을 블록 시킬 뿐 아니라, 많은 시간이 걸릴 수 있어서, 기본적으로 시스템 병목을 일으킨다.


해결책은 작은 배치 단위로 삭제나 업데이트 작업을 하는 것이다. 트랜잭션이 중지돼도, 소수의 행만 복원하면 되므로, 데이터베이스는 훨씬 더 빨리 온라인으로 돌아온다. 그리고 더 작은 배치작업들이 디스크에 커밋(Commit)하는 동안, 다른 작업들이 끼어들어서 어느 정도의 작업을 할 수 있어서 동시성이 크게 개선된다.


10. 서두르지 말고 천천히 하라

일부 개발자들은 이런 삭제와 업데이트 작업이 같은 날 완료되어야만 한다는 사실을 머리 속에 새겨놓고 있다. 늘 그런 것은 아니다. 특히, 아카이빙 작업은 더욱 그렇지 않다. 이 작업은 필요한 만큼 늘일 수 있으며, 이 작업을 완료하는 데는 더 작은 배치작업들이 도움이 된다. 이런 집약적인 작업을 더 천천히 할 수 있다면, 여분의 시간을 시스템이 다운되지 않도록 하는 데 투여하기 바란다.


11. ORM을 피하라

ORM(Object-relational Mapper: 객체 관계형 매퍼)는 지구상에서 최악의 코드를 만들어 내고 있으며, 개발자가 직면할 가능성이 있는 대부분의 성능 문제에 책임이 있다. 그렇지만, ORM을 피할 수 없다면, 스스로 자체적인 저장 프로시저를 작성하고 ORM이 자체 쿼리를 작성하는 대신 사용자가 작성한 쿼리를 호출하게 함으로써 부정적인 측면을 최소화할 수 있다.


12. 가능한 경우, 저장 프로시저(Stored Procedure)를 사용하라

더 훌륭한 코드로 이끄는 것 외에, 저장 프로시저는 다른 많은 장점도 가지고 있다. 저장 프로시저는 호출이 더 짧을 것이기 때문에, 트래픽을 크게 줄여준다. 프로파일러(Profiler) 같은 도구를 사용해서 추적하기가 더 쉬워서 사용자가 성능 통계치를 확보하고 잠재적인 문제를 더 빨리 규명할 수 있게 해준다. 더욱 일관성 있는 방식으로 정의할 수 있으며, 이는 실행 계획(Execution Plan)을 재사용할 가능성이 더 높으며, 임의 쿼리에 비해 엣지 케이스(Edge Case)와 감사용으로 사용하기가 더 쉽다는 의미이다.


많은 닷넷 코더들은 비즈니스 로직이 데이터베이스가 아닌 애플리케이션의 프론트 엔드에 속한다고 믿고 있다. 그렇지만, 그들은 틀렸다(대부분의 경우).


13. 더블 디핑(Double-Dipping: 중복 처리)을 피하라

저장 프로시저 사용은 때로 “더블 디핑”으로 이어질 수 있다. 대규모 테이블에 대해 별개의 쿼리를 여러 개 실행하고, 그것들을 임시 테이블에 넣은 다음에, 테이블들을 다시 조인하는 것이다. 이는 성능에 커다란 방해물이 될 수 있다. 가능한한 대규모 테이블을 한 번만 쿼리 하는 것이 훨씬 더 낫다.


조금 다른 시나리오는 한 프로세스의 몇 가지 단계에서 커다란 테이블의 일부가 필요한 경우로, 이는 매 번 커다란 테이블에 대한 쿼리를 유발한다. 일부에 대한 쿼리를 실행하고 그것을 다른 곳에 영속화 시킨 다음에, 후속 단계를 영속화된 더 작은 데이터 세트로 유도하라.


14. 커다란 트랜잭션은 작은 트랜잭션 여러 개로 쪼개라

단일 트랜잭션에서 여러 개의 테이블을 처리하는 작업은 해당 트랜잭션이 끝날 때까지 모든 테이블을 잠글 수 있기 때문에, 다수의 블로킹으로 이어진다. 해결책은 이 트랜잭션을 각각이 개별적으로 단일 테이블에 대한 작업을 하는 여러 개의 루틴(Routines)으로 쪼개는 것이다. 이는 블로킹 횟수를 줄여주고 다른 작업들이 계속해서 이루어질 수 있도록 다른 테이블들을 풀어준다.


15. 트리거(Trigger) 사용을 자제하라

하려고 하는 작업이 무엇이든, 원래 작업의 동일한 트랜잭션에서 수행될 것이기 때문에 트리거 사용도 비슷한 문제로 이어질 수 있다. 이는 트리거가 완료될 때까지 여러 개의 테이블을 잠그는 결과를 초래할 수 있다는 의미이다. 이런 트리거를 별개의 트랜잭션들로 쪼개면 더 적은 수의 자원을 잠그게 돼서 필요한 경우 변경사항 복원을 쉽게 만들어준다. 가능하면 트리거를 피하라.



16. GUID에 대한 클러스터링을 피하라

테이블 데이터 정렬을 위해 GUID(Globally Unique Identifier: 범용 고유 식별자)를 사용하지 말라. 임의로 생성되는 이런 16비트 숫자는 사용자의 테이블을 훨씬 더 빨리 파편화한다. DATE나 IDENTIFY 같은 값을 점진적으로 증가시켜서 데이터를 정렬하는 것이 훨씬 낫다. 휘발성 있는 모든 열에 대해서도 갖은 규칙이 적용된다. 단 몇 분 만에 극적으로 테이블들이 파편화될 수도 있다.


17. 테이블에 있는 모든 것을 카운트(Count)하지 말라

테이블에 데이터가 존재하거나 어떤 고객에 대한 데이터가 존재하는 지를 확인할 필요가 있으며, 확인 결과에 따라, 어떤 조치를 취해야 한다고 가정하자.


필자는 그런 데이터의 존재를 확인하기 위해 누군가가 SELECT COUNT(*) FROM dbo.T1 명령을 실행하는 것을 자주 보았다.


SET @CT = (SELECT COUNT(*) FROM dbo.T1);

If @CT > 0

BEGIN <Do something>

END


전혀 불필요한 명령이다. 존재 여부를 확인하고 싶다면, 다음과 같이 하라:


If EXISTS (SELECT 1 FROM dbo.T1)

BEGIN

<Do something>

END


다른 말로 하면, 테이블에 있는 모든 것을 카운트하지 말라는 것이다. 첫 번째 행으로 돌아가면 찾을 수 있다. SQL 서버는 EXIST 문을 제대로 사용할 수 있을 정도로 똑똑하며, 두 번째 블록의 코드는 아주 빠르게 결과를 돌려준다. 테이블이 크면 클수록, 더 많은 차이를 낼 것이다.


18. 행을 카운트하려면 시스템 테이블(System Table)을 사용하라

커다란 테이블의 행을 정말로 카운트할 필요가 있다면, 시스템 테이블에서 끌어 올 수 있다. ‘SELECT rows from sysindex’ 명령문은 모든 인덱스에 대한 열의 수를 알려줄 것이다.


그리고 클러스터된 인덱스가 데이터 자체를 나타내기 때문에, ‘WHERE indid = 1’을 추가하면 테이블 행을 얻을 수 있다. 그 다음에는 그냥 테이블 이름을 추가하기만 하면 만사형통이다. 이렇게 하면, 최종 쿼리는 다음과 같다


SELECT rows FROM sysindexes WHERE object_name(id) = ‘T1’ AND indexid = 1


19. 필요한 수의 열만 끌어오라

열을 개별적으로 나열하는 대신 모든 쿼리를 SELECT * 명령문으로만 코딩한다면 너무 쉬울 것이다. 또 다시 문제는 필요한 것보다 더 많은 데이터를 끌어 온다는 것이다. 개발자가 120개의 열과 수 백만 개의 행을 가지고 있는 테이블을 대상으로 SELECT *를 실행하고는, 겨우 3~5개만 사용하고 말았다. 그 시점에, 개발자는 필요한 것보다 훨씬 더 많은 데이터를 처리시켰을 뿐만 아니라 다른 프로세스들로부터 자원을 뺏어가기도 한 것이다.


20. 네거티브 검색(Negative Search)를 피하기 위해 쿼리를 재 작성하라

인덱스를 사용할 수 없는 쿼리를 사용해서 데이터를 행 별로 비교할 필요가 있을 때, 예를 들어 FROM Customers WHERE RegionID <> 3 같은 경우는 인덱스를 사용할 수 있도록 쿼리를 재작성하는 것이 더 낫다.


SELECT * FROM Customers WHERE RegionID < 3 UNION ALL SELECT * FROM Customers WHERE RegionID


데이터 세트가 큰 경우, 인덱스를 사용하는 것이 테이블 스캔 버전을 크게 능가하는 결과를 내 놓을 수도 있다. 물론, 더 열악한 결과를 낼 수도 있으니 구현에 앞서 시험해보라.


필자는 이 쿼리가 팁 13번(중복 처리를 피하라)을 어긴다는 것을 알았지만, 융통성 없는 규칙은 없다는 것을 보여주는 것이기도 하다. 여기서는 중복 처리를 했지만, 대가가 큰 테이블 스캔을 피하기 위해서이다.


21. 맹목적으로 코드를 재사용하지 말라

필요한 데이터를 끌어온다는 것을 알기 때문에 다른 누군가의 코드를 복사하기가 십상이다. 문제는 종종 필요한 것보다 훨씬 더 많은 데이터를 끌어오고 있으며, 개발자들이 양을 줄이려 하는 경우는 거의 없어서, 거대한 데이터 상위 집합에 이르고 만다. 이는 대개 추가적인 외부 조인(Outer Join)이나 WHERE 문에서 추가 조건 형태로 나타난다. 재사용된 코드를 꼭 필요한 수준으로 줄일 수 있다면 커다란 성능 이득을 볼 수 있다.


이런 기법들 모두가 모든 상황에서 작동하지는 않는다는 것만 명심하라. 어떤 기법이 가장 잘 동작하는지를 알기 위해 실험을 해야만 할 것이다. 그렇지만, 일반적으로 언급한 SQL 팁들을 잘 사용하면 동시성을 증가시키고, 성능을 가속화시키며, DBA부터 최종 사용자들까지, 모든 사람의 삶을 훨씬 더 쉽게 만들어 줄 것이다.

반응형


출처 : zzikjh

링크 : http://zzikjh.tistory.com/entry/JDBC-PreparedStatement의-실행속도-문제


Q : 특정 쿼리가 PreparedStatement로 실행하면 너무 느린데 같은 쿼리를 Statement 로 실행시는 1초도 안걸립니다. 이유는?



A1 : 첫번째 생각할 것은 최적화기의 문제입니다. 오라클의 경우엔 규칙기반 최적화와, 비용기반 최적화기가 있는데 각각을 rule-based optimizer와 cost-based optimzer가 있습니다. 그리고 이런 최적화기를 선택하는 기준을 설정하기 위한 파라미터로 optimizer_goal 인가하는 파라미터가 있는데 이 값이 all_rows인 경우 쿼리를 olap성으로 파악해서 throughput을 최대화하고, first_rows인 경우에는 oltp성으로 파악해서 response time을 최소화 합니다. 둘다 maximize되는 모드는 없습니다. 예를 드는게, 테이블을 읽을 때 어떻게 읽을까 하는 전략인데 all_rows의 경우에는 full table scan을 지향하고, first_rows에서는 index scan을 사용합니다. 룰 기반 최적화기는 이와 달리, 각종 스캔방식에 점수를 미리 매겨놓고 이 점수대로 최적화하는 것입니다. 가령, 어떤 행을 인덱스로 읽는게 10점이면 풀 테이블 스캔은 5점 이런식입니다. 따라서 인덱스가 있다면 인덱스를 무조건 씁니다. (점수가 높으니까요.) 하지만 인덱스라는게 반환하는 행의 개수가 전체 10~15%일때 인덱스 사용의 효과가 나타나는게 일반적인데, 이 것까지는 룰 기반 최적화에서는 고려하지 않습니다. 다시 말해 옛날 방식이란 거죠. 별다른 설정을 하지 않았다면 아마도 최적화 모드가 all_rows일 텐데 반응시간을 높이고 싶다면 이것을 first_rows로 바꿔주어야합니다. 바꾸는 방법은 oracle 8i 이하에서는 init{SID}.ora (여기서 {SID}자리에는 실제 SID를 적어주면 됩니다. 예를들어 ORCL이라면 initORCL.ora가 됩니다)를 열어서 수정하고 데이터베이스를 재시작하면 됩니다. 오라클 9이상부터는 이렇게만 하면 안되고 방식이 조금 바뀌었는데, 자세한건 저도 기억이 안나네요.. 메뉴얼이나 otn에 물어보세요. (otn.oracle.co.kr 가셔서 좌측에 포럼 클릭하시면 됩니다.)
두번째 생각할 것은, prepared statement는 실행계획을 한번 만들고 계속 재사용한다는 것입니다. 따라서 값의 분포등이 바뀌었어도 이런걸 고려 안해주고 예전에 만든 실행계획을 계속 사용합니다. 따라서 새로이 인덱스를 만들었거나 해도 무조건 예전의 실행계획만 쓰게 되죠. 그래서 이걸 제대로 반영하려면 매번 실행 계획을 작성하는 statement를 쓰시거나 아니면 shared pool이라는 오라클내 공유메모리를 비워주면 되는데 alter system flush shared_pool; 이란 명령을 씁니다. ('_'를 빼야하는건지도 모르겠네요. 잘 기억이 안나서.. 죄송.) 

세번째 생각할 내용은, 실행 계획이 매번 잘 바뀌도록 비용을 제대로 산정해주고 있는가의 문제인데, 비용 산정을 위해서는 값의 분포(히스토그램)나 전체 행의 수같은 정보가 필요합니다. 이것을 분석하는 명령은 자체 제공하는 dbms_stats 패키지가 있고, analyze명령이 있고, monitoring 옵션이 있습니다. 제대로 해주려면 이런 비용 측정 정책도 잘 정립해야됩니다. 자세한 내용은 굉장히 많으니 서적이나 asktom.oracle.com에서 검색을 통해서 참고하시기 바랍니다. 마지막으로 말씀드리고 싶은건, 자주 실행하는 명령도 아니고 그러면 그 문장 하나 Statement로 한다고 해서 큰일날 일도 없단 것입니다. 몽땅 다 Statement로 코딩했다면 모르되, 한두개 쿼리가 Statement라고 심각한 성능저하가 생기고 하지는 않습니다. 

-------------------------------------------------------------------------------------------------

A2 : BIND 를 쓸 경우 실행계획이 틀어져서 느려지는 그런 경우를 unsafe literal 이라고 합니다. 예를 들어...

select * from emp where empno = :v1

위의 SQL에서 empno 는 emp의 PK입니다. 따라서 preparedStatement를 써서 BIND변수를 사용하든지 그렇지 않든지 실행계획은 똑 같이 나옵니다.

이런 경우를 safe literal 이라고 합니다. 하지만,

select * from emp where deptno > :v1

위의 경우... :v1이 ...  1 일 경우와 1000일 경우는 분포도가 전혀 다릅니다. 1일 경우에는 
대부분의 deptno가 1보다는 크기 때문에... 분포도가 넓습니다. 즉, 인덱스를 타지 않는게
더 좋습니다.    하지만 deptno가 1000보다 큰 경우는 거의 없습니다. 즉, 분포도가 좁습니다. 이 경우 인덱스를 타야 합니다.

그러니가 히스토그램이라는 것은 이처럼 실재 :v1 값이 무엇이냐에 따라서 분포도가 달라지는 분포도 정보입니다. 어느 value가 어느 정도의 분포도를 가지고 있는지 그 정보를
히스토그램이라고 합니다.

문제는 BIND를 쓰면... :v1 이 1이 들어올지 1000이 들어올지에 무관하게 무조건 단 하나의 실행계획만을 만들고 일괄적으로 적용하다가 보니...  1000이 들어오는데도 불구하고 인덱스를 타지 않고 Full Scan하는 상황이 나올 수 있습니다.

즉, BIND 변수를 활용하며... 히스토그램 정보를 활용할 수 없습니다.

그렇다고면? prepatedStatement를 쓰지 말아야 할까요???  아닙니다. 써야 합니다.
위와 같이 불충분한 통계정보(히스토그램)이 없어서 실행계획을 잘못 수립해서 느려지는 경우는 어쩔 수 없는 현재의 옵티마이져의 한계로 보아야 합니다. 이 경우 똑똑한 사람이
힌트 등을 통해서 SQL에게 올바른 실행계획을 가르쳐줘야 합니다.

아주 특수한 경우 위의 이유로 튜닝을 위해 Statement를 쓰는 경우도 있습니다.
예를 들어... 성별이 남, 여 두가지일 경우... 남자가 전체의 2%이며 아주 극소수라고 가정하면.. 남자일 경우에는 인덱스르 타야하고... 여자일 경우 인덱스를 타지 말아야 합니다.
이 경우... 어차피 distinct 값의 종류가 딱 2가지 이므로... BIND를 쓰지 않아서
하드파싱을 해봤자... 2개의 SQL 종류 밖에는 나오지 않으므로 문제가 없습니다.

select * from user where sex = 'm'
select * from user where sex = 'f'

오라클의 Shared pool의 Library cache에는 위와 같이 딱 두종류의 SQL이 저장되어있어서 재사용되겠지요. 답변이 길었는데... 위의 이야기는 DBMS에 대해서 잘 아시는 분이 아니라면 어려울 수도 있습니다.

간단하게 이야기해서 실행계획을 비교해보십시오.

BIND변수를 썼을 때와 그렇지 않았을 때를... 분명이 위의 경우 실행계획이 전혀 다르게 나올겁니다. 그러니까. Unsafe literal 이죠.

따라서 ... BIND 쓰지 않았을 경우와 똑같이 실행계획이 나오도록 힌트를 사용해서 조정해 주십시오.

반드시 preparedStatement를 쓰시구요. 어쩌다 일주일에 한번쯤 돌리는 SQL이 아니라... 수시로 똑 같은 SQL이 반복적으로 계속 들어온다면 반드시 preparedStatement로 작성하셔야합니다. 즉, OLTP에서는 무조건 써야 합니다.


'JAVA' 카테고리의 다른 글

JavaScript 쌩 자바스크립트로 테이블 로우 추가 및 삭제  (0) 2019.02.27
Mybatis #과 $의 차이  (0) 2018.04.05
반응형


SQL SERVER 관련해서 검색 하다가 나온 자료....

하늘몽구님 감사합니다...


출처 : 하늘몽구

링크 : http://skymong9.egloos.com/v/1841645


ORCLE이 정말 좋은 RDB라는 것을 알게 하는 것이 바로 페이징 기법일거라 생각되네요.
MySQL도 내부적으로 페이징이 가능한 쿼리를 지원해주지만 MSSQL은 최근 2005버전까지도... 좋은 페이징 기법을 소개하지 못하고 있는 것 같네요. 성능 좋은 페이징 기법이 공개된 것이 있기도 하지만... 초심자에게 쉽지 않은 쿼리들인 것 같습니다.
이에 조금 쉽게... 어떻게 하면 성능이 향상되는지를 설명해 보려고 합니다.

아래의 내용은 온라인상에서 바로 작성하는 내용이고 맞춤법등이 틀릴 수 있기 때문에 copy해서 사용하지 마시기 바랍니다.

개념을 잡는 정도로 활용하시면 좋을 것 같습니다.

MS SQL의 페이징 기법의 키는 TOP 키워드입니다.

#1. TOP과  클러스터드 인덱스

SELECT TOP 10 ID, subject, contents FROM TBL

이 쿼리는 누구나 알고 있는 쿼리입니다. 여기서 중요한 것이 TOP 10 입니다.  상위 10개만 갖고 오겠다는 뜻입니다.MSSQL은 내용저장을 클러스터드 인덱스 순으로 저장을 하게 됩니다. 만약 ID를 PK로 지정하셨거나 따로 클러스터드 인덱스로써 ID를 지정해 놓으셨다면 ID순으로 자동으로 정렬이 되어 상위 10개만 가져오게 됩니다.

기본키는 반드시 설정할 필요는 없습니다. 다만 레코드를 구분하는 아이디값을 대게의 경우 기본키로 놓게 되는데요.

사실 기본키가 성능에 있어서의 의미는 없다고 봅니다.(확인된 바 없음)

기본키는 자동으로 인덱스 컬럼이 된다는게 의미가 있겠죠. 그것도 테이블당 딱 하나 사용할 수 있는 클러스터드 인덱스로 자동 설정됩니다. 헌데 문제는 ASC로 설정된다는 것입니다.

웹페이지에서 최근 글의 경우는 대부분 DESC로 정렬합니다. 즉 최근 글을 먼저 표시해주지요. PK로 만들더라도 꼭 ID를 DESC로 인덱스하도록 만들어야 합니다.

바로 이 유일 인덱스 컬럼이 성능을 좌우하는 핵입니다.

초심자의 경우 인덱스의 중요성을 넘기는 경우가 많은데요. PK가 중요한 것이 아니라 정렬을 원하는 컬럼을 클러스터드 인덱스로 만들어 놓는 것이 성능에 가장 중요합니다.(레코드 수가 늘 수록 엄청난 성능 향상이 있습니다.)

팁) TOP 10 PERCENT라는 키워드도 가능합니다. 말 그대로 전체의 10%만 가져온다는 것입니다. 쓸모가 많은 팁이라 생각되네요.


#2. 가장 많이 쓰이는 페이징 쿼리

SELECT TOP 10 ID, subject, contents FROM TBL where ID not in (SELECT TOP 현재페이진 이전까지의 모든 게시물 수 ID FROM TBL order by ID DESC) order by DESC

참 좋은 쿼리입니다. MS SQL에서 나올 수 있는 가장 간결하고 좋은 쿼리가 아닌가 싶습니다.

저역시 작은 규모의 게시판 종류는 무조건 이 쿼리를 이용합니다. 유지보수가 편하기 때문입니다. 누구나 쉽게 알아 볼 수 있기 때문에 제가 도저히 못 봐줄 유지보수 프로젝트라면 다른 사람이 대신할 수 있는 쿼리이기 때문에... 이 쿼리를 즐겨사용합니다.

다만 이 쿼리에는 조건이 하나 붙습니다.

사용자들이 100페이징 이하(게시물 수로 2000개 이하정도)의 글을 되도록 조회한다.

이 쿼리는 2만건 이하의 테이블에 적당하다고 생각됩니다.

1페이지라고 하면 성능이 최고가 되며
2페이지라면 not in 안의 쿼리문에 의해 한페이지가 10개의 글이라고 가정하면 10개를 일단 불러 들이게 됩니다. 제거를 위해서죠.
3페이지라면 20개를 불러들여서 제거를 해야 하겠고
4페이지라면 30개를...

그럼 100페이지라면 990개의 글을 읽어 들여야 겠군요. 990개를 먼저 불러 들이는 것이 문제입니다.

가장 심플한 쿼리이지만 DB에 어느정도의 부하를 주는 쿼리라는 것을 알 수 있습니다.

개인적으로 레코드셋종류의 객체에 내용을 불러오는 쿼리는 0.1초 이전에 끝나야 한다고 생각됩니다. 물론 검색이 들어가면 이야기가 달라지지만 기본적인 형태(아무런 검색이 없는 경우)에서는 0.1초 이하에서 OPEN을 끝내는 것이 좋다고 생각되네요.

 

#3. ORDER BY문

아시다시피 ORDER BY문은 정렬의 조건입니다. 위 쿼리의 경우 ORDER BY ID DESC이니깐 ID에 대해 내림차순으로 정렬하겠네요. 사실 ORDER BY 문은 안써주는 것이 성능에 가장 좋습니다.

만약 ID를 클러스터드 인덱스로 지정해 놨다면 안써도 될 것 같습니다. 클러스터드 인덱스를 내림차순으로 지정해 놨다면 ORDER BY ID DESC는 성능에 전혀 영향을 미치지 않습니다. 가끔 MSSQL에서 서브쿼리를 썼을 경우 원하는 값을 리턴하지 않습니다. 이유는 모르겠습니다. 서브쿼리문을 가져오는 방법에서 더 빨리 가져오려고 하는 그 부분에서 나오는 문제일거라 생각되네요.

클러스터드 인덱스로 지정했을 경우 ORDER BY ID DESC는 되도록이면 넣으시는게 좋습니다. 정확성을 위해서!;


#4. 성능저하 요소 SELECT COUNT(*) FROM TBL

NOT IN 쿼리보다 심플한 페이징 쿼리는 MSSQL에서 없는 것 같습니다. 개발자 입장에서 프로그래밍하기도 정말 쉬운 쿼리입니다. 현재 페이지 번호만 넘겨주면 모든 것이 가능합니다. 가장 큰 장점이 바로 개발하기 편하다는 것이 겠고, 사용자의 웹접근 액션과도 상당히 잘 맞아 떨어집니다. 사람들은 10페이지 이상은 잘 보지 않으려는 경향이 있기 때문입니다.

NOT IN 쿼리는 생각보다 좋은 쿼리라는 것을 일단 말씀 드립니다. 단 조건은 INDEX를 잘 설정해줬다는 조건이 붙습니다.


게시판 등을 개발할 때 전체 게시물 수를 구해오는데 가장 많이 쓰는 쿼리문이 SELECT COUNT(*) FROM TBL입니다. 페이지 바로 가기 버튼을 위해서도 필요하고 게시물 번호를 붙히는 데도 쓰이기 때문에 쓰는 경우가 많습니다.

헌데 이 쿼리문 속도가 상당히 늦습니다. 100만건을 조회할 경우 1초가 넘어가 버리는 무식한 쿼리입니다. 야야~ 그럼 IDEX행을 가져와봐... 라고 하실 것 같습니다. SELECT COUNT(ID) FROM TBL ... 허나 애석하게 속도가 똑같습니다.

그리고 COUNT함수는 가능하면 (*)를 사용하시기 바랍니다. 이것이 정확한 방법입니다. COUNT(*)에서 속도향상을 위한 방법은 솔직히 말씀드려서 없습니다.

가장 좋은 방법은 테이블 하나를 만들어서 데이타 입력/삭제시마다 업데이트 하면서 게시물 수를 저장해 놓는 방법입니다. MSSQL 내에서는 트리거라는 기능을 제공합니다. INPUT/DELETE 시 UPDATE TBL_SETTING set TBL_COUNT = xxx 뭐 이런 식으로 짜 놓으면 되겠죠.

전체 게시물 수를 가져올 때는 되도록이면 트리거를 쓰거나 프로그램을 통해 정보를 저장하는 테이블에 업데이트하고 이 자료를 페이징시 가져오는 것이 성능을 위한 좋은 방법입니다.

1만건 이하라면 COUNT(*)를 쓰던 NOT IN을 쓰던 별 지장이 없다는 게 제 생각입니다. 고로, 자꾸 써먹어도 좋은 쿼리 들임을 일단 알려드립니다.


#5 성능향상에 가장 좋은 방법

1000만건을 테스트 해보지는 않았지만, 1페이지와 100만 페이지가 같은(해보진 않았죠^^)-혹은 비슷한- 성능을 보이는 쿼리는 다름 아닌 NOT IN보다 훨씬 더 간결한 쿼리입니다.

SELECT TOP 11 ID, subject, contents FROM TBL where ID <= 현재페이지 최상위 ID (order by ID DESC)

이 쿼리에서 가장 중요한 것은 ID가 클러스터드 인덱스로 ID를 설정하고 내림차순으로 지정되어 있어야 한다는 것입니다.

아무리 많은 글들도 0.1초안에 해결될만한 가장 성능이 좋은 쿼리문입니다. 동접자수 엄청나고 글 수가 많다면 이방법 이외의 방법은 사용하지 마시기 바랍니다.

허나 장점이 있으면 단점도 있습니다. 이 쿼리를 사용할 경우 ID값에 바로 접근하긴하기 때문에

이전 1 2 3 4 5 6 7 8 9 10 다음 <- 이와 같은 구성이 불가능하다는 것입니다.

그래서 TOP 10이 아닌 11을 사용한 것입니다. 1부터 10까지 구성은 힘들지만 다음페이지 버튼은 가능하기 때문입니다. 갯수가 11개면 다음 페이지가 있다는 이야기가 되며, 다음페이지의 최상위 ID값도 가져올 수 있게 됩니다.

어쨌거나 위 쿼리는 성능에 있어서는 더이상 좋을 수 없는 쿼리입니다. 분명 글이 많고 사용자 액션이 단순한 사이트라면 반드시 고려해보셔야 할 쿼리입니다. 이전 페이지 구현은 프로그램적으로 머리를 좀 굴려야 할 부분이긴 합니다.

어짜피 다음 페이지 버튼을 눌러야만 가능하니깐 이전 페이지의 ID값은 무조건 가지고 갈 수 있습니다. POST방식으로 이전 페이지 정보를 계속 넘기는 방법도 괜찮은 방법일 것입니다.


SELECT TOP 10 ID FROM TBL where ID > 현재페이지 최상위 ID order by ID ASC 를 어쩔 수 없이 쓰는 것도 한 방법이겠구요. 그래도 다른 쿼리들보다는 빠른 방식이니깐요. <- 이건 저도 테스트 해보진 않았습니다.


#6. 사용자 편의성도 좀 생각해 보자

대부분 웹사이트에서는 NOT IN쿼리가 좋은 방법입니다. 사용자 편의성에 있어서 좋은 선택이니깐요.

이전 1 2 3 4 5 6 7 8 9 10 다음 <- 이게 얼마나 편한 방법입니까^^; 그리고 사람들은 10페이지 이상 조회를 거의 하지 않기도 합니다.

성능을 생각한다면 #5번의 방법이 정말 좋은 방법이지요. 다만 사용자 편의를 위한 인터페이스 구현은 사실상 불가능 합니다. 이전 페이지 구현도 쉽지 않죠.

목표가 생겼습니다.

이전 1 2 3 4 5 6 7 8 9 10 다음

이 기능을 한번 구현해 보죠. 약간 성능 저하가 있더라도... 전체 NOT IN보다는 훨씬 더 빠르게 한다는 목표를 가지고...

일단 쿼리를 하나 보죠.

SELECT TOP pageSize*10+1 ID from TBL where ID <= 1페이지 11페이지 21페이지 등 각 1페이지의 처음 ID

이 쿼리는 페이지 바로가기 버튼 구현을 위한 쿼리입니다.

페이지 사이즈가 10개라고 하면 101개를 가져옵니다. 101개면 너무 많지 않냐 하겠지만 적은 갯수입니다.^^

컬럼이 하나밖에 없기 때문에 속도 저하가 거의 없는 쿼리죠.

이 결과를 가지고 1 2 3 4 5 6 7 8 9 10 다음 버튼 구현이 가능합니다. 이전 버튼 구현은 역시 약간 복잡하죠?

PageInfo = rs.getrows() 등의 좋은 메소드 등을 통해 배열로 만든 후 이 기능을 구현하는 것이 가장 좋은 방법일거라 생각됩니다.


SELECT TOP 10 ID, subject, contents from TBL where ID not in(SELECT TOP pageSize*(현재페이지의한자리숫자-1 / 0일때는 10) ID from TBL  where ID <= 페이지 11페이지 21페이지 등 각 1페이지의 처음 ID order by ID DESC)  and ID <= 페이지 11페이지 21페이지 등 각 1페이지의 처음 ID order by ID DESC


이 쿼리는 실제로 글을 뿌려주는 쿼리입니다. NOT IN이 쓰였네요. 하지만 10페이지 단위로 끊어서 10페이지 글 내에서 NOT IN을 사용하기 때문에 항상 빠른 속도를 내줄 수 있는 쿼리입니다.

페이지당 글 수가 아무리 많아도 100개 이하가 대부분이기 때문에 10페이지 단위로 끊는다고 해도 1000개의 글 내에서 모든 작업이 이루어지기 때문에 성능 저하는 거의(아예) 없다고 보시면 됩니다. 1000개정도 레코드는 아무것도 아니지요.


다만 걸리는 것은 페이지 바로 가기 버튼을 구현하기 위해 비슷한 쿼리를 두번 날렸기 때문에 두배의 비용이 든다는 거겠지요.


#7. 검색에 대한 이야기

검색 속도를 위한 가장 좋은 방법은 고가의 검색 엔진을 사용하는 것입니다. 검색엔진에 DB를 설정하고 URL 저장 방법만 설정해 놓고 스케쥴링만 해 놓으면 검색엔진은 알아서 DB를 검색하고 고쳐진 값에 대해서 URL 링크를 인덱싱해 놓습니다.

속도도 빠르고 한글의 경우 형태소 분석기를 통해 별에 별 검색도 가능하며 서비스 측면에서는 하일라이트 생성기등을 통해 사용자 편의성을 제공해주고... 뭐... 문제는 쩐이군요^^;

MSSQL에서 우리는 like '%xxx%' 검색을 많이 활용하게 됩니다.

SELECT ID, subject, contents from TBL where subject like '%xxx%'

이 쿼리는 xxx를 가지는 모든 subject 컬럼에 대해 검색을 하게 됩니다. 그럼 어떻게 해야 할까요. 넵~ 인덱스를 설정해야 합니다. subject에 대해서 넌클러스터드 인덱스를 설정해 주어야 합니다. 그러면 엄청난 속도 향상을 느낄 수 있습니다.

subject 는 varchar(255)형입니다. 그렇기 때문에 인덱스 설정이 가능합니다.

그럼 contents like '%xxx%'는?

헌데 contents가 text형이거나 varchar(max) (이건 2005에서 지원)라면?...

불행히도 인덱스를 줄수가 없습니다. MSSQL은 그다지 많은 인덱스 공간을 지원해주지 않습니다.

이경우 가능하면 input쪽에 varchar(4000)정도 만큼만 글을 입력하도록 제한하거나 varchar(4000)을 몇개 더 만들어서 DB저장시 나누어서 넣어주는게 좋습니다.

편법이고 지저분한 방법입니다.^^; 그래도 뭐 속도 향상이 있다면야... 모두들 어쩔 수 없이 text형도 검색을 하지만, 좋은 방법이 아닙니다. 글이 1만개만 넘어가다 상당한 부하가 걸릴 거라 생각됩니다.

넌클러스터드 인덱스만이라도 걸려 있다면, 성능은 무척 좋아집니다.

하지만 너무 방법이 지저분 하네요.

더좋은 방법이 있겠죠? MSSQL은 이런 경우를 위해 풀텍스트검색서비스를 지원합니다. varchar(MAX)나 text형 등을 시중의 검색엔진과 비슷한 방식으로 인덱싱하고 검색할 수 있는 방법은 제공합니다. 속도가 그렇게 좋지는 못합니다만...  형태소 분석기도 들어 있고 유사어 검색도 가능하고 정확도 정렬도 가능합니다. 다만 문제는 속도가 생각처럼 나오지는 않는 다는 것입니다. 특히 정확도 정렬을 위한 정렬의 경우 100만건 이상으로 테스트시 1초가 넘어가더군요.

그리고 MSSQL 2000에서 한글은 제대로 지원되지 않았습니다. 2005에서 한글 지원이 되는데... 실제로 띄어쓰기를 하지 않아도 검색이 되는 정도를 확인하였습니다. 동의어 검색도 가능하다고 하는데... 저역시 많은 테스트가 필요할 것 같습니다.

2000에서는 전혀 안쓰는 기능이었지만, 2005에서는 충분히 활용가치가 있을 것 같네요. 다만 호스팅 업체에서 이 기능을 지원하지 않는 다는 것이 가장 큰 문제입니다.^^; 인덱싱 속도는 꽤 빠르나 하드디스크를 많이 차지하기 때문에, 그리고 인덱싱시 꽤 부하를 주기 때문에 지원하지 않는 것 같으며, 2000의 경우 한글 인덱싱 자체가 잘 안되기 때문에 지원을 하지 않는 것 같습니다.

다만 MSSQL 2005라면 프로젝트에 충분히 쓸 수 있을 것도 같습니다. 제가 이번에 활용해보도록 하겠습니다.

상용검색엔진에 비하면 못하겠지만 어느정도 흉내를 내주고 CONTAINS(TABLE)/ FREETEXT(TABLE)등의 4개의 함수를 통해 SQL쿼리문 내에서 사용하기 때문에 개발하기가 수월합니다.

풀텍스트 검색엔진에 대한 이야기는 또 다음에 계속 하도록 하겠습니다.



정리;
속도 향상을 위해 가장 중요한 것은 바로 INDEX 설정!;

반응형

회사에서 리눅스 서버를 모니터링 해야 하여 기존에 Putty 컨테이너에 Putty만 5개를 실행하여 모니터링 하던 사항을

저의 필요에 의해 모니터링 툴을 개발하게 되었습니다.


매번 5개의 Putty를 실행하여 접속하고 모니터링 하려니 너무 귀찮은 감이 있어 프로그램을 만든건 안자랑....


요즘은 라이브러리가 패키지화 되어서 Nuget에 많이 올라와 있는데 사용하기 어려운 부분 없이 잘 되어 있고 

많은 사람들이 예제를 올려놓아 편한 감이 없이 않아 많이 있습니다.


일단 C# 윈폼 프로젝트 생성 후 Nunet 패키지를 설치 합니다.


설치할 패키지는 다음과 같습니다.


SSH.Client

SSH.NET (종속 : Renci.SshNet.Async)


두개의 패키지를 설치 할 경우 Visual Studio 2012의 경우에는 설치가 안될 수 있습니다.


이 경우 Visual Studio Community 2017 를 설치 하면 쉽게 패키지를 설치 할 수 있습니다.


Visual Studio 2012에서는 Nuget 패키지 설치가 잘 안되더군요


그리고 프로젝트 복사하여 Visual Studio 2012에서 프로젝트를 로드 하면 잘 됩니다 ㅎㅎㅎㅎㅎㅎ


다만 Visual Studio Community 2017를 설치 하는데 오래 걸리는건....각자가....잘....하시길....


다음은 코드 입니다.


아래 두 함수는 커넥션 및 결과를 받아 뿌릴 수 있는 내용입니다.


private SshClient Connect_SSH(string host, int port, string user, string passwd)
{
	try
	{
		SshClient cSSH = new SshClient(host, port, user, passwd);

		cSSH.ConnectionInfo.Timeout = TimeSpan.FromSeconds(120);

		cSSH.Connect();

		return cSSH;
	}
	catch (Exception ex)
	{
		label5.Text = "Status : Error";
		return null;
		Console.WriteLine(ex.Message);
		Console.WriteLine(ex.StackTrace);
	}
}

private void recvCommSSHData()
{
	while (true)
	{
		try
		{
			if (Command_sShell != null && Command_sShell.DataAvailable)
			{
				String strData = Command_sShell.Read();

				appendTextBoxInThread(textBox5, strData);
			}
		}
		catch (Exception ex)
		{
			Console.WriteLine(ex.Message);
			Console.WriteLine(ex.StackTrace);
		}

		Thread.Sleep(200);
	}
}


아래는 실제 이벤트 처리 부분이며 해당 코드로 실제 커넥션 후 스레드 동작까지를 사용합니다.


cSSH_Command.CreateShellStream("vt100", 80, 60, 800, 600, 65536) 이 부분은 저도 찾아 보진 않았지만 실제 SSH 통신을 위한 설정이라고만 기억하고 있습니다.

한번쯤 해당 내용을 찾아봐야 할거 같네요


if (cSSH_Command == null) { cSSH_Command = Connect_SSH(접속지 IP 또는 도메인, 포트, Login_ID, Login_PW); Command_sShell = cSSH_Command.CreateShellStream("vt100", 80, 60, 800, 600, 65536); if (cSSH_Command.IsConnected) { label5.Text = "Status : Connected"; Command_thread = new Thread(() => recvCommSSHData()); Command_thread.IsBackground = true; Command_thread.Start(); } }

위와 같이 작업을 하게 되면 마무리가 되며 기본적은 커멘드를 입력 하여 사용 할 수 있는 SSH 환경이 마무리 됩니다.


아래는 직접 만든 모니터링 툴이며 sFTP를 통하여 폴더에 지정된 폴더에 파일을 업로드 할 수 있도록 만들었습니다.


톰캣이 총 3개가 구동이 되다 보니 프로그래스 바 하나가 왔다리 갔다리 하긴 하지만......


아직 Thread를 사용하는데에 익숙치 않지만 공부를 더 해야겠어요 !!



'.NET' 카테고리의 다른 글

C# WMI를 이용한 원격 접속 및 원격 모니터링  (1) 2018.03.30

+ Recent posts