2025-08-15 17:07
Tags:
NULL 완벽 정복 핸드북 ‘존재하지 않음’의 미학
데이터베이스를 다루다 보면 어김없이 마주치는 존재, 바로 NULL입니다. 어떤 이에게는 혼란의 씨앗이고, 어떤 이에게는 데이터의 유연성을 더하는 필수 요소. NULL은 단순히 ‘비어있음’을 의미하는 것을 넘어, 데이터베이스 철학의 한 축을 담당하는 중요한 개념입니다. 이 핸드북은 NULL의 탄생 비화부터 그 본질, 올바른 사용법, 그리고 개발자들이 흔히 저지르는 실수까지, NULL에 대한 모든 것을 깊이 있게 탐구합니다.
1. NULL의 탄생: 왜 ‘없음’을 표현해야 했을까?
모든 것의 시작에는 ‘필요’가 있습니다. NULL 역시 마찬가지. 1965년, 영국의 컴퓨터 과학자 토니 호어(Tony Hoare)는 ALGOL W라는 프로그래밍 언어를 설계하며 ‘참조의 부재’를 표현할 방법이 필요했습니다. 예를 들어, 어떤 사람의 ‘배우자’ 정보를 저장한다고 가정해 봅시다. 미혼인 사람의 ‘배우자’ 필드에는 무엇을 넣어야 할까요? 빈 문자열(”)? 숫자 0? 이런 값들은 ‘데이터가 있지만 비어있거나 0’이라는 의미를 가질 수 있어 ‘애초에 데이터가 존재하지 않음’과는 다릅니다.
이러한 모호함을 해결하기 위해 탄생한 것이 바로 ‘null reference’, 즉 NULL입니다. 이는 ‘값이 없음’, ‘알 수 없음’, ‘적용할 수 없음’ 등 다양한 ‘부재’의 상태를 나타내는 특별한 마커(marker)입니다.
비유: 서류함의 빈칸
NULL을 거대한 서류함에 비유할 수 있습니다. 어떤 서류의 특정 항목에 ‘해당 없음’이라고 적힌 포스트잇이 붙어있는 상태가 바로 NULL입니다. 이 칸은 비어있는 것도 아니고, ‘모름’이라고 적힌 것도 아닙니다. 그저 ‘현재로서는 채울 값이 존재하지 않거나, 알 수 없는 상태’임을 명시적으로 나타내는 특별한 표시입니다.
흥미롭게도, NULL을 창시한 토니 호어는 훗날 이를 “10억 달러짜리 실수(The Billion-Dollar Mistake)“라고 회고했습니다. NULL 참조로 인해 발생하는 수많은 오류, 예외 처리, 그리고 디버깅에 소요되는 시간과 비용이 그만큼 막대했기 때문입니다. 하지만 그의 의도와는 별개로, 관계형 데이터베이스 시스템(RDBMS)은 이 개념을 적극적으로 받아들였고, 오늘날 NULL은 데이터 모델링의 필수불가결한 요소가 되었습니다.
2. NULL의 본질: ‘값’이 아닌 ‘상태’
NULL을 이해하는 가장 중요한 첫걸음은 NULL이 특정 값이 아니라는 사실을 인지하는 것입니다. NULL은 숫자 0이나 공백 문자(’ ‘)와 근본적으로 다릅니다.
-
0: 수량적으로 ‘없음’을 의미하는 명확한 숫자 값.
-
공백 문자(’ ’): 길이가 1인 문자열 값.
-
빈 문자열(”): 길이가 0인 문자열 값.
-
NULL: 값이 존재하지 않거나 알 수 없는 상태(state) 또는 마커(marker).
이러한 본질적 차이 때문에 NULL은 일반적인 데이터와 다른 방식으로 동작하며, 이는 3치 논리(Three-Valued Logic)라는 독특한 논리 체계로 이어집니다.
3치 논리: TRUE, FALSE, 그리고 UNKNOWN
컴퓨터 과학의 기본 논리는 참(TRUE)과 거짓(FALSE)만 존재하는 2치 논리입니다. 하지만 NULL이 개입하는 순간, 제3의 상태인 **‘알 수 없음(UNKNOWN)‘**이 등장합니다.
예를 들어, A = 10
이라는 명제는 참(TRUE)입니다. A = 20
이라는 명제는 거짓(FALSE)입니다. 그렇다면 A = NULL
이라는 명제는 어떨까요?
정답은 ‘알 수 없음(UNKNOWN)‘입니다. A가 ‘알 수 없는 상태’인데, 어떻게 ‘알 수 없는 상태’와 같다고 확신할 수 있을까요? 마찬가지로 NULL = NULL
역시 결과는 TRUE가 아닌 UNKNOWN입니다. ‘알 수 없는 무언가’와 ‘또 다른 알 수 없는 무언가’가 같다고 누구도 장담할 수 없기 때문입니다.
이 3치 논리는 데이터베이스 쿼리의 WHERE 절에서 예상치 못한 결과를 낳는 주된 원인이 됩니다. WHERE 절은 조건의 결과가 TRUE인 행만 반환하기 때문입니다.
-- age가 NULL인 고객을 찾으려는 잘못된 시도
SELECT * FROM customers WHERE age = NULL;
-- 이 쿼리는 단 한 개의 행도 반환하지 않습니다.
-- age = NULL의 결과가 UNKNOWN이므로, WHERE 절의 TRUE 조건을 만족시키지 못하기 때문입니다.
이것이 바로 column = NULL
대신 column IS NULL
이라는 특별한 구문을 사용해야 하는 이유입니다.
3. NULL 사용법: 올바르게 다루는 기술
NULL의 독특한 성질을 이해했다면, 이제 이를 쿼리와 데이터 설계에서 올바르게 다루는 방법을 알아볼 차례입니다.
3.1. NULL 비교: IS NULL
과 IS NOT NULL
앞서 설명했듯이, NULL과의 모든 비교 연산
(`=`, `!=`, `<>`, `>`, `<`) 결과는 UNKNOWN입니다. 따라서 NULL 상태 자체를 확인하기 위해서는 반드시 `IS NULL`과 `IS NOT NULL` 연산자를 사용해야 합니다.
- `WHERE column IS NULL`: 해당 컬럼이 NULL 상태인 행을 찾습니다.
- `WHERE column IS NOT NULL`: 해당 컬럼에 어떤 값이든 존재하는(NULL이 아닌) 행을 찾습니다.
— 올바른 방법: age가 NULL인 고객 찾기 SELECT * FROM customers WHERE age IS NULL;
— age 정보가 입력된 고객만 찾기 SELECT * FROM customers WHERE age IS NOT NULL;
3.2. 연산에서의 NULL: 전파(Propagation)
NULL은 산술 연산, 문자열 결합 등 대부분의 연산에서 ‘전파’되는 특성이 있습니다. 즉, NULL과 연산한 결과는 항상 NULL입니다.
-
100 + NULL
→NULL
-
'Hello' || NULL
→NULL
(표준 SQL 기준, 일부 DBMS는 다를 수 있음)
이는 계산 과정에서 하나의 값이라도 빠져있으면 전체 결과를 신뢰할 수 없다는 논리적 귀결입니다. 급여(salary)와 보너스(bonus)를 더해 총 급여를 계산하는데, 보너스 값이 NULL(미정)이라면 총 급여 역시 NULL(계산 불가)이 되는 것이 타당합니다.
3.3. 집계 함수와 NULL: 무시의 미학
집계 함수(COUNT
, SUM
, AVG
, MAX
, MIN
)는 NULL을 다룰 때 조금 다른 규칙을 따릅니다. 대부분의 집계 함수는 연산 대상에서 NULL을 자동으로 제외합니다.
-
COUNT(*)
: 행 전체의 개수를 세므로 NULL 여부와 관계없이 모든 행을 카운트. -
COUNT(column)
: 해당 컬럼에서 NULL이 아닌 값의 개수만 카운트. -
SUM(column)
: NULL을 무시하고 나머지 값들의 합계를 계산. -
AVG(column)
: NULL을 무시하고 나머지 값들의 평균을 계산. (주의:SUM(column) / COUNT(column)
과 동일) -
MAX(column)
,MIN(column)
: NULL을 무시하고 최대/최소값을 찾음.
예시: scores
테이블에 (100), (80), (NULL), (90)
값이 있다면,
-
COUNT(*)
→ 4 -
COUNT(scores)
→ 3 -
SUM(scores)
→ 270 (100 + 80 + 90) -
AVG(scores)
→ 90 (270 / 3)
이러한 동작 방식은 매우 유용하지만, AVG
함수를 사용할 때 분모가 전체 행 수가 아닌 NULL을 제외한 행 수가 된다는 점을 반드시 인지해야 합니다.
3.4. NULL 값 대체: COALESCE
와 친구들
분석이나 애플리케이션 로직에서 NULL을 특정 기본값(예: 0, ‘N/A’)으로 바꿔야 할 때가 많습니다. 이때 유용하게 사용되는 함수가 COALESCE
입니다.
COALESCE(expression1, expression2, ...)
는 인자로 주어진 표현식들 중에서 NULL이 아닌 첫 번째 값을 반환합니다.
-- bonus가 NULL이면 0으로 대체하여 total_salary 계산
SELECT salary, bonus, salary + COALESCE(bonus, 0) AS total_salary
FROM employees;
COALESCE
는 표준 SQL 함수이며, 대부분의 DBMS에서 지원합니다. 이와 유사한 역할을 하는 DBMS별 함수들도 있습니다.
- Oracle:
NVL(expression1, expression2)
- MySQL:
IFNULL(expression1, expression2)
- SQL Server:
ISNULL(expression1, expression2)
COALESCE
는 두 개 이상의 인자를 받을 수 있어 더 유연하므로, 가급적 COALESCE
를 사용하는 것이 이식성 측면에서 유리합니다.
4. 심화 탐구: NULL을 둘러싼 논쟁과 이슈
4.1. NULL
vs 빈 문자열(''
)
데이터베이스 세계의 오랜 논쟁거리 중 하나는 NULL
과 빈 문자열(''
)의 관계입니다.
- 표준 SQL 및 대부분의 DBMS (MySQL, PostgreSQL, SQL Server 등):
NULL
과''
을 서로 다른 것으로 취급.NULL
은 ‘값의 부재’를,''
은 ‘길이가 0인 문자열 값’을 의미. - Oracle:
NULL
과''
을 동일한 것으로 취급. VARCHAR2 타입의 컬럼에''
을 삽입하면NULL
로 저장.
이 차이점은 데이터베이스 마이그레이션 시 심각한 문제를 일으킬 수 있으므로 반드시 숙지해야 합니다.
4.2. NULL
과 인덱스(Index)
NULL
값을 인덱스에 포함할지 여부도 DBMS마다 정책이 다릅니다.
- PostgreSQL, SQL Server:
NULL
값을 인덱스에 포함. B-Tree 인덱스에 다른 값들과 함께 저장. - MySQL:
NULL
값을 인덱스에 포함. - Oracle: 기본적으로 단일 컬럼 B-Tree 인덱스에는
NULL
값을 포함하지 않음. (여러 컬럼으로 구성된 복합 인덱스에서는 다른 컬럼 중 하나라도NOT NULL
이면NULL
값도 인덱스에 포함됨)
이러한 차이는 WHERE column IS NULL
조건의 쿼리 성능에 영향을 줄 수 있습니다.
4.3. 데이터 설계: NOT NULL
제약조건의 중요성
NULL을 허용하는 것은 데이터의 유연성을 주지만, 동시에 예측 불가능성과 복잡성을 증가시킵니다. 애플리케이션 코드에서 항상 NULL 체크를 해야 하고, 의도치 않은 연산 결과를 낳을 수 있습니다.
따라서 데이터베이스 스키마를 설계할 때, 값이 반드시 존재해야 하는 컬럼에는 NOT NULL
제약조건을 거는 것이 기본 원칙입니다.
- Primary Key: 당연히
NOT NULL
이어야 함. - 사용자 ID, 필수 입력 항목 등:
NOT NULL
로 설정하여 데이터의 무결성을 보장. - 선택적 입력 항목 (ex: 별명, 추천인 코드 등):
NULL
을 허용할 수 있음.
NOT NULL
제약조건과 함께 DEFAULT
값을 지정하면, 데이터 삽입 시 해당 컬럼 값이 누락되더라도 DEFAULT
로 지정된 기본값이 자동으로 입력되어 NULL
을 방지할 수 있습니다.
5. 결론: NULL, 친구인가 적인가?
NULL은 양날의 검과 같습니다. ‘알 수 없음’과 ‘존재하지 않음’을 표현하는 강력하고 필수적인 도구이지만, 그 특성을 제대로 이해하고 다루지 않으면 언제든 우리의 발등을 찍을 수 있습니다.
토니 호어의 “10억 달러짜리 실수”라는 말은 NULL 자체가 문제라기보다는, 그것을 다루는 프로그래머와 설계자의 세심한 주의가 없다면 큰 대가를 치를 수 있다는 경고에 가깝습니다.
NULL을 현명하게 다루기 위한 핵심 요약:
- NULL은 값이 아닌 상태임을 기억하라.
- 비교할 때는 항상
IS NULL
,IS NOT NULL
을 사용하라. - 연산에 NULL이 포함되면 결과도 NULL임을 인지하라.
- 집계 함수는 NULL을 알아서 제외한다는 사실을 활용하라.
- NULL을 특정 값으로 대체하고 싶을 땐
COALESCE
를 사용하라. - 설계 단계에서 가능한 한
NOT NULL
제약조건을 적극적으로 사용하라.
NULL을 피할 수는 없습니다. 그렇다면 우리는 NULL을 깊이 이해하고, 존중하며, 올바르게 통제하는 방법을 배워야 합니다. 이 핸드북이 여러분의 여정에 든든한 길잡이가 되기를 바랍니다.