Oracle single quotation

근 2년간 일을 하면서 가장 많은 문제가 발생되는 부분은 아무래도 연동 쪽이 아닌가 하는 생각이 듭니다. 사용자 정보, 다른 페이지 호출, SSO 등 여러 연계 프로그램이 한 프로세스 안에 동작하거나 혹은 별도의 프로세스로 동작을 합니다.

금일 발생 되었던 문제는 사용자 정보를 가져오는 부분에 Oracle 데이터베이스에서 Select 하는 부분에 문제가 있어 사용자 데이터를 1행도 못 가지고 오는 문제가 발생 되었습니다.

제가 주로 사용하는 데이터베이스인 MySQL로 테스트를 해보면 원하는 결과값이 나오지만 오라클에서만 하면 결과값이 다릅니다.

문제의 원인은 Single Quotation 이었습니다.

  1: Select * from user where userid is not null and userid != ‘’ ;

위의 문장은 단순하게 userid 칼럼의 값이 null 이 아니고 공백도 아닌 사용자를 가져오도록 하는 것입니다. (습관적으로 이렇게 사용을 합니다.)
복잡한 쿼리를 전송 하는게 아니기 때문에 해당 Query 문을 Oracle 에서 실행 했습니다.(단순하게 재연을 위해 가상의 테이블을 생성했습니다.)

  1: CREATE TABLE TEST (
  2:    PHONE VARCHAR(10)
  3: )

모든 데이터를 조회했습니다.

  1: SELECT * FROM TEST;

 

single1

입력되어 있는 데이터는 ‘1000’, null, ‘ ’ 입니다.
그러면 NULL이 아닌 데이터만 조회해보겠습니다.

  1: SELECT * FROM TEST WHERE PHONE IS NOT NULL;

single2

출력된 데이터는 아마 ‘1000’, ‘ ‘ 입니다.

그럼 문제의 Single Quotation 이 포함된 Query 를 요청하겠습니다.

  1: SELECT COUNT(*) FROM TEST WHERE PHONE != ''

 

single3

데이터가 없다고 나옵니다..-_-
해결책은 SingleQuotation 을 다른 SingleQuotation 으로 감싸는 방법입니다.

  1: SELECT COUNT(*) FROM TEST WHERE PHONE != ‘''’

single4

처음에 제한 원한 결과가 출력됩니다.

BATCH 파일을 이용한 MySQL 백업 및 파일 정리

아래와 같은 스크립트를 통해 데이터베이스 백업과 기존 파일 삭제 프로세스를 구동 할 수 있다.
Batch 파일을 생성한 이후에 윈도우 작업 스케줄에 포함 시키면 끝

작업 스케쥴러에 포함하기

제어판 – 관리도구 – 작업 스케쥴러 – 기본 작업 만들기
기본 작업 만들기 마법사를 따라 시간 및 주기를 설정함.
작업은 프로그램 시작으로 설정하고 프로그램 찾아보기를 클릭하여 BAT 파일을 선택합니다.



웹 서비스에 별도의 스레드를 가동하여 처리 하려고 했는데 너무 쉽게 처리를 할 수 있는 방법을 배워버렸습니다… Shell 을 이용하는 방법에 대해 공부를 해야 할 것 같습니다. Shell Programming도 아마 무궁 무진 하지만 간단하게 어떻게 사용하는지 그리고 다른 사람이 프로그래밍한 것을 읽을 수 있게 하면 될 것 같습니다.


@echo off
echo Running dump...
REM 파일명으로 사용하기 위해 시간을 구한다. yyyy-MM-dd_hhmmss
set createTime=%date%_%time:~0,2%%time:~3,2%%time:~6,2%

mysqldump -u[사용자아이디] -p[비밀번호] --databases [DB명] -r [저장경로]\%createTime%.sql

REM sql 파일 용량을 줄이기 위해 압축
7z a %createTime%.zip %createTime%.sql

REM 압축 후 SQL 파일 삭제
del %createTime%.sql

REM 이전 파일들을 삭제하는 프로세스 시작
REM 파일 삭제 기간
set day=-365
echo before %day% day ago files deleting ...

REM TEMP 디렉토리에 있는 1년전 파일들을 삭제한다. 파일들을 삭제한다.
forfiles /p D:\TEMP /m *.sql /d %day% /c "cmd /c del @file"

echo before %day% day ago files deleting finish
echo Done!

MySQL str_to_date(string, format)

STR_TO_DATE(str,format) is the inverse of the DATE_FORMAT() function.

STR_TO_DATE() returns a DATETIME value.

The following specifiers may be used in the format string.

The ‘%’ character is required before format specifier characters.

Specifier
Description

%a
Abbreviated weekday name (Sun..Sat)

%b
Abbreviated month name (Jan..Dec)

%c
Month, numeric (0..12)

%D
Day of the month with English suffix (0th, 1st, 2nd, 3rd, ?-)

%d
Day of the month, numeric (00..31)

%e
Day of the month, numeric (0..31)

%f
Microseconds (000000..999999)

%H
Hour (00..23)

%h
Hour (01..12)

%I
Hour (01..12)

%i
Minutes, numeric (00..59)

%j
Day of year (001..366)

%k
Hour (0..23)

%l
Hour (1..12)

%M
Month name (January..December)

%m
Month, numeric (00..12)

%p
AM or PM

%r
Time, 12-hour (hh:mm:ss followed by AM or PM)

%S
Seconds (00..59)

%s
Seconds (00..59)

%T
Time, 24-hour (hh:mm:ss)

%U
Week (00..53), where Sunday is the first day of the week

%u
Week (00..53), where Monday is the first day of the week

%V
Week (01..53), where Sunday is the first day of the week; used with %X

%v
Week (01..53), where Monday is the first day of the week; used with %x

%W
Weekday name (Sunday..Saturday)

%w
Day of the week (0=Sunday..6=Saturday)

%X
Year for the week where Sunday is the first day of the week, numeric, four digits; used with %V

%x
Year for the week, where Monday is the first day of the week, numeric, four digits; used with %v

%Y
Year, numeric, four digits

%y
Year, numeric (two digits)

%%
A literal ‘%’ character

%x
x, for any ‘x’ not listed above

MySQL 서비스 등록

어제 잘 되던 MySQL 에 접속 할 수 없다는 오류 메시지가 출력 되었습니다.
서비스가 죽어 있나 해서 윈도우 서비스 목록에서 재 시작을 하려 했지만
서비스 목록에서 MySQL 이 없어졌습니다. (급 당황)

혹시나 해서 Console 로 접속해서
C:\>net start MySQL

서비스 이름이 잘못되었습니다.

NET HELPMSG 2185을(를) 입력하면 도움말을 더 볼 수 있습니다.

검색해서 찾아보니 서비스에 등록하는 방법이 있습니다.
C:\Program Files\MySQL\MySQL Server 5.1>mysqld -install
Service successfully installed.

C:\Program Files\MySQL\MySQL Server 5.1>
C:\Program Files\MySQL\MySQL Server 5.1>
C:\Program Files\MySQL\MySQL Server 5.1>
C:\Program Files\MySQL\MySQL Server 5.1>net start MySQL
MySQL 서비스를 시작합니다..
MySQL 서비스가 잘 시작되었습니다.

다시 접속을 시도 하니 잘 됩니다.. ㅎㅎ

MySQL 기간 검색 쿼리

-- Date 형 예제
  SELECT 
    userid,
    sender,
    TIME 
  FROM
    cims_received_message 
  WHERE TIME >= DATE_SUB(CURDATE(), INTERVAL 1 MONTH) && TIME <= CURDATE() ;
 
-- Datetime 형 예제
SELECT 
  userid,
  sender,
  TIME 
FROM
  cims_received_message 
WHERE TIME >= DATE_SUB(NOW(), INTERVAL 1 MONTH) && TIME <= NOW() ;

Date Type 과 Datetime Type 둘다 상관 없이 됩니다.

MySQL 대용량 데이터 입력

고객이 요청한 사항들을 보면 기능은 별것 아니지만 다른 부분에서 문제가 발생되는 경우가 있습니다. 이번에도 메신저 사용자에게 전체 공지를 보내는 기능을 추가하는 부분에서 문제가 발생했습니다.
서버 사이드에서 쪽지 처리 방법은 사용자가 쪽지를 보내면 해당 쪽지를 받는 사용자 받은 쪽지함에 저장하고 받는 사용자에게 알림을 주는 형식입니다.
문제가 되는 사항은 받는 사용자 받은 쪽지함에 저장하는 부분입니다.
전체 공지를 한다면 사용자가 2,3천 정보 많게는 1만건 정도의 데이터를 입력해야 하는 경우가 발생합니다. 그렇게 되면 사용자 별로 반복문을 돌면서 하나씩 INSERT를 하기에는 너무 많은 소요 시간이 필요합니다.
테스트 환경
OS: Windows 7 64Bit
Memory: 4GByte
Java Version: 1.6.20 64Bit
MySQL : 5.1

 


INSERT INTO 방식 사용
코드가 정확하지 않을 수 있습니다. (예제로만 작성).

// 데이터베이스가 연결되어 있다는 가정.
List users = new ArrayList();
Message message = new Message(sender, '테스트', new Date());
String sql = "INSERT INTO rMessages (sender, receiver, content, time) values (?, ?, ?, ?) "
PreparedStatement psmt = conn.PrepareStatement(sql);
// 받는 사용자 수만큼 반복
for(String userID, users) {
    psmt.setString(message.getSender());
    psmt.setString(userID);
    psmt.setStrign(message.getContent());
    psmt.setTime(new java.sql.Time(new Date().getTime()));
    psmt.executeQuery(); 
}

위와 같이 실행하면 2천명의 사용자에게 전송했을때 소요시간이 1분정도 소요 됩니다.
1분이라면 빠른시간 같지만 프로그래머에겐 최악의 실행시간입니다.
이런 상황을 해결하기 위해 다른 방법을 찾던 중에 회사 과장님의 조언을 받아 ‘LOAD DATA INFILE’ 성능이 좋다는 조언을 들었습니다.

http://dev.mysql.com/doc/refman/5.1/en/load-data.html [참조]
MySQL INSERT 성능비교
http://kevin.vanzonneveld.net/techblog/article/improve_mysql_insert_performance/


LOAD DATA INFILE 사용

<br />// 임시 데이터 파일을 생성
// 파일명에 한글을 입력하게 되면 LOAD DATA에서 경로를 못찾을 수 있습니다.
String fileName= "D:/adminNotice.txt";
File dataFile = new File(fileName);
PrintWriter pw = null;
try {
    dataFile.createNewFile();
    FileOutputStream fos = new FileOutputStream(dataFile);
    // 주의: 인코딩을 데이터베이스와 동일하게 입력
    pw = new PrintWriter(new OutputStreamWriter(fos,"utf8"),true);  

} catch (IOException e) {
    e.printStackTrace();
}

// 파일 쓰기
for(String userID: users) {
    message.setReceiver(userID);

    // adminNotice.txt
    // admin, receiver1, 안녕하세요, 2011-10-10 07:36:00
    // admin, receiver2, 안녕하세요, 2011-10-10 07:36:00
    // admin, receiver3, 안녕하세요, 2011-10-10 07:36:00
    // admin, receiver4, 안녕하세요, 2011-10-10 07:36:00
    // toString() 메소드를 오버라이딩 해서 위의 출력형태로 변경
    // 마지막에 라인 구분자(\r\n)을 입력해야 합니다.
    // 파일에 데이터를 출력합니다.
    pw.write(message.toString); 
}


// MySQL 데이터 입력
String sql = "LOAD DATA INFILE 'D:/adminNotice.txt' INTO TABLE rMessages FIELDS TERMINATED BY ',' LINES TERMINATED BY '\\r\\n'";
PreparedStatement psmt = conn.PrepareStatement(sql);
psmt.executeQuery();

LOAD DATA INFILE 방식을 사용했을때 소요 시간은 1초 미만으로 소요 됩니다.
일반적인 INSERT INTO 방식보다 코드가 길긴 하지만 소요 시간이 대략 60배 이상 감소 하기 때문에 LOAD DATA 방식을 사용해서 처리를 했습니다.
몇건이 안되는 데이터베이스 입력 이라면 INSERT INTO 방식을 사용하는 편이 좋을 것 같습니다.
주의: LOAD DATA INFILE을 사용하려면 파일 사용 권한이 필요 합니다.
mysql> grant file on . to
coozplz@localhost identified by ‘coozplzPW’;

혹시 다른 방법으로 시도 하신분이 계시면 댓글로 부탁드립니다.

 

번외로 프로시저를 생성해서 처리하는 방법

[프로시저 생성 코드]

CREATE PROCEDURE test(IN receiver VARCHAR(255)) 
BEGIN 
DECLARE var INT;
SET var = 10000001;
WHILE(var < 10002000) DO
INSERT INTO rMessage (
    sender
    , receiver
    , content
    , time) 
        VALUES( 
        'admin'
        ,content
        ,'안녕하세요'
        , now())

END WHILE;
END  //
DELIMITER ; 

[Java code]

for(String userID: users) {
    // CALL test('receiver1');
    // CALL test('receiver2');
    // CALL test('receiver3');
    // CALL test('receiver4');    
    String sql="CALL test('"+userID+"')";
    PreparedStatement psmt = conn.PrepareStatement(sql);
    psmt.executeQuery();
}

프로시저를 사용할 경우 속도는 단순 INSERT INTO 방식보다 빠르게 나왔습니다. 2배정도 나옵니다.