NOT IN 서브쿼리가 조용히 0건을 돌려준 이유
문제 발생
차단하지 않은 사용자의 글만 뽑는 쿼리가 갑자기 빈 결과를 돌려줬습니다.
SELECT * FROM dev_posts
WHERE author_id NOT IN (SELECT user_id FROM blocked_users);오류도 없고 경고도 없었습니다. blocked_users에 user_id가 NULL인 행 하나가 들어간 날부터였습니다.
원인 분석
3값 논리 때문입니다. MySQL 문서가 규칙을 그대로 적습니다 — SQL 표준을 따르기 위해 IN()은 왼쪽 표현식이 NULL일 때뿐 아니라, 목록에서 매칭을 찾지 못했고 목록의 표현식 중 하나가 NULL일 때도 NULL을 반환합니다.
NOT IN은 문서 설명대로 NOT (expr IN (...))과 같으므로 그 결과도 그대로 이어집니다.
5 IN (1, 2, NULL) → NULL (FALSE가 아니다)
5 NOT IN (1, 2, NULL) → NOT NULL → NULLWHERE는 참인 행만 남기므로 NULL은 탈락합니다. 결국 차단 목록에 NULL이 한 개라도 있으면 모든 행이 사라집니다. 오류가 아니라 표준에 맞는 동작이라 조용합니다.
IN(긍정형)에서는 이 문제가 덜 눈에 띕니다. 매칭되는 행은 여전히 참이고, 매칭 안 되는 행만 FALSE 대신 NULL이 되어 어차피 탈락하기 때문입니다.
해결 방안
NOT EXISTS로 바꿉니다.NULL에 영향을 받지 않고, 대개 실행 계획도 좋습니다.
SELECT p.* FROM dev_posts p
WHERE NOT EXISTS (
SELECT 1 FROM blocked_users b WHERE b.user_id = p.author_id
);- 또는 서브쿼리에서
NULL을 걸러냅니다.
WHERE author_id NOT IN (SELECT user_id FROM blocked_users WHERE user_id IS NOT NULL)-
더 근본적으로는 스키마에서 막습니다. 그 컬럼이
NULL이면 안 되는 값이라면NOT NULL제약을 겁니다. 이 버그가 애초에 생기지 않습니다. -
LEFT JOIN ... IS NULL패턴도 안전합니다. 팀이 익숙한 형태를 고르되,NOT IN만은 서브쿼리 결과에NULL가능성이 있을 때 쓰지 않습니다. -
왼쪽 컬럼도 확인합니다.
author_id가 nullable이면(MEOKKO에서는 탈퇴 사용자 보존을 위해 실제로 그렇습니다) 그 행도 조건 결과가NULL이 되어 조용히 빠집니다. 의도한 동작인지 매번 확인합니다.
댓글0
댓글을 남기려면 로그인이 필요해요. 로그인
아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.