레이블이 VARCHAR인 게시물을 표시합니다. 모든 게시물 표시
레이블이 VARCHAR인 게시물을 표시합니다. 모든 게시물 표시

2010년 12월 30일 목요일

CHAR vs VARCHAR ?



CHAR를 사용할지 VARCHAR를 사용할 지의 문제는
문자열 데이터의 타입을 선정할 때, 항상 고민하는 부분이 아닐까 생각된다.

CHAR VARCHAR의 장단점을 확인해보고, 그 장단점에 맞게 선택하는 것이 최적일 것이라 생각한다.

1. CHAR
  1.1 특징
    1.1.1 Disk에 저장 시, 고정 길이로 저장된다.
    1.1.2 고정 길이이기 때문에, 별도의 유효(실질) 데이터 길이를 관리하지 않는다.
  1.2 장점
    1.2.1 고정 길이이기 때문에, 이 컬럼의 변경으로 인해서 Record의 위치 이동(Row migration)이 필요한 경우는 없다.
    1.2.2 별도의 유효(실질) 데이터 길이를 관리하지 않기 때문에 VARCHAR에 비해서 최소 1~2 바이트는 절약된다.
  1.3 단점
    1.3.1 실제 데이터의 길이와 관계 없이 테이블 생성 시(CREATE TABLE...)에 정의된 사이즈만큼 Disk를 사용한다.


2. VARCHAR
  2.1 특징
    2.1.1 Disk에 저장 시, 가변 길이로 저장된다.
    2.1.2 가변 길이이기 때문에, 별도로 실제 데이터의 길이를 관리해야 한다. (데이터의 길이에 따라서 1~2바이트가 사용됨)
  2.2 장점
    2.2.1 실제 데이터의 길이 변화가 심해도 Disk 공간을 절약할 수 있다.
  2.3 단점
    2.3.1 이 컬럼의 값이 예전보다 긴 값으로 변경될 경우, Record의 위치 이동(Row migration)이 발생할 수 있다.
    2.3.2 실제 데이터의 길이 값을 관리해야 하기 때문에 1~2바이트가 더 필요하다.

여기서 참고로
Disk의 공간을 더 차지한다는 것은, 데이터 파일의 각 페이지 낭비가 커서 데이터 파일의 사이즈가 커지는 결과를 가져오고, 이는 큰 Disk가 필요하다는 것보다는 MySQL이 쿼리를 처리하기 위해서 읽어야 하는 Disk의 페이지 수가 많아진다는 것이 더 큰 영향이라고 볼 수 있다. (, 레코드 100건을 읽기 위해서 데이터 파일 사이즈가 작을 때에는 1 Disk 읽기로 완료되었던 작업이 파일 사이즈가 커져서 Disk 읽기 2번이 필요 해질 수 있다는 것을 의미한다.)







그럼, 이제 위의 내용을 기초로 어떤 데이터를 CHAR로 또는 VARCHAR 선택해야 할지 생각 해보자.
A. 컬럼의 모든 값의 길이가 동일한 경우
B. 1~2 글자의 Alpha-numeric
C. 거의 대부분이 15자리 Alpha-numeric 값인데 가끔 10~14 자리 값이 들어올 수 있는 경우

조금은 억지가 있는 질문들이지만

AB의 케이스 모두 일반적으로 CHAR로 설정하는 것이 좋지만,

B의 케이스는 UTF8 CHAR(1) 3바이트, EUCKR CHAR(1) 2바이트를 차지하게 되는데
저장되는 데이터가 Alpha-numeric이라면 조금씩의 낭비는 발생하게 되며
이를 피하기 위해서는 해당 컬럼만 CharacterSet을 별도로 지정 해주는 방법도 있지만 
상당한 혼란을 초래할 수도 있다. 일반적으로 코드 형태의 컬럼이 B케이스가 아닐까 생각된다.

C 케이스의 경우에도, 개인적으로는 CHAR 타입이 좋지 않을까 생각한다. 만약 이 컬럼의 값의 길이가 
자주 변경(10~15자리 사이에서)이 된다면 더더욱 CHAR로 선정함이 더 좋다
하지만 값의 길이가 가변 폭이 크다면 VARCHAR로 선택하는 것이 좋다.

조인시 서로 비교되는 컬럼을 동일 타입으로 유지하는 것 이외에 특별히 "이건 꼭 CHAR로 해야돼" 라고 
생각할 필요는 별로 없어 보인다.
하지만, VARCHAR로 해야 될 것을 CHAR로 적용해서는 무리가 있다.



개인적으로 CHAR VARCHAR를 선정하는 기본적인 절차(상당히 주관적이고 개인적인 판정 절차임)
1. 모든 값이 고정 길이 값인가 ?    -> CHAR
2. 모든 값은 아니어도 대 부분(대략 90% 이상)의 값이 고정 길이이고 자주 변경이 발생하는가 ? -> CHAR
3. 5 바이트 미만의 코드 값인가 ?   -> CHAR
4. 그 이외에는 모두 VARCHAR 로 선정

2010년 12월 29일 수요일

문자열 (CHAR, VARCHAR) 타입의 비교 및 정렬 방식



DBMS 종류별로 CHAR 타입의 데이터를 읽어 오고 비교하는 방식에서 조금씩의 차이가 있다.
가끔 오라클과 같은 타 DBMS에 익숙한 사용자는 자주 이런 부분을 혼동하는 경우가 많다.

우선 CHAR 타입이 고정 길이로 관리된다는 것은 타 DBMS와 동일하다.
하지만, CHAR 타입의 필드를JDBCPHP 또는 C api를 이용하는 Application에서 읽어 왔을 때, 오라클과는 달리 지정된 길이만큼 공백으로 채워져 있지 않다는 것이다.
, CHAR(10)이라는 컬럼에 "ABC"라는 값을 저장해 두었다고 가정했을 때, Application에서 그 필드를 Fetch해 보면, 오라클은 "ABC       "와 같이 필드 값을 리턴하지만, MySQL "ABC" 만 리턴하게 된다. 또한 MySQL은 사용자가 입력한 내용의 마지막에 공백이 있는 경우, 그 공백마저도 제거해 버리게 된다. 공백이 아닌 문자 뒤에 붙어 있는 공백만 그렇게 처리된다.

아래와 같이 저장된 값들은 Application에서 읽어 보면 실제 우측에 표기된 것과 같은 값을 얻게 된다.
l  " ABC"  -> " ABC"
l  "AB C"  -> "AB C"
l  "A BC " -> "A BC"

CHAR 타입의 경우, MySQL은 마지막에 붙어 있는 공백 문자가 사용자의 입력인지 MySQL이 내부적으로 자리 수를 채우기 위해서 붙여 넣은 공백인지 구분하지 않기 때문이다.
그리고, 당연히 "ABC" "ABC  "는 동일한 값으로 간주되기 때문에 UNIQUE 인덱스가 있는 경우에는 DUPLICATE-KEY 오류를 발생시키게 된다.

만약, 오라클과 같은 형태로 CHAR 타입의 자리 수만큼 공백이 추가된 상태로 값을 리턴 받기를 원한다면 my.cnf (또는 my.ini) 설정 파일에 sql_mode 라는 변수를 정의하고, 그 값에 "PAD_CHAR_TO_FULL_LENGTH" 설정을 추가 해주면 오라클과 동일한 형태의 결과 Application에서 전달 받을 수 있게 된다.

VARCHAR 타입의 경우, 내부적인 실제 데이터의 길이를 보관하기 때문에 마지막에 붙어 있는 공백 문자를 같이 데이터로 간주하고 저장 및 읽어 올 때 같이 리턴하게 된다.

그리고, 또 한가지 MySQL에서 주의해야 할 사항은, CHAR 타입과 VARCHAR타입의 비교에서는 뒤에 붙은 공백 문자를 모두 제거하고 비교를 하게 된다.
MySQL의 문자열 타입이 가지게 되는 Collation이라는 속성이 있는데, Collation이라는 것은 비교 및 정렬의 기준을 제공하는 속성이다.
사실은 이러한 공백을 제거한 비교 작업은 이 Collation이라는 속성의 특징으로 발생하는 결과이며, MySQL의 문자열의 비교 및 정렬은 모두 마지막 공백을 제거(Right Space Trim)한 후, 비교 및 정렬 작업을 수행하게 된다.
그래서 최종적으로 아래와 같은 결과를 발생시키게 되는 것이다.


CREATE TABLE test (
  fd1 CHAR(10) DEFAULT NULL,
  fd2 VARCHAR(10) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;

insert into test values ('ABC ','ABC  ');
insert into test values ('ABC ','ABC');
insert into test values ('ABC ','ABC      ');
insert into test values ('ABC ','ABC    ');


SELECT
  CONCAT('''',fd1,'''') as converted_fd1,
  CONCAT('''',fd2,'''') as converted_fd2,
  (fd1=fd2) as is_equal
FROM test
ORDER BY fd2 DESC;
+---------------+---------------+----------+
| converted_fd1 | converted_fd2 | is_equal |
+---------------+---------------+----------+
| 'ABC'         | 'ABC  '       |        1 |
| 'ABC'         | 'ABC'         |        1 |
| 'ABC'         | 'ABC      '   |        1 |
| 'ABC'         | 'ABC    '     |        1 |
+---------------+---------------+----------+

이 결과로 test 테이블 모든 레코드의 fd1 값과 fd2값은 모두 동일한 값이라는 것을 확인할 수 있으며,
뒤에 붙은 공백 문자는 정렬에 아무런 영향을 미치지 않는다는 것도 확인할 수 있다.