본문 바로가기
OraclE

오라클과 NLS의 찰떡궁합 들여다보기(2)

by 타마마임팩트_쫀 2009. 1. 20.
이번 회는 캐릭터셋을 변경하는 작업에 대한 준비와 그 방법에 대한 것을 알아보고자 한다. 캐릭터셋 변경은 미래의 시스템 확장과 개발의 용이함을 위해 권장되는 작업이지만, 실제 위험성이 크기 때문에 매우 조심해야 한다. 그래서인지 OTN의 NLS 포럼에서도 빈번히 등장하는 질문이기도 한 "캐릭터셋 변경"에 대해 이번 회에서 집중적으로 다루어보기로 하겠다.

  1. 잘못된 캐릭터셋을 사용해온 시스템, 치료해야 하나?
    1. 캐릭터셋 오용의 예
    2. 권장 사항
      1. 1. 바로잡기의 필요성을 인식하라
      2. 2. 절대 함부로 변경하지 말라
  2. 캐릭터셋을 변경 방식과 위험성
    1. 캐릭터셋 변경이란?
      1. 변경 케이스 1) 캐릭터셋 딕셔너리 정보 변경 + 데이터 불변
      2. 변경 케이스 2) 캐릭터셋  딕셔너리 정보 변경 + 데이터 변경
    2. 캐릭터셋 변경의 위험성
      1. 1) 데이터 절삭
      2. 2) 데이터 깨짐
        1. US7ASCII 데이터베이스의 한글 데이터를 exp/imp를 이용하여 KO16MSWIN949 또는 UTF8로 마이그레이션
        2. KO16KSC5601 데이터베이스의 한글 데이터를 exp/imp를 이용하여 KO16MSWIN949 또는 UTF8로 마이그레이션
      3. 3) 애플리케이션 오동작
  3. 캐릭터셋 변경의 실제
    1. ALTER DATABASE 명령을 이용한 딕셔너리 변경
      1. 절차
    2. 데이터 마이그레이션
      1. 준비작업
      2. CSSCAN 실행
        1. 1) CSSCAN 설치
        2. 2) CSSCAN 구동
      3. exp/imp를 이용한 변경
      4. CSALTER를 이용한 변경
  4. 글을 마치며


연.재.순.서.

1회 : 오라클과 NLS의 찰떡궁합 들여다보기(1)
2회 : 오라클과 NLS의 찰떡궁합 들여다보기(2)
3회 : 오라클 GDK를 사용하여 깔끔한 다국어 개발 유틸리티를 만들자
4회 : 한글화된 오라클 제품, 그 이면의 비밀.

잘못된 캐릭터셋을 사용해온 시스템, 치료해야 하나?

캐릭터셋 오용의 예

캐릭터셋과 전혀 맞지 않는 데이터를 저장하고 검색할 수 있을까? 정답은 당연히 없다. 그렇지만, 현실적으로 많은 시스템의 데이터베이스에 그런 데이터가 저장되어 있을 것이라고 믿고 있다.
  1. 오용 1) US7ASCII 데이터베이스에 한글을 저장한 경우
  2. 오용 2) US7ASCII 데이터베이스에 한글, 중국어, 일본어 등 다양한 언어를 저장한 경우
  3. 오용 3) KO16KSC5601 데이터베이스에 확장 한글(비완성형)을 저장한 경우
이 세 가지 경우는 잘못된 캐릭터셋을 사용하고 있는 경우 중 문제가 발생할 만한 대표적인 경우들이다. 다음 표는 이 경우들에 대해 발생할 수 있는 문제점과 취할 수 있는 치료방법을 요약하고 있다.


문 제 감시 시점
지 속적 사용 가능 여부
해 결책
오용1
1) 다른 정상적인 데이터베이스와 DBLINK로 연결시 한글 전달 실패
2) 캐릭터 관련 함수들의 예측할 수 없는 결과
3) 다른 캐릭터셋으로 변경 시도시 데이터 마이그레이션 실패
4) exp/imp를 사용해 다른 데이터베이스로 데이터 마이그레이션 실패
1) 똑같이 잘못된 구성을 가진 데이터베이스 집합 시스템에서는 지속 운영 가능

2) 정상적인 데이터베이스와 정상적인 통신은 불가능함

3) 정상적은 데이터베이스로 업그레이드시 난관 봉착
Full Backup이 필요없다고 절대 말하지 말라!

오라클 엔지니어로 하여금 캐릭터셋을 강제로 KO16MSWIN949로 변경하게 함으로써 해결할 수 있다.

단, 변경 전에 정말 데이터들이 순수하게 KO16MSWIN949라는 한 캐릭터셋으로 커버되는 것이 맞는지 확인해야 한다. 그렇지 않으면 "예 2"의 경우가 된다.

이런 캐릭터셋 변경은 오라클 엔지니어나, 지원 계약을 맺은 고객이 충분히 엔지니어로부터 위험성에 대해 설명을 들은 후에야 시도할 수 있다는 사실을 잊지 말기 바란다. 그렇지 않은 시도는 누구도 책임져 주지 않는다.

캐릭터셋 변경 후 애플리케이션의 디버깅은 필수이다.
오용2
1) 다른 데이터베이스와 DBLINK로 연결시 한글 전달 실패
2) 캐릭터 관련 함수들의 예측할 수 없는 결과
3) 다른 캐릭터셋으로 변경 시도시 실패
4) exp/imp를 사용해 다른 데이터베이스로 데이터 마이그레이션 실패
1) 똑같이 잘못된 구성을 가진 데이터베이스 집합 시스템에서는 지속 운영 가능

2) 정상적인 데이터베이스와 정상적인 통신은 불가능함

3) 정상적은 데이터베이스로 업그레이드시 난관 봉착
최악의 케이스. 다양한 캐릭터셋이 한자리에 모인 잔치집으로, 어떤 한 캐릭터셋으로의 강제 변환이 의미없다.

그나마 각 언어별로 데이터를 구분하여 조회할 수 있다면, 한국어는 한국어별로, 중국어는 중국어별로 physical OS file로 덤프를 떠서 해결하기를 권장한다.

각 언어별로 Manual하게 고쳐나가는 것 밖에는 도리가 없다.

Physical dump file은 SQL Loader를 통해 새로운 UTF8 데이터베이스에 안전하게 로딩할 수 있다. 물론 지난 호에 나온 대로 각 언어별로 NLS_LANG변수값은 확실히 설정해주어야 한다.

SQL Loader를 사용할 때 NLS_LANG값은:
  • 한국어 파일 업로드시 : .KO16MSWIN949
  • 중국어간체 : .GB2312
  • 중국어번체 : .ZHT16MSWIN950 또는 .BIG5

이런 내용은 자신이 없거나, 책임질 수 있는 위치가 아니면, 절대 함부로 시도해서는 안된다.

데이터베이스를 제대로 고쳤을 경우에는 애플리케이션도 재개발 수준의 디버깅을 해야 한다.
오용3
1) exp/imp를 사용해 다른 데이터베이스로 데이터 마이그레이션 실패
2) 가끔 이런 데이터베이스를 기반으로 한 애플리케이션에서 깨진 글자들을 발견하게 됨(종종 우리나라 게시판들에서 볼 수 있는 현상)
3) 운영자들이 자신의 시스템은 정상이라고 생각하는 경우가 많이 감지가 어려움
4) KO16KSC5601(완성형)이 우리나라 한글을 대표하는 캐릭터셋이라는 인식이 문제
1) 일부 비완성형 한글 데이터들의 처리를 주의하면 지속 사용할 수 있고, ,다른 KO16MSWIN949, UTF8기반의 정상적인 한글 데이터베이스와도 어느 정도 통신이 가능하다

KO16KSC5601에서 KO16MSWIN949로 캐릭터셋을 변경한다. KO16MSWIN949는 KO16KSC5601의 수퍼셋이므로 이러한 변경은 자연스럽다. 가장 쉽게 해결될 수 있는 케이스라고 할 수 있다.

그렇지만, 여전히 캐릭터셋 변경이라는 것은 위험하므로, 반드시 지원을 받아야 하는 것은 필수이다.


권장 사항

1. 바로잡기의 필요성을 인식하라

잘못된 캐릭터셋을 가진 데이터베이스를 계속 사용하는 것은 결국 잘못된 애플리케이션 개발을 유도해 전체적으로 잘못된 요소들만으로 이루어진 시스템이 되고 만다. 초기 구성의 부적절함으로 인해 특히 개발자가 고생할 수 있다. 개발자의 입장에서는 KO16MSWIN949나 UTF8기반이 아닌, US7ASCII 데이터베이스를 기반으로 한글을 입출력하는 프로그램을 개발하도록 강요받는다면 결국 억울하게 부가적인 업무를 전가받는 것이다.

캐릭터셋 변경은 비단 데이터베이스 자체 뿐 아니라, 애플리케이션을 비롯한 전체 시스템에 영향을 주기 때문에 경우에 따라 단순한 작업이 아니며, 짜투리 시간을 활용하여 감행할 수 있는 사항이 아니다. 적절한 시기를 선택해야 한다.

여러분이 자바 기반의 웹 개발자라면 다음과 같은 코드를 사용하여 개발한 적이 있는가?

저장:
String p_UserComment = new String(request.getParameter("comment").getBytes("KSC5601"),"8859_1");
pstmt.setString(1,p_UserComment);
pstmt.executeUpdate();

조회:
if(resultSet.next())
{
    String v_UserComment = new String(resultSet.getString("comment").getBytes("8859_1"),"KSC5601");
..
{

이런 억지스런 인코딩의 변환이야말로 잘못된 구성으로 인해 개발자들이 부가적으로 고생하는 단적인 예이다.  한글을 쓰든, 중국어든, 베트남어 기반의 애플리케이션이든, 올바른 구성의 시스템상에서는 저런 식의 코드 변환이 필요할 리가 없다.

꼬여진 개발은 결국 가까운 미래에 "확장 불가", "마이그레이션 불가"라는 벽에 부닥칠 수 있다.

2. 절대 함부로 변경하지 말라

 여러분들 중에 함부로 Production(Live) Database를 변경할 만한 경솔함과 만용을 가진 사람이 있을 거라고 생각해서 이를 언급하는 것은 아니다. 하지만, 제아무리 실력이 뛰어난 DBA라고 할 지라도, 데이터 전체를 무력화 시킬 수 있는 작업을 홀로 감행하지 않기를 거듭 강조하고 싶다. 반드시 적어도 다음 사람들이 연관되고 협력해야 한다
  • 해당 사용업체의 DBA
  • 의사결정 권한을 가지고, 시스템 전체의 운영을 책임지는 책임자
  • 오라클의 지원 엔지니어

캐릭터셋을 변경 방식과 위험성

캐릭터셋 변경이란?

일반적으로 우리가 "캐릭터셋을 변경한다"고 표현하는 작업은 사실 다음 두 가지 작업을 동시에 의미한다. 따라서, 이 말이 사용될 때에는 다음 두 가지 중 어떤 의미를 가리키는지 확실히 짚고 넘어갈 필요가 있다.

변경 케이스 1) 캐릭터셋 딕셔너리 정보 변경 + 데이터 불변

캐릭터셋 변경 시도 시점을 기준으로, 데이터베이스에 저장되어 있는 데이터 자체는 전혀 변경하지 않은 채,  딕셔너리에 있는 캐릭터셋 정보만 변경하는 작업이다.  일반적으로 캐릭터셋에 맞게 데이터를 잘 저장시켜 온 데이터베이스에는 사용할 수 없는 방법이다. 예를 들어 KO16MSWIN949 캐릭터셋을 가진 데이터베이스에 한글을 Windows-949의 코드페이지에 맞게 잘 저장하고 사용해 왔다면 이 데이터베이스의 딕셔너리 정보만을 UTF8으로 바꾸어서는 전혀 엉뚱한 결과를 얻게 되는 것이다. 주로 다음과 같은 상황에서 사용할 수 있다.
  • 비어있는(empty) 데이터베이스 인스턴스 : DBCA(Database Configuration Assistants)를 이용해 데이터베이스 인스턴스를 생성할 때, 캐릭터셋을 잘못 선택했다면, 도중에 작업을 중단하는 방법도 있지만(사실 중단하기를 권장한다),  생성을 마친 후 캐릭터셋 정보를 변환함으로써 문제를 해결할 수 있는 경우가 있다. KO16MSWIN949로 생성해야 할 데이터베이스를 KO16KSC5601로 설정하여 생성했다면, 생성을 마친 후 간단한 절차를 거쳐 KO16MSWIN949로 변경할 수 있는 것이다.

  • 캐릭터셋과는 전혀 엉뚱하고도 다른 캐릭터셋으로 인코딩된 정보를 강제 저장해 온 인스턴스 : 이 경우가 상당히 많은데, 데이터베이스 캐릭터셋은 US7ASCII밖에 없는 것으로 생각을 했던 것일까? US7ASCII은 그 이름만 보아도 절대 한글을 저장할 수 없을 것 같은 캐릭터셋인데, 여기에 한글 데이터를 버젓이 저장해온 시스템들이 상당히 많다. 왜 이런 일이 발생할 수 있는가에 대해서는 지난 연재의 NLS_LANG 변수 코너를 읽어보고 생각해 보기 바란다. 캐릭터셋은 US7ASCII이지만, 실제로는 완성형 + 한글 윈도우즈 코드 페이지(KO16MSWIN949)의 데이터를 저장해 온 경우, 데이터는 현재 상태에서 가공하지 않은 채, 딕셔너리에 있는 캐릭터셋 정보만을 강제로 KO16MSWIN949로 변환하는 방식으로 문제를 해결할 수 있다.
"ALTER DATABASE CHARACTER SET" 명령어가 이에 해당하며, 여기에 대해서는 다음 섹션에서 자세한 절차를 다루도록 하겠다.

변경 케이스 2) 캐릭터셋  딕셔너리 정보 변경 + 데이터 변경

캐릭터셋의 변경과 함께 데이터베이스가 가지고 있는 데이터의 인코딩까지도 변경하는 마이그레이션 작업을 포함하는 경우로, "변경 케이스 1"에 비해 상당한 시간이 소요되는 방대한 작업이며 그에 따라 위험성이 크다. 기존 데이터베이스의 백업은 물론 당연하며, 새로운 캐릭터셋을 가진 새 데이터베이스 인스턴스를 생성하여 그곳에 현재 데이터베이스로부터 데이터를 마이그레이션을 하는 것이 가장 안전하다. 이 경우 보통 exp/imp를 사용하여 해결할 수 있다. 현존하는 데이터베이스의 데이터를 직접 변경하려고 할 경우를 대비해 오라클에서는 CSSCAN이라는 툴을 제공하고 있다.

캐릭터셋 변경의 위험성

"내가 책임지고 변경하겠소!"

일반적으로 존재하는 데이터베이스의 캐릭터셋을 변경하는 작업은 상당히 위험하다. 그래서, 현업에서는 누구도 쉽사리 총대를 매고 이 명령어를 수행하려고 들지 않는다. 괜한 용기를 부렸다가는 돌이킬 수 없는, 또는 돌이키기가 너무나 고통스러운 상황을 맞이할 수 있는 게 이런 작업이다.

실제로 데이터베이스 캐릭터셋을 변경할 때 대략 다음과 같은 문제가 발생할 수 있다.

1) 데이터 절삭

 KO16KSC5601이나 KO16MSWIN949 캐릭터셋을 가진 데이터베이스에서는 한글 한 글자당 2바이트의 물리적 공간이 소모된다.  그래서, 대부분의 데이터베이스 스키마 설계시 한글 한 글자를 2바이트로 감안하는 경우가 많다. 예를 들어, 한글 및 영문 50자까지 허용할 수 있는 컬럼의 경우, 대략 컬럼 길이를 설정할 때 100바이트로 설정하게 된다. 이런 상황에서, 다국어 지원의 필요성이 있어, UTF8로 마이그레이션해야 하는 상황을 생각해 보자. UTF8 데이터베이스에서는 한글 한 글자당 3바이트가 소모되므로 당장 100바이트의 컬럼은 50자가 아닌 33자밖에 저장하지 못하는 컬럼으로 전락한다.

다음과 같은 상황을 생각해 보자. KSC5601데이터베이스에 길이 10바이트의 컬럼을 가진 테이블이 있다고 가정하고, 여기에 5자의 한글을 꽉 채워보자.

SQL> CREATE TABLE t (sval VARCHAR2(10), svalchar VARCHAR2(5 CHAR));
SQL> INSERT INTO t VALUES('한국오라클','한국오라클');

C:\Documents and Settings\jwryoo>set NLS_LANG=.KO16KSC5601
C:\Documents and Settings\jwryoo>exp scott/tiger@KSC5601
...
. . exporting table                              T          1 rows exported
Table(T) or Partition(T:P) to be exported: (RETURN to quit) >
...

C:\Documents and Settings\jwryoo>imp scott/tiger@AL32UTF8
. importing SCOTT's objects into SCOTT
. . importing table                            "T"
IMP-00019: row rejected due to ORACLE error 12899
IMP-00003: ORACLE error 12899 encountered
ORA-12899: value too large for column "SCOTT"."T"."SVAL" (actual: 15, maximum: 10)
Column 1 한국오라클
Column 2 한국오라클          0 rows imported
Import terminated successfully with warnings.

마이그레이션하기 전에 반드시 테이블의 컬럼 길이는 비율에 맞게 확장해야 한다.

-- 10 * 3 / 2 = 15
SQL> alter table t
  2  modify sval varchar2(15);

Table altered.

SQL> alter table t
  2  modify svalchar varchar2(15);

Table altered.

..
C:\Documents and Settings\jwryoo>set NLS_LANG=.KO16KSC5601
C:\Documents and Settings\jwryoo>exp scott/tiger@KSC5601
....
Export terminated successfully without warnings.
:\Documents and Settings\jwryoo>imp scott/tiger@AL32UTF8
Enter table(T) or partition(T:P) names. Null list means all tables for user
Enter table(T) or partition(T:P) name or . if done: T

Enter table(T) or partition(T:P) name or . if done: .

. importing SCOTT's objects into SCOTT
. . importing table                            "T"          1 rows imported
Import terminated successfully without warnings.

C:\Documents and Settings\jwryoo>sqlplus scott/tiger@AL32UTF8
SQL> SELECT * FROM t;

SVAL                           SVALCHAR
------------------------------ -----------
한국오라클                      한국오라클

US7ASCII 데이터베이스에 한글을 저장하게 될 경우, 한 글자당 거의 100% 2바이트로 저장된다. 사용자들이 일반적으로 사용하는 한글 클라이언트는 대부분 Windows-949(한글 Windows OS)나 KSC5601 완성형(UNIX계열)이고 이들은 모두 2바이트이기 때문이다. 따라서, 여기에 한글을 저장하므로 마찬가지로 "한글최대길이 * 2"의 길이만큼 컬럼을 지정하게 된다. 하지만, US7ASCII 데이터베이스를 바로잡을 때에는 KO16MSWIN949 캐릭터셋으로 강제변환하게 되므로 데이터에 대한 변경은 없다. 따라서, 이 부분만큼은 크게 문제가 없다고 하겠다. 하지만 역시 USASCII에서 UTF8으로 갈 경우에는, KO16MSWIN949로 강제 변경 후, 데이터 마이그레이션을 해야 하므로, 반드시 스키마 점검을 해야 할 것이다.

2) 데이터 깨짐

데이터 마이그레이션 시 일부 데이터 혹은 전체 데이터가 손상되는 경우가 발생한다. 그래서 특히 타겟 데이터베이스와 동일한 조건의 데이터베이스를 이용해 테스트를 충분히 하는 것은 필수 과정이다. 특히 exp/imp를 이용하여 데이터 마이그레이션을 할 때 이런 현상이 나타난다.
US7ASCII 데이터베이스의 한글 데이터를 exp/imp를 이용하여 KO16MSWIN949 또는 UTF8로 마이그레이션
데이터 전체가 손상된다. 불가능하며 시간낭비이다. 애초에 시도하지 않는 것이 좋다. 일부 DBA들이 꼼수를 사용하고 있으나, 그 위험은 시도하는 본인의 부담이라는 것을 잘 인식해야 한다(at their own risk).
KO16KSC5601 데이터베이스의 한글 데이터를 exp/imp를 이용하여 KO16MSWIN949 또는 UTF8로 마이그레이션
이 경우가 데이터 일부 깨짐 현상이 발생할 수 있는 대표적인 케이스이다. KO16KSC5601은 만능 한글 캐릭터셋이 아니라 완성형 2350자만을 지원한다는 사실은 이미 누누히 강조해 왔을 것이다. 하지만 NLS_LANG=.KO16KSC5601으로 설정함으로써, 많은 데이터베이스가 비완성형 한글 8822자들의 일부를 저장하고 사용해 왔을 것으로 믿는다. 이와 같은 데이터는 정상적인 절차를 거쳐 exp/imp를 이용해 다른 캐릭터셋의 데이터베이스로 마이그레이션이 불가능하다.

C:\Documents and Settings\jwryoo>set NLS_LANG=.KO16KSC5601

C:\Documents and Settings\jwryoo>sqlplus scott/tiger@KSC5601
SQL> select sval from t;

SVAL
---------------
커피숖

C:\Documents and Settings\jwryoo>exp scott/tiger@KSC5601
....

C:\Documents and Settings\jwryoo>imp scott/tiger@AL32UTF8
..
Enter table(T) or partition(T:P) name or . if done: .

. importing SCOTT's objects into SCOTT
. . importing table                            "T"          1 rows imported
Import terminated successfully without warnings.

SQL> sqlplus scott/tiger@AL32UTF8
SQL> select sval from t;

SVAL
------------------------------
커피?

SQL> select dump(sval) from t;

DUMP(SVAL)
-------------------------------------------------

Typ=1 Len=12: 236,187,164,237,148,188,239,191,189

-- 231 191 189 : Corrupted character  (ㅤ숖 : 236 136 150)

이런 경우는 "ALTER DATABASE" 명령어로 KO16KSC5601에서 KO16MSWIN949로 딕셔너리 변경(변경 케이스 1)만을 한 후 작업하면 문제를 피할 수 있다.

3) 애플리케이션 오동작

US7ASCII 데이터베이스에 한글을 저장하고 그것을 기반으로 만들어진 애플리케이션은 과연 캐릭터셋을 올바르게 변경한 후에 안전할 수 있을까? 정답은 물론 "없다"이다. 문자열을 처리하는 연산에서 대혼란이 올 수 있다. US7ASCII 데이터베이스에 저장된 한글의 모습을 들여다보자.

US7ASCII:



SQL> select sval from t;

SVAL
-------------------------------------

한국오라클

SQL> select length(sval) from t;

LENGTH(SVAL)
------------
          10

SQL> select instr(sval,'오') from t;

INSTR(SVAL,'오')
----------------
               5

SQL> select substr(sval,3,2) from t;

SU
--


SQL> select concat(substr(sval,3,3),'좋아') from t;

CONCAT(
-------
국옥종

KO16KSC5601:
KO16MSWIN949:
UTF8, AL32UTF8:


SQL> select sval from t;

SVAL
-------------------------------------

한국오라클

SQL> select length(sval) from t;

LENGTH(SVAL)
------------
           5

SQL> select instr(sval,'오') from t;

INSTR(SVAL,'오')
----------------
               3

SQL> select substr(sval,3,2) from t;

SUBSTR(SVAL,3,2)
----------------
오라

SQL> select concat(substr(sval,3,3),'좋아') from t;

CONCAT(SUBSTR(SVAL,3,3),'좋아')
------------------------------------
오라클좋아

예에서 보는 바와 같이 스트링 연산들이 US7ASCII에서는 왜곡될 수 밖에 없다. 따라서, 애플리케이션 개발자들도 이 왜곡된 결과를 바로잡는 코드를 부가적으로 애플리케이션에 넣도록 강요받게 된다.

US7ASCII:
왜곡된 애플리케이션 코드를 이용한 결과 바로 잡기


SQL> select sval from t;

SVAL
-------------------------------------

한국오라클

SQL> select length(sval)/2 from t;

LENGTH(SVAL)/2
------------
          5

SQL> select instr(sval,'오') from t;    -- 왜곡된 결과 그대로 사용

INSTR(SVAL,'오')
----------------
               5

SQL> select substr(sval,5,4) from t;

SU
--


SQL> select concat(substr(sval,5,6), '좋아') from t;

CONCAT(SUB
----------
오라클좋아

KO16KSC5601:
KO16MSWIN949:
UTF8, AL32UTF8:


SQL> select sval from t;

SVAL
-------------------------------------

한국오라클

SQL> select length(sval) from t;

LENGTH(SVAL)
------------
           5

SQL> select instr(sval,'오') from t;

INSTR(SVAL,'오')
----------------
               3

SQL> select substr(sval,3,2) from t;

SUBSTR(SVAL,3,2)
----------------
오라

SQL> select concat(substr(sval,3,3),'좋아') from t;

CONCAT(SUBSTR(SVAL,3,3),'좋아')
------------------------------------
오라클좋아

보는 바와 같이 원하는 결과를 얻기 위해서는 개발자가 일일이 "한글은 2바이트"라는 가정 하에 바이트 단위의 연산을 해 가며 프로그래밍을 해야 한다.

오라클을 이용해 바이트단위의 프로그래밍을 하는 것은:
- 최신식 오디오 시스템을 구축해 놓고 라디오 가능만 사용하는 것
- 전자동 세탁기를 사 놓고, 손으로 통을 돌려 세탁하는 것

- 가스렌지를 구비해 놓고  성냥으로 불을 켜는 것

대조표의 오른쪽에 있는 것처럼, 한글을 지원하는 데이터베이스의 경우 한글을 2바이트로 저장하든, 3바이트로 저장하든 스트링 연산 함수는 기본적으로 캐릭터 단위로 동작하므로 애플리케이션의 코드가 동일함을 알 수 있다. 잘못된 데이터베이스 캐릭터셋은 잘못된 애플리케이션 코딩을 유도하고, 이는 나중에 제대로 된 데이터베이스 캐릭터셋으로 마이그레이션시 심각한 휴유증을 양산할 소지가 있다. 따라서, 캐릭터셋 변경시 애플리케이션의 스트링 연산 부분을 손보는 것은 필수작업이다.

캐릭터셋 변경의 실제

ALTER DATABASE 명령을 이용한 딕셔너리 변경

앞서 살펴본 "변경 케이스 1"에 해당하는 것으로 데이터에 대한 어떠한 검토나 수정은 없이 딕셔너리의 캐릭터셋 정보만을 교체하는 작업이다. KO16KSC5601을 KO16MSWIN949로 변경할 때 유용하다. 그리고 한글만을 저장해 온 US7ASCII 데이터베이스 또한 이 방식으로 KO16MSWIN949로 변경할 수 있다.

절차

 SQL> SHUTDOWN IMMEDIATE;
<do a full backup>
SQL> STARTUP MOUNT;
SQL> ALTER SYSTEM ENABLE RESTRICTED SESSION;
SQL> ALTER SYSTEM SET JOB_QUEUE_PROCESSES=0;
SQL> ALTER SYSTEM SET AQ_TM_PROCESSES=0;
SQL> ALTER DATABASE OPEN;
SQL> ALTER DATABASE CHARACTER SET KO16MSWIN949;
SQL> SHUTDOWN IMMEDIATE;
SQL> STARTUP;

SQL> SHUTDOWN IMMEDIATE;
Database closed.
Database dismounted.
ORACLE instance shut down.
SQL> startup mount
ORACLE instance started.

Total System Global Area  171966464 bytes
Fixed Size                   777956 bytes
Variable Size             145760540 bytes
Database Buffers           25165824 bytes
Redo Buffers                 262144 bytes
Database mounted.
SQL> ALTER SYSTEM ENABLE RESTRICTED SESSION;

System altered.

SQL> ALTER SYSTEM SET JOB_QUEUE_PROCESSES=0
  2  ;

System altered.

SQL> ALTER SYSTEM SET AQ_TM_PROCESSES=0;

System altered.

SQL> ALTER DATABASE OPEN
  2  ;

Database altered.

SQL> ALTER DATABASE CHARACTER SET KO16MSWIN949;

Database altered.

-- 한편 현재 캐릭터셋의 Superset이 아닌 캐릭터셋으로는 변경이 불가능하다.

SQL> ALTER DATABASE CHARACTER SET WE8DEC;
ALTER DATABASE CHARACTER SET WE8DEC
*
ERROR at line 1:
ORA-12712: new character set must be a superset of old character set

-- 다시 데이터베이스를 재구동하면 이제 어엿한 KO16MSWIN949 데이터베이스가 되어 있다.

C:\Documents and Settings\jwryoo>set NLS_LANG=.KO16MSWIN949

C:\Documents and Settings\jwryoo>sqlplus scott/tiger@KSC5601

SQL> select sval from t;

SVAL
---------------
커피숖

변경 후에는 이제 exp/imp를 이용하여 UTF8 데이터베이스로 마이그레이션하는 것도 가능해진다.

이 방식을 사용하려면 반드시 새로운 캐릭터셋은 기존 캐릭터셋의 Superset이어야 한다. 그렇지 않으면 기존 데이터의 안전이 보장될 수 없기 때문이다.

기존 캐릭터셋
새로운 캐릭터셋
변경 가능 여부
US7ASCII
KO16KSC5601/KO16MSWIN949/UTF8/AL32UTF8
가능
KO16KSC5601
KO16MSWIN949
가능
KO16MSWIN949
UTF8
불가능
UTF8
AL32UTF8
가능

강제로 캐릭터셋을 변경하는 옵션 또한 알려져 있으나, 이 지면에 소개할 수 없는 점 양해 바란다. 간단하지만 극히 위험한 작업이다. 이미 강조했지만, 반드시 오라클 엔지니어나 오라클과 지원계약을 맺은 고객의 담당자가 충분히 그 위험성과 방법에 대해 숙지한 후에 작업할 수 있따는 것을 명심하기 바란다.

데이터 마이그레이션

준비작업

  • 데이터베이스 컬럼 길이 점검 : 데이터 자체가 변경되므로 데이터의 길이 또한 변경될 수 있다.
  • 애플리케이션 점검 : 애플리케이션 또한 스키마 및 캐릭터셋 변경으로 인해 영향을 받지 않는지 점검한다. 특히 UTF8으로 갈 경우, 한글을 2바이트로 가정하고 작성한 코드가 없는지 점검한다.

CSSCAN 실행

CSSCAN은 캐릭터셋 변경시 발생할 수 있는 문제점을 미리 감지하고 보고서를 생성해준다는 점에서 매우 유용하다. 비록 이것을 실행하는 것이 완전히 필수적인 요소라고 할 수는 없어도(CSALTER 방식에서는 필수, 지난 일주일 내에 CSSCAN을 수행한 결과가 있어야 함) 지금 보유하고 있는 데이터가 소중하다고 생각된다면 반드시 돌려보는 것이 좋을 것이다.

1) CSSCAN 설치
SQL> connect / as sysdba
Connected.
SQL> @/home/oracle/oracle/rdbms/admin/csminst.sql
2) CSSCAN 구동
[oracle@krrnddel oracle]$ csscan system/..

Connected to:
Oracle Database 10g Enterprise Edition Release 10.1.0.2.0 - Production
With the Partitioning, OLAP and Data Mining options

(1)Full database, (2)User, (3)Table: 1 > 2

Current database character set is KO16KSC5601.

Enter new database character set name: > KO16MSWIN949

Enter array fetch buffer size: 102400 >

Enter number of scan processes to utilize(1..32): 1 >

Enter user name to scan: > SCOTT

Enumerating tables to scan...

. process 1 scanning SCOTT.DEPT[AAAL+oAAEAAAAAJAAA]
. process 1 scanning SCOTT.EMP[AAAL+qAAEAAAAAZAAA]
. process 1 scanning SCOTT.BONUS[AAAL+sAAEAAAAApAAA]
. process 1 scanning SCOTT.TESTTBL[AAAME1AAEAAAAA5AAA]
. process 1 scanning SCOTT.KANGYS[AAAME5AAEAAAABBAAA]
. process 1 scanning SCOTT.DEPTENTITY_GRADUATE_DEPTCOMP[AAAMI8AAEAAAABJAAA]
. process 1 scanning SCOTT.DEPTENTITY_DEPT_DEPTCOMP[AAAMJAAAEAAAAB5AAA]
. process 1 scanning SCOTT.NLSTECH_SAMPLE_SORT_KOREAN_M[AAAMm8AAEAAAABZAAA]
. process 1 scanning SCOTT.NLSTECH_SAMPLE_SORT_KSC5601[AAAMm9AAEAAAACBAAA]
. process 1 scanning SCOTT.CHARSET_TEST[AAAMwLAAEAAAACJAAA]
. process 1 scanning SCOTT.T[AAAM9IAAEAAAACRAAA]

Creating Database Scan Summary Report...

Creating Individual Exception Report...

Scanner terminated successfully.

$ ls scan*
scan.err  scan.out  scan.txt

  • scan.out : STDOUT에 출력된 결과를 저장해 놓은 파일
  • scan.err : 캐릭터셋 변경시 손실되는 데이터

    scan.err의 내용물 예:

    [Application data individual exceptions]

    User  : SCOTT
    Table : TESTTBL
    Column: VAL
    Type  : VARCHAR2(100)
    Number of Exceptions         : 1
    Max Post Conversion Data Size: 6

    ROWID              Exception Type      Size Cell Data(first 30 bytes)
    ------------------ ------------------ ----- ------------------------------
    AAAME1AAEAAAAA9AAA lossy conversion         <8c>c
    ------------------ ------------------ ----- ------------------------------
    ...

  • scan.txt : 종합 보고서. 다음 예에 나온 섹션들이 가장 핵심적인 부분이라 하겠다. Data Dictionary 분석 테이블이 비어 있는 것은위의 CSCSAN 실행을 User단위로(SCOTT 사용자에 한해) 테스트를 했기 때문이다. 새로운 캐릭터셋에서도 변경이 없는 데이터(Changeless, 주로 아스키 영문 데이터)와 변경 가능한(Convertible) 데이터들의 합이 정상적으로 마이그레이션되는 데이터의 비중을 보여준다.
    • Truncation : 변경 후 Truncate될 데이터
      Lossy : 변경 후 손상될(깨질) 데이터

    ...
    [Data Dictionary Conversion Summary]

    Datatype                    Changeless      Convertible       Truncation            Lossy
    --------------------- ---------------- ---------------- ---------------- ----------------
    VARCHAR2                             0                0                0                0
    CHAR                                 0                0                0                0
    LONG                                 0                0                0                0
    CLOB                                 0                0                0                0
    VARRAY                               0                0                0                0
    --------------------- ---------------- ---------------- ---------------- ----------------
    Total                                0                0                0                0
    Total in percentage              0.000%           0.000%           0.000%           0.000%


    [Application Data Conversion Summary]

    Datatype                    Changeless      Convertible       Truncation            Lossy
    --------------------- ---------------- ---------------- ---------------- ----------------
    VARCHAR2                            37               20                1                5
    CHAR                                 0                0                0                0
    LONG                                 0                0                0                0
    CLOB                                 4                0                0                0
    VARRAY                               0                0                0                0
    --------------------- ---------------- ---------------- ---------------- ----------------
    Total                               41               20                1                5
    Total in percentage             61.194%          29.851%           1.493%           7.463%

    [Distribution of Convertible, Truncated and Lossy Data by Table]

    USER.TABLE                                              Convertible       Truncation            Lossy
    -------------------------------------------------- ---------------- ---------------- ----------------
    SCOTT.CHARSET_TEST                                                0                0                3
    SCOTT.DEPTENTITY_DEPT_DEPTCOMP                                    2                1                0
    SCOTT.NLSTECH_SAMPLE_SORT_KOREAN_M                                5                0                0
    SCOTT.NLSTECH_SAMPLE_SORT_KSC5601                                 5                0                0
    SCOTT.T                                                           0                0                1
    SCOTT.TESTTBL                                                     8                0                1
    -------------------------------------------------- ---------------- ---------------- ----------------

    [Distribution of Convertible, Truncated and Lossy Data by Column]

    USER.TABLE|COLUMN                                       Convertible       Truncation            Lossy
    -------------------------------------------------- ---------------- ---------------- ----------------
    SCOTT.CHARSET_TEST|CHARCOL                                        0                0                3
    SCOTT.DEPTENTITY_DEPT_DEPTCOMP|DNAME                              2                1                0
    SCOTT.NLSTECH_SAMPLE_SORT_KOREAN_M|TEXT                           5                0                0
    SCOTT.NLSTECH_SAMPLE_SORT_KSC5601|TEXT                            5                0                0
    SCOTT.T|SVAL                                                      0                0                1
    SCOTT.TESTTBL|VAL                                                 8                0                1
    -------------------------------------------------- ---------------- ---------------- ----------------

    [Indexes to be Rebuilt] : 캐릭터셋 변경 후 재생성되어야 하는 인덱스들의 리스트

    USER.INDEX on USER.TABLE(COLUMN)                                                        
    -----------------------------------------------------------------------------------------
    -----------------------------------------------------------------------------------------

 이 결과를 보면 어떤 테이블의 어떤 행에서 어떤 컬럼에 어떤 데이터가 손상이 될 수 있는지 살펴볼 수 있다. 단 US7ASCII 데이터베이스에 한글을 저장해 놓고 KO16MSWIN949 또는 UTF8로 데이터 마이그레이션 및 캐릭터셋 변환이 가능한지 CSSCAN을 돌려보고 싶다면 그러지 않기를 권장한다. 그것은 불가능하다는 것은 명백하고 잘못하면 엄청난 크기의 리포트만을 하염없이 기다려야 할 지도 모른다.

그런데 데이터 딕셔너리에는 Changeless만 있는 것이 사실 가장 안전하다. 변경 가능한(Convertible) 데이터가 존재하는 것이 100% 나쁘다고 할 수는 없겠지만, 딕셔너리가 수정되었을 때의 파장은 알 수 없으므로 이는 조심해야 한다.
  • CLOB에 저장된 데이터 : US7ASCII에서는 CLOB에 있는 데이터 역시 US7ASCII 형식으로 저장되지만, KO16MSWIn949나 UTF8같은 멀티바이트 캐릭터셋에서는 데이터가 UCS-2와 호환되는 고정 바이트로 CLOB에 저장된다. 따라서, US7ASCII에서 다른 멀티바이트 캐릭터셋으로 마이그레이션하려고 CSSCAN을 구동했을 때에는 CLOB내의 데이터는 모두 Changeless가 아니라 Convertible로 처리될 것이다.
  • 당연한 이야기이지만, 캐릭터들은 기존 캐릭터셋과 새로운 캐릭터셋에 모두 존재하지만 실제 바이너리값에서는 차이가 있다. KO16MSWIN949에서의 "가"(176 160)와 UTF8에서의 "가"(234 176 128)는 엄연히 바이너리 값이 다르다. 따라서, 멀티바이트(한글)로 테이블명이나 인덱스명을 생성했을 때 CSSCAN은 이들을 Convertible로 처리할 것이다.
exp/imp를 사용하여 새로운 데이터베이스로 데이터와 딕셔너리를 마이그레이션할 것이 아니라면(CSALTER를 사용할 것이라면), 오라클은 안전을 위해 CLOB에 들어있지 않은 딕셔너리 Convertible 데이터들을 마이그레이션 전에 처리할 것을 권장한다
  • 한글 이름으로 된 테이블 이름 변경
  • 한글 이름으로 된 테이블 및 객체들은 exp후 drop시키고, 마이그레이션 후 다시 imp한다
기타 딕셔너리에서 Lossy나 Corrupted된 데이터가 발생한다면 이는 심각하며 반드시 전문가나 오라클 기술 지원 인력과 상의해야 할 문제이다.

exp/imp를 이용한 변경

전통적인 백업 방식을 이용하여 마이그레이션하는 방법으로 다른 버전의 데이터베이스들끼리의 데이터 마이그레이션 작업을 상당히 깔끔하게 할 수 있다는 장점이 있다. 하지만, exp가 잘되었다거나 imp가 잘 되었다는 것이 데이터가 안전하게 저장되었다는 것을 검증해 주지는 못한다. 예를 들어 KO16KSC5601 데이터베이스에서 exp한 파일에 잘못된 데이터(숖)이 있다고 하더라도 이것을 다시 UTF8 데이터베이스에 imp했을 때 그것을 발견하지 못한다. exp/imp에 대해서는 앞서 충분히 보여주었으므로 따로 예제를 들지 않겠다. NLS_LANG 변수값의 설정이 매우 중요하다는 사실은 확실히 기억하기 바란다.

CSALTER를 이용한 변경

실제 CSALTER를 수행하기 전에 반드시 Full Database Scan이 이루어져야 한다. 그렇지 않으면 다음과 같은 메시지를 만나게 될 것이다.

SQL> @@/home/oracle/oracle/rdbms/admin/csalter.plb normal

0 rows created.


Function created.


Function created.


Procedure created.

This script will update the content of the Oracle Data Dictionary.
Please ensure you have a full backup before initiating this procedure.
Would you like to proceed ?(Y/N)?y
..
Full database scan is required

csalter.plb는 SYS사용자만이 수행할 수 있고, 수행 시점에서 이 데이터베이스에 접속된 유일한 세션이어야 한다. 그리고 반드시 새로운 캐릭터셋은 기존 캐릭터셋의 Superset이어야 한다. 한글 지원 캐릭터셋과 US7ASCII 캐릭터셋들의 Subset-Superset 관계는 앞서 ALTER DATABASE 방식을 설명하는 중 소개된 테이블을 참조하기 바란다.

글을 마치며

캐릭터셋 변경은 필요하고도 위험하다. 캐릭터셋을 잘못 사용해 왔다는 사실을 알게 되는 시점부터 운영자는 고민에 빠진다. 캐릭터셋을 잘못 사용해 왔다는 것은 그 상단의 애플리케이션조차도 잘못되어 있을 가능성이 높다는 의미이다. 따라서, 데이터를 잘 보존하면서 캐릭터셋을 옮기는 것도 리스크가 크지만, 그 상단의 애플리케이션 또한 다시 디버깅하고 테스팅해야 한다는 사실 또한 운영자에게 두려움으로 다가올 수 있다.
  • 캐릭터셋 변경에 대한 방식을 충분히 습득하자
  • 캐릭터셋 변경에 따른 위험성을 충분히 이해하자
  • 캐릭터셋 변경에 대한 관계자들의 폭넓은 동의가 확보하자
  • 캐릭터셋 변경 전에 반드시 변경을 되돌릴 수 있는 장치를 마련하자
  • 캐릭터셋 변경 전에 충분히 테스트 DB로 테스트하자

[출처] http://www.oracle.com/technology/global/kr/pub/columns/oracle_nls_2.html