데이터베이스 이상 현상(Anomaly)

이혁진·2023년 1월 9일

이상 현상

이상 현상이란 릴레이션 내의 특정 종속 관계로 인해 삽입, 수정, 삭제를 할 경우에 발생하는 문제를 말한다. 특정 종속 관계란 함수 종속을 말하며, 그 중에서도 부분 함수 종속이나 이행적 함수 종속 등을 포함한다. 또한 그러한 종속 관계가 존재하는 것을 잘못 설계된 릴레이션이라 한다. 이러한 종속 관계가 존재하는 테이블에 위의 세 작업을 수행할 경우에 null값이 늘어나거나, 데이터 중복이나 데이터 불일치, 의도되지 않은 데이터의 훼손 등의 문제가 발생한다. 잘못된 종속 관계가 있는 릴레이션에 각 세 작업을 수행한 경우 문제를 정리하면 다음과 같다.

종속 관계 존재 -> 삽입 -> 불필요한 데이터 저장(특히 데이터 중복, 데이터 불일치, null값)
종속 관계 존재 -> 삽입 -> 불필요한 데이터 저장 -> 삭제 -> 원치 않는 데이터의 삭제
종속 관계 존재 -> 삽입 -> 불필요한 데이터 저장 -> 데이터 중복 -> 수정 -> 데이터 불일치

불필요한 데이터의 저장이란 특정 함수 종속 관계로 인해 여러 관심사가 한 릴레이션에 존재하여 한 관심사의 저장을 위해 다른 관심사의 데이터도 불필요하게 저장해야 한다는 것을 말한다.

데이터 중복

데이터 중복이란 하나의 데이터가 여러 행에 걸쳐서 존재하는 것을 말한다. 이것으로 인해서 특성 데이터를 수정하거나 삭제 혹은 새로운 데이터를 삽입할 때 해당 작업 외에 다른 처리가 필수적이게 된다. 그러한 추가적 처리가 없다면, 데이터 간의 모순이 발생하게 되기 때문이다. 무슨 말이냐면

EMPLOYEE_DEPARTMENT(empl_id, …, dept_id, dept_name, dept_leader_id)
= {(1, 세환, …, 1001, 개발, 1), (2, 수정, …, 1001, 개발, 1), (3, 인영, …, 1001, 개발, 1)}

빨간 부분을 보면, dept_id, dept_name, dept_leader_id 부분이 중복된다. 원래는 하나의 데이터임에도 여러 줄에 걸쳐서 기록이 되어있다. 이 경우에 개발팀 리더가 인영이 되었다고 해보자.

EMPLOYEE_DEPARTMENT(empl_id, …, dept_id, dept_name, dept_leader_id)
= {(1, 세환, …, 1001, 개발, 1), (2, 수정, …, 1001, 개발, 1), (3, 인영, …, 1001, 개발, 3)}

하나만 수정하게 된다면, 개발팀이라는 정보가 여러 값을 가지게 된다. 이를 갱신 이상이라고도 한다.(데이터 중복으로 인한 데이터 불일치) 이를 해결하기 위해서는 모든 개발팀 행에서 leader_id 를 전부 3으로 바꿔줘야 한다. 데이터 중복으로 인해 이러한 추가적 작업 뿐만 아니라 하나의 데이터를 여러 곳에 저장하기 위한 저장 공간이 낭비된다.

삽입 이상

삽입 이상이란 튜플을 삽입할 경우 불필요한 데이터까지 삽입하여 발생하는 문제를 말한다. 잘못된 설계로 인해서, 같은 말로는 잘못된 함수 종속 관계가 릴레이션에 남아있는 경우에, 불필요한 속성을 삽입해야만 필수적인 데이터를 삽입할 수 있는 경우가 있다.

EMPLOYEE_DEPARTMENT(empl_id, empl_name, birth, dept_id, dept_name, dept_leader_id)
= {(1, 세환, …, 1001, 개발, 1), (2, 인영, …, 1001, 개발, 1), (3, null, null, 1002, 마케팅, null)}

이 테이블에는 임직원 정보 뿐만 아니라 부서 정보도 같이 저장하고 있다. 그래서 임직원이 아니라 새로운 부서가 만들어져서 이것을 저장하고 싶을 때에는 통상적 방법으로는 삽입이 불가하다. 왜냐하면 기본키가 empl_id이고 부서는 직원 아이디를 가지지 않으므로 논리적으로 empl_id가 null이 되어야 맞기 때문이다. 편법을 써서 기본키를 쓰고 직원 정보 부분의 속성을 null로 채워 부서 정보만 저장할 수 있다. 이때 null로 이루어진 부분과 기본키가 불필요한 데이터이다. 논리적으로 부서 정보를 저장하는데 직원 정보가 필요한 것은 말이 되지 않기 때문이다.

하지만, 그렇게 한다면 다음의 문제가 발생한다.

EMPLOYEE_DEPARTMENT(empl_id, empl_name, birth, dept_id, dept_name, dept_leader_id)
= {…, …, (3, null, null, 1002, 마케팅, null), (4, 다혜, …, 1002, 마케팅, 4)}

아무도 없는 마케팅 부서를 넣었는데, 다혜 직원이 해당 부서에 새로 들어온 경우 데이터 불일치가 발생하게 된다. 무슨 말이냐면 3번 직원의 행에서 마케팅 부서의 리더는 미정된 상태이고, 4번 직원에서 마케팅 부서의 리더는 다혜 직원으로 논리적 모순이 발생한다는 것이다. 이러한 현상을 해결하기 위해서는 기존의 3번 직원의 행을 삭제하는 추가 작업이 필요하게 된다. 이것도 아래 서술할 불필요한 데이터의 저장 때문이다.

또한 직원 정보를 삽입하는 경우 불필요한 데이터를 저장하면서 데이터 중복이 발생할 수 있다. 여기서 불필요한 데이터의 저장이란 잘못된 종속 관계로 인해 여러 관심사가 한 릴레이션에 있는 경우(부서와 직원) 데이터를 반드시 같은 행에 함께 저장해야 한다는 것이다.

EMPLOYEE_DEPARTMENT(empl_id, empl_name, birth, dept_id, dept_name, dept_leader_id)
= {(1, 세환, …, 1001, 개발, 1)}

이 상태에서

EMPLOYEE_DEPARTMENT(empl_id, empl_name, birth, dept_id, dept_name, dept_leader_id)
= {(1, 세환, …, 1001, 개발, 1), (2, 인영, …, 1001, 개발, 1)}

이렇게 되면 부서 부분의 속성에서 데이터 중복이 발생하고, 후에 갱신 이상이 발생할 수 있다.

불필요한 데이터는 null값이 될 수도 있는데, 새로 들어와서 아직 부서가 정해지지 않은 직원이 있다고 해보자. 그 경우 부서를 전부 null값으로 저장해야 할 것이다.

EMPLOYEE_DEPARTMENT(empl_id, empl_name, birth, dept_id, dept_name, dept_leader_id)
= {(1, 세환, …, 1001, 개발, 1), (1, 세환, …, null, null, null)}

null 값에 의해서 저장 공간의 낭비 뿐만 아니라 후에 조인을 할 경우 의도치 않은 결과가 발생할 수 있고, count같은 집계 함수에서 역시 의도치 않은 결과가 나타날 수 있다.

위의 문제를 해결하기 위해서 테이블을 분리할 수 있다. 이를 정규화 과정이라 한다.

EMPLOYEE_DEPARTMENT(empl_id, empl_name, birth, dept_id, dept_name, dept_leader_id)
= {(1, 세환, …, 1001, 개발, 1), (2, 인영, …, 1001, 개발, 1), (3, null, null, 1002, 마케팅, null)}

위의 테이블을 다음의 두 테이블로 쪼갠다면

EMPLOYEE(empl_id, empl_name, birth, dept_id)
= {(1, 세환, …, 1001), (2, 인영, …, 1001)}
DEPARTMENT(dept_id, dept_name, dept_leader_id)
= {(1001, 개발, 1), (1002, 마케팅, null)}

만약 마케팅 부서에 다혜 직원이 들어온 경우에는 부서 정보를 갱신하고, 직원에 데이터를 추가한다.

EMPLOYEE(empl_id, empl_name, birth, dept_id)
= {(1, 세환, …, 1001), (2, 인영, …, 1001), (3, 다혜, …, 1002)}
DEPARTMENT(dept_id, dept_name, dept_leader_id)
= {(1001, 개발, 1), (1002, 마케팅, 3)}

위와 다르게 데이터 불일치가 발생하지 않는다. 다른 삽입 이상 유형도 생각해보자.

EMPLOYEE(empl_id, empl_name, birth, dept_id)
= {(1, 세환, …, 1001), (2, 인영, …, 1001), (3, 다혜, …, 1002), (4, 태건, …, null)}

신입 직원이라면 부서 id를 null로 하면 된다. 그러면 이전과 다르게 null값이 줄어들게 되었다. 또한 새로운 직원이 기존 부서에 속하게 되는 경우 역시 중복 데이터가 감소한다.

EMPLOYEE(empl_id, empl_name, birth, dept_id)
= {(1, 세환, …, 1001), (2, 인영, …, 1001), (3, 다혜, …, 1002),
(4, 태건, …, null), (5, 민지, …, 1001)}

정리하면 삽입 이상이란 특정 함수 종속 관계로 인하여 다른 관심사의 데이터가 같이 저장되어야 하고, 이것으로 데이터 중복, 데이터 불일치, null값, 혹은 삭제 이상이나 갱신 이상의 원인을 제공할 수 있다.

삭제 이상

삭제 이상이란 튜플을 삭제 시에 꼭 필요한 데이터까지 같이 삭제되는 문제를 말한다. 삽입 과정에서 다른 관심사의 데이터가 같은 행에 저장되게 되는데, 이 경우에 하나의 관심사만 삭제하길 의도하더라도 행이 전부 없어지므로, 다른 관심사 역시 사라지게 된다. 예시를 들자면

EMPLOYEE_DEPARTMENT(empl_id, empl_name, birth, dept_id, dept_name, dept_leader_id)
= {(1, 세환, …, 1001, 개발, 1), (2, 인영, …, 1001, 개발, 1), (3, 수정, …, 1002, 마케팅, 3)}

이렇게 되어있을 때, 수정 직원이 퇴사하여 해당 행을 삭제해야 한다고 해보자. 그러면

EMPLOYEE_DEPARTMENT(empl_id, empl_name, birth, dept_id, dept_name, dept_leader_id)
= {(1, 세환, …, 1001, 개발, 1), (2, 인영, …, 1001, 개발, 1)}

이렇게 되는데, 주목해야 할 점은 마케팅팀 최후의 직원이었던 수정 직원이 삭제되면서 부서 정보 자체가 사라지는 것이다. 이를 해결하기 위해서 삭제 대신에 직원 부분을 null로 매꾸어보자

EMPLOYEE_DEPARTMENT(empl_id, empl_name, birth, dept_id, dept_name, dept_leader_id)
= {(1, 세환, …, 1001, 개발, 1), (2, 인영, …, 1001, 개발, 1), (3, null, null, 1002, 마케팅, 3)}

부서 정보는 남아있게 되었지만, null값으로 인한 다른 문제가 발생하게 될 것이다. 해결책은 테이블을 분리하는 것이다.

EMPLOYEE_DEPARTMENT(empl_id, empl_name, birth, dept_id, dept_name, dept_leader_id)
= {(1, 세환, …, 1001, 개발, 1), (2, 인영, …, 1001, 개발, 1), (3, 수정, …, 1002, 마케팅, 3)}

이 테이블을 다음과 같이 분리한다.

EMPLOYEE(empl_id, empl_name, birth, dept_id)
= {(1, 세환, …, 1001), (2, 인영, …, 1001), (3, 수정, …, 1002)}
DEPARTMENT(dept_id, dept_name, dept_leader_id)
= {(1001, 개발, 1), (1002, 마케팅, 3)}

삭제 시에는 직원 테이블에서 수정을 삭제하고 부서에서 리더를 null로 한다.

EMPLOYEE(empl_id, empl_name, birth, dept_id)
= {(1, 세환, …, 1001), (2, 인영, …, 1001)}
DEPARTMENT(dept_id, dept_name, dept_leader_id)
= {(1001, 개발, 1), (1002, 마케팅, null)}

아직 부서 정보가 보존된 것을 확인할 수 있다. 또한 테이블 정규화 전과 달리 null값도 거의 없는 것을 알 수 있다.

정리하면 삭제 이상이란 함수 종속 관계로 인해 다른 관심사가 테이블에 같이 저장되고, 삭제 시에 의도하지 않는 관심사의 데이터도 같이 삭제되는 것을 말한다.

갱신 이상

갱신 이상이란 중복되는 튜플 중 일부만 변경하였을 때 데이터 불일치가 발생하게 되는 문제이다. 중복 데이터 중 하나만 바뀌게 된다면 하나의 데이터가 여러 값을 가지게 되고, 이는 논리적으로 모순이 된다. 예시를 들자면

EMPLOYEE_DEPARTMENT(empl_id, empl_name, birth, dept_id, dept_name, dept_leader_id)
= {(1, 세환, …, 1001, 개발, 1), (2, 인영, …, 1001, 개발, 1), (3, 수정, …, 1002, 마케팅, 3)}

개발 팀이 개발 1팀으로 최근에 바뀌었는데, 시간차나 실수 같은 여타 이유로 인해서 업데이트가 인영에 대해서만 적용된 경우

EMPLOYEE_DEPARTMENT(empl_id, empl_name, birth, dept_id, dept_name, dept_leader_id)
= {(1, 세환, …, 1001, 개발, 1), (2, 인영, …, 1001, 개발 1, 1), (3, 수정, …, 1002, 마케팅, 3)}

세환 행의 개발 팀과 인영 행의 개발 팀은 같은 데이터를 의미하지만 다르게 기록되는 데이터 불일치가 발생하게 된다. 만약 테이블을 쪼개었더라면

EMPLOYEE(empl_id, empl_name, birth, dept_id)
= {(1, 세환, …, 1001), (2, 인영, …, 1001), (3, 수정, …, 1002)}
DEPARTMENT(dept_id, dept_name, dept_leader_id)
= {(1001, 개발, 1), (1002, 마케팅, 3)}

단순히 부서 테이블에서 개발 팀의 이름을 개발 1로 바꾸면 될 것이다.

DEPARTMENT(dept_id, dept_name, dept_leader_id)
= {(1001, 개발 1, 1), (1002, 마케팅, 3)}

어떤 이유로든간에 갱신이 동시에 이루어지고, 데이터 불일치가 없어진다.

정리하면 갱신 이상이란 함수 종속 관계로 인해서 데이터 중복이 발생하고, 중복으로 인해 일부만 갱신되어 데이터 불일치가 발생하게 된다는 것이다.

위의 이상 현상 사례에서 테이블을 쪼개는 작업을 통해서 null값, 중복 데이터, 불필요한 데이터가 같이 저장되는 문제가 해결되는 것을 알 수 있다.

그리고 이상 현상은 직원과 부서 사례를 외우는 것도 좋다고 했던 것 같다.

삽입 이상 : EMP_DEPT에 튜플 삽입 시 부서가 미정된 직원 혹은 직원이 없는 부서 삽입 시에 발생
삭제 이상 : 부서의 마지막 직원을 삭제 시 부서 정보도 없어짐
수정 이상 : 부서 정보를 변경하면 부서의 모든 직원 튜플에서 동일하게 변경해야 함

출처

[1] https://youtu.be/JwfQ8ouhAzA
[2] https://youtu.be/fw8hvolebLw
[3] https://youtu.be/EdkjkifH-m8
[4] https://youtu.be/5QhkZkrqFL4
[5] https://taehoon9393.tistory.com/42
[6] https://cs.kangwon.ac.kr/~ysmoon/courses/2009_2/db/11.pdf

profile
한양대학교 정보시스템학과 22학번 이혁진입니다. 컴퓨터 아키텍처와 시스템 등 다양한 주제로 공부한 내용을 기록합니다.

0개의 댓글