Intro
나는 DBMS 아키텍처나 SQL을 공부할 때, 모두 'Oracle’을 기준으로 배웠다. 그런데 막상 프로젝트에 투입되니 샤용해야 할 DBMS는 사이트마다 천차만별어서 기존에 알던 것들이 무용지물이 되었다. (지금 프로젝트에서는 Sybase ASE, HANA, IQ와 PostgreSQL을 주로 사용하고 있다.) DBMS에 따라서 완전히 다르게 동작하는 경우가 많아서 마치 새로운 세계관 속에 들어와있는 것처럼 느껴진다.
처음에는 DBMS별로 함수의 차이부터 비교를 해보다가 세부적인 차이가 너무 많아서 1:1 비교도 불가능할 정도라는 걸 느끼고 이 무용한 작업을 때려치웠다. 대신, 새로운 DBMS를 만났을 때 어떤 점을 먼저 확인해보면 좋을지 일종의 체크리스트를 만들어 보기로 했다. 그 시작으로, NULL의 정의가 DBMS마다 어떻게 다를 수 있는지, 그리고 그로 인해 어떤 동작들이 달라질 수 있는지 알아보려 한다.
NULL
의 정의
NULL은 값도 아닌 값
Null의 영문 정의는 having no legal power로, 어떠한 효력이나 가치가 없음을 의미한다. DBMS에서 NULL
은 미지의 값, unknown value라는 의미로 쓰이지만 그렇다고 미지수라고 이해해서는 안 된다. 값으로 인정되지 않는 값이라고 생각하는 것이 NULL
의 특징을 대체로 더 잘 이해할 수 있다.
길이가 0인 VARCHAR 문자열은 NULL일까?
DBMS마다 다르다. 두 가지 경우 모두 가능하다.
- 길이 0인 VARCHAR 문자열을 NULL로 인식 : 오라클, 액세스
- 길이 0인 VARCHAR 문자열과 NULL을 다르게 인식 : SQL Server
NULL
의 특징
1. 연산 불가
NULL의 주요한 특징은 일반 연산이 불가능하다는 점이다. 값으로 인정되지 않는 값이기에 연산 시 패싱 대상이다.
- 동등 연산자, 비동등 연산자 사용이 불가능하다.
- 널 끼리의 연산이 불가능하다.
따라서, 널 값의 존재 여부를 파악하거나 연산을 위한 전처리를 위해 별도의 전용 연산을 사용해야 한다. 대표적인 예는 다음과 같다.
IS NULL
NVL()
NULLIF()
COALESCE()
2. NULL
끼리는 서로 같은 값일까?
NULL이 뭔지 알 수 없는 값도 아닌 값이라면, 여러 NULL은 모두 같은 값으로 봐야 할까, 각기 다른 값으로 봐야 할까? 알쏭달쏭해 보이지만 답은 명확하다. 정답은 "내가 사용하는 DBMS에 따라 다르다"이다.
DBMS에 따라서 널 값들을 동일하게 처리하는지, 또는 동일하지 않다고 처리하는지가 상이하다.
- 모든 널 값을 동일하게 여기고 처리 : SQL Server, DB2
- 개별 널 값이 다르다고 여기고 처리 : MySQL, PostgreSQL
3. 소결
DBMS가 어떤 결정을 했는가에 따라서 달라지는 세부사항들이 있을 수 있기에 내가 사용 중인 DBMS가 NULL을 어떻게 처리하는 지 파악하는 것이 중요하다.
사용상의 주의점
DBMS별로 널 처리에 따라 어떤 점이 달라질 수 있는지 정리해보았다.
정렬 순서
SQL 표준에 따르면 임의 널 값 두 개의 정렬 순서는 같아야 한다.
그 이외의 것들은 DBMS에 따라 달라질 수 있다. 특히 주의해야 할 점은 널과 널이 아닌 값 간의 정렬 순서가 DBMS마다 다르다는 점이다.
NULL FIRST
: 액세스, SQL ServerNULL LAST
: 오라클, PostgreSQL
문자열 연산
문자열을 합치기 위해서 사용되는 함수는 대표적으로 CONCAT()
과 ||
가 있다. DBMS에 따라서 널 값의 정의가 미묘하게 달라서, NULL을 인수로 넣는 경우 이 함수들의 결과도 달라질 수 있다.
오라클은 empty string을 널과 동일하게 취급하기 때문에 CONCAT()
과 ||
연산에 NULL을 포함시켜도 NULL을 반환하지 않는다.
CONCAT(NULL, 'abc')
=NULL||'abc'
= `‘abc’
그러나 PostgreSQL은 CONCAT()
과 ||
연산의 작동이 다르다. CONCAT() 함수는 NULL을 연산에 참여시키지 않는다.
CONCAT(NULL, 'abc')
='abc'
NULL||'abc'
=NULL
MySQL은 CONCAT()
함수만 지원하는데, NULL을 포함하면 NULL을 반환한다.
CONCAT(NULL, 'abc')
=NULL
기본키(PK)
기본키는 널 값을 가질 수 없다. (ISO SQL 표준)
Primary Key가 NULL값을 가질 수 없다는 건 너무 당연한 이야기니까… 곁들여서 기본 키 선정 시 체크리스트 세 가지를 소개한다.
[참고] 기본키 설계 시 체크리스트
- 유일한 값이어야 한다
- 널 값이 아니어야 한다
- 안정적인 값(수정 변경이 없는 값)이어야 한다
인덱스 (Nonunique)
PK는 널 값을 가질 수 없다고 했는데, 그럼 인덱스는 널 값을 가질 수 있을까? 인덱스에 널 값을 저장하는 DBMS도 있다.
- 인덱스에 널 값이 저장되지 않음 : 오라클
- 오라클은 복합키에 NOT NULL 컬럼이 하나라도 있어야 인덱스를 만들 수 있다.
- 인덱스에 널 값이 저장됨 : MySQL
- DBMS에 따라 널을 제외한 인덱스 생성 옵션을 제공하는 경우가 있다 (필더링된 인덱스)
- e.g. DB2
EXCLUDE NULL KEYS
인덱스에 널 값을 저장할 수 있느냐 없느냐가 왜 중요할까? 조회 조건에 따라 성능에 영향을 줄 수 있기 때문에 중요하다. 특히, 인덱스에 널 값을 저장하지 않는 DBMS들은 IS NULL
조건을 줬을 때 인덱스를 탈 수 없으니 인덱스 설계나 쿼리 작성 시 유의해야 한다.
UNIQUE 인덱스
인덱스에 널 값을 저장할 수 있는 DBMS라면, 유일 인덱스에 널 값을 저장할 수 있을까?
- NULL 값을 딱 하나 저장할 수 있음: DB2, SQL Server
- 모든 널 값을 동일하게 여기는 DBMS는 UNIQUE INDEX에 널 값을 여러 개 포함할 수 없다.
- NULL 값을 여러 개 저장할 수 있음: MySQL, PostgreSQL
- 널 값을 서로 다르게 여기는 DBMS는 UNIQUE INDEX에 널 값을 여러 개 포함할 수 있다.
UNIQUE 제약 조건
UNIQUE 제약 조건을 가진 컬럼에는 NOT NULL 제약 조건이 있어야 할까? 이 부분도 DBMS마다 다르다.
- UNIQUE 제약 조건이면, NOT NULL : DB2
- UNIQUE 제약 조건이더라도 널 값을 가질 수 있으나 단 하나의 널 값만 허용 : SQL Server
- UNIQUE 제약 조건에 널 허용 기능 제공 : MySQL, 오라클, PostgreSQL
NULL.. 생각하고 싶지 않다면
이렇듯 NULL의 세계는 복잡하고 미묘해서 사용자를 골치 아프게 한다. 그래서 두 가지 방식으로 NULL의 입력을 방지하는 경우가 많다.
- NULL 값 입력을 허용하지 않는다.
- 기본 값으로 공백문자 또는 0을 입력한다.
하지만 위 방법 모두 근본적인 해결책은 아니다. 가장 좋은 방법은 정규화를 통해 NULL 값이 필요하지 않은 데이터 구조로 설계하는 것이다. 물론 성능상의 이유로 반정규화가 필요한 경우도 많다. 하지만 실제로 대부분의 로우가 NULL이거나 공백문자, 0이 들어가 있다면, 지나치게 반정규화되어 있는 컬럼은 아닌지 생각해보자.
References
다음 책과 문서들을 참고했다.
- 존 비아시에스 등, <SQL 코딩의 기술>
- Support ANSI SQL behaviour for null/empty Character string data types, 원문
- [MySQL, Oracle] IS NULL, IS NOT NULL 조건은 인덱스를 사용할까? 원문
이 글은 옵시디언을 통해 작성되었습니다.
'데이터베이스 Database' 카테고리의 다른 글
[Neo4j 기초] 그래프 DB 모델링 (0) | 2023.02.08 |
---|