_대문 | 방명록 | 최근글 | 홈피소개 | 주인놈 |
FrontPage › 속성에대하여..
|
|
이제부터는 물리적인 구현에 대해서도 약간은 생각을 해주어야 한다. 어떤 것이 가장 유리한 것이지 판단하는 기준을 잘 세워야 한다.
[edit]
1 속성의 개념 #속성은 개체집합의 관리하고자 하는 항목들(2개 이상)을 의미한다. 즉, 사물의 본질을 이루는 성질을 말하는데, 그것이 없이는 그 사물을 생각할 수 없는 조건을 의미하기도 한다. 보통 이러한 것은 물리적으로 Not Null로 선언한다. Null 허용 컬럼들의 경우는 옵션에 해당한다. 속성은 가공되지 않은 소스이다. 즉, 데이터이다. 데이터를 가공하면 사용자가 원하는 정보가 될 수 있음을 말한다. 간혹 가공된 소스가 속성이 될 수도 있으나 이것은 본질을 이루는 것은 아니다.
![]() 중요한 것은 어떤 것을 속성의 집합으로 보아야 하는가에 대한 물음이다. 위 그림의 “년 + 월 + 일” 이 하나의 속성으로 “날짜” 라고 볼 것인가 아니면 “년”, “월”, “일” 이렇게 모두 떨어진 것이 속성인가? 어떤 것을 속성으로 보느냐는 것은 철저히 업무에 따라서 틀려진다. 이제부터 그것에 대한 답을 찾아보자.
[edit]
2 속성의 표현 #![]() 역시 옛날의 표현방식이다. 대학에서는 아직도 이러한 표기법을 사용하기도 한다. 실무에서는 좀처럼 사용하기 힘든 방식이다. 표현공간이 너무 넓을 뿐더러 모델의 복잡성이 증가하기 때문이다.
[edit]
3 관계와 속성 #주의해야 할 것이 있다. 그것은 관계의 논리적인 또는 물리적인 표현이 속성이 아니라는 것이다. 아래의 DDL 문장을 보자. Report_no 와 같은 경우는 Foreign Key 이다. 즉, 이것은 관계의 물리적인 표현이지 속성이 아니라는 것이다.
create table abbreviation( abbr_no char(6) , report_no integer not null unique , primary key (abbr_no) , foreign key (report_no) reference report ); 단지 표현의 문제가 있을 뿐이다. 키개체집합의 경우는 많은 관계를 가진다. 관계가 매우 많은 경우는 묵시적인 관계를 가지도록 할 수도 있다. 모델러의 역량이다. 해당 개체집합을 도출하고, 속성을 정의할 때 관계를 표현해 줌으로써 의미가 더 명확해 질 수 있으므로 속성으로 표현할 수도 있다. 하지만 관계라는 것을 확실히 표시해 두는 것이 좋다. 많은 경우 개체인지 속성인지 관계인지 혼돈하는 경우가 많이 있다. 이는 각각의 개념이 아직도 미흡하기 때문이다. 또한 정의가 제대로 이루어지지 않았기 때문이기도 하다.
[edit]
4 독립성 #속성은 독립성을 가져야 한다. 속성이 독립적이라는 것은 속성의 자체만으로도 어떤 의미가 있어야 한다는 말이다. 고객의 생일에 맞추어 “고객님의 00번째 생일을 축하합니다.” 라는 생일 축하 문자메시지를 고객의 핸드폰에 보낸다고 가정하자. 그렇다면 생년월일의 속성이 “년” , “월”, “일” 이 속성이 되는 것이 맞을까? “년월”, “일” 이 맞을까? 아니면 “년” , “월일” 이 맞을까?
![]() 관계 이론에서는 속성들이 식별자에 종속되어 같은 레벨(형제)로 어떤 주제로 묶여져 있으므로 ‘릴레이션’이라고 부른다.(용어주의)
[edit]
5 속성명칭 #속성의 명칭을 결정하는 것은 매우 중요하다. 개체집합과 마찬가지로 속성(집합)의 정의와 명칭을 정의하는 것은 매우 중요한 작업이다. 아래의 그림을 보자. 아래의 그림은 속성의 명칭을 나타낸 것이다.
![]() 학점의 경우를 살펴보자. 학점하면 생각나는 것이 무엇이냐를 여러 사람들에게 물어보면 4.5와 같은 숫자를 생각하는 사람도 있고, A, B, C와 같은 학점(등급)을 생각하는 사람도 있다. 중요한 것은 모델러가 이러한 것을 결정하는 것이다. 4,5와 같은 점수를 나타내는 것인지 A, B와 같은 등급의 점수를 나타내는 것인지를 사용자에 의해서 결정을 하도록 유도해야 한다. 상태의 경우도 살펴보자. 뜻하는 바를 정확히 알 수 없다. 무엇의 어떤 상태인지를 명확히 파악해야 한다. 특기1, 특기2의 경우는 각각이 속성이라기 보다는 특기가 다중값 속성이다.
[edit]
6 속성의 정의 방법 #속성을 정의하는 방법은 개체집합을 정의하는 방법과 유사하다. 우선 속성의 후보를 선정 후 해당 속성에 대해서 하나씩 집고 넘어가는 방식이다. 그러나 속성을 정의할 때는 개체집합과 같이 분류하지는 않는다. 대신에 개체집합의 단위로 속성을 그룹화한다. 그 다음 속성의 자격검증에 들어간다. 속성의 자격검증은 다음의 질문으로 한다.
[edit]
7 속성 후보의 선정 #속성의 후보 선정 역시 개체집합의 후보를 선정할 때의 소스와 같다. 중요한 것은 구시스템의 속성을 그대로 가져가지 말라는 것이다. 일단은 속성이라고 판단하지 말고 후보만 수집한다는 생각을 해야 한다. 업무문서의 경우는 가공된 값(계산된 값)을 많이 가진다. 그러므로 어떤 데이터 소스에서 도출된 값인지를 파악하는 것이 중요하다. 현업 사용자의 경우는 업무를 많이 알고 있으므로 관리해야 할 속성을 많이 알고 있다. 그러나 현업 사용자들도 확실하지는 않으므로 역시 후보수집에만 초점을 맞추어야 한다.
![]() 용어 사전 또는 레포지토리 이용하는 것도 좋은 방법이다. 만약 속성에 대한 지식이 부족하다면 전문서적 또는 관련 서적을 참고하는 것도 도움이 된다. 또한 타시스템 밴치마킹 하여 미래에 관리해야 할 속성이 있는지에 대한 데이터를 수집하는 것도 좋다.
[edit]
8 개체집합 단위로 그룹화 #속성을 개체집합 단위로 그룹화 할 때는 엔터티 집합의 도메인 내에 있는 관리 속성인지 파악하는 것이 주 업무이다. 현 단계는 어떤 속성이 어떤 엔터티 집합에 관리 속성인가만 파악하면 되는 단계이므로 현 단계를 충실히 수행하면 된다.
[edit]
9 원자값 #원자값이란 의미 있는 정보의 단위이다. “의미 있는 정보의 단위” 란 무엇인가 알아보도록 하자. 아래의 그림은 “성명”에 대한 내용이 나온다. 그림처럼 성과 명을 분리해야 하는가? 아니면 성과 명을 하나로 두어야 하는가?
![]() 이에 대한 답은 사용자가 인식하는 의미 있는 단위가 무엇인가에 따라서 판가름된다. 즉, 업무에 따라서 성과 명을 분리하느냐 합체하느냐가 결정된다는 것이다. 만약 성이 무엇인지 분명히 구분해야 한다면 성을 분리해야 하는 것이 당연하다. 이름의 경우는 아래의 그림과 같이 같은 경우가 많이 있다.
![]() 이름의 경우는 짓다 보니 같은 경우가 많이다. 우리나라의 경우는 “성명”이 붙어 있어야 의미가 있다. 그러나 외국의 경우는 First Name, Middle Name, Last Name 과 같이 분리해 놓은 사례를 많이 볼 수 있다. 이것은 성과 중간이름, 이름이 모두 의미가 있기 때문이다. 외국의 경우는 남성과 여성이 결혼을 하게 되면 여성의 성이 남편을 따라가게 되므로 분리해야 의미가 있을 수도 있다. 역시 업무에 따라서 틀려진다.
![]() 날짜의 속성을 년도, 월, 일과 같이 나누었다. 그리고 인덱스는 yyyy + mm + dd와 같이 결합인덱스로 구성하였다. 만약 다음의 쿼리와 같다면 오른쪽의 쿼리는 원하는 결과를 얻을 수 없을 것이다.
![]() 물론 인덱스를 사용할지 인덱스를 사용하지 못할지는 통계정보등의 상황과 옵티마이저가 결정할 일이지만 인덱스를 사용하지 못할 가능성도 많이 있다. 결합인덱스를 구성하는 두 개의 선행컬럼의 선택도에 달려 있는 문제이다. 이 경우는 성능보다도 어떻게 하면 원하는 결과를 나타낼 수 있는가가 문제이다. 개발자는 다음과 같은 생각을 할 수 있을 것이다.
‘200412’ and ‘200503’ --인덱스 사용 불가 그러나 이와 같이 where절의 조건을 사용한다면 인덱스를 사용하지 못한다. 왜냐하면 인덱스는 yyyy + mm + dd로 생성된 것이지 yyyymm으로 생성된 것이 아니기 때문이다. 또 다른 예를 보자.
![]() 사원번호 ‘1234’ 인 사원이 오늘이나 특정 일에 처리한 건수를 알고 싶다면 10만 건을 액세스 할 수 밖에 없다. 왜냐하면 발생일시 = {발생일 + 발생시간}로 복합속성으로 구성되고 있기 때문이다. 이러한 경우 하루 10만 건 발생하고 결합인덱스를 구성하는 첫 번째 컬럼이 ‘=’로 비교되지 않으므로 하루 발생 건수를 모두 뒤져야만 원하는 결과를 얻을 수 있다.
![]() 위 쿼리는 년도 보다는 mm의 선택도가 그나마 좋기 때문에 code와 mm 에 생성된 인덱스를 이용하여 Index Merge를 하게 된다. Merge의 방식은 Hash Match 방식이다. 문제는 이러한 단일 쿼리만을 봤을 때는 큰 영향이 없다. 그러나 이러한 것들이 쌓여 시스템에 전체적인 영향을 미치는 것이다.
테이블 수정이 불가능 할 경우는 년월일에 대한 각각의 단일 인덱스를 삭제하고 yyyy + mm + dd로 구성된 결합인덱스를 생성하는 것이 좋다. 어차피 년, 월, 일은 각각 선택도가 좋지 않기 때문에 쓰여지지 않는 불필요한 인덱스가 된다. 만약 따로 각각 나누어야 할 이유가 있었다면 이야기는 틀려지지만 대부분의 경우는 년, 월, 일의 각각이 가지는 의미가 없기 때문에 년월일이 합쳐진 속성을 하나의 속성이라고 보아야 옳다. 다음은 yyyy + mm + dd의 결합인덱스를 이용할 경우의 결과 이다. (873 row(s) affected)
Table 'date_test'. Scan count 2, logical reads 754, physical reads 0, read-ahead reads 0. 역시 필자가 의도한 바는 설계의 잘못으로 인해 성능에 미치는 영향이다. 테이블이 dt(yyyymmdd), code와 같이 만들어졌다면 대부분의 문제는 해결된다. index는 dt + code로 했다. select dt, code from date_test2 where dt like '200406%' and code like '10208%' (873 row(s) affected) Table 'date_test2'. Scan count 1, logical reads 1531, physical reads 0, read-ahead reads 0. select dt, code from date_test2 where dt in (select dt from dumy where dt like '200406%') and code like '10208%' (873 row(s) affected) Table 'date_test2'. Scan count 30, logical reads 98, physical reads 0, read-ahead reads 0. Table 'dumy'. Scan count 1, logical reads 3, physical reads 0, read-ahead reads 0. select a.dt, a.code from date_test2 a inner join (select dt from dumy where dt like cast(datepart(yyyy, getdate()) as varchar) + '%') b on a.dt = b.dt and a.code like '10208%' (5742 row(s) affected) Table 'Worktable'. Scan count 5759, logical reads 5796, physical reads 0, read-ahead reads 0. Table 'dumy'. Scan count 1, logical reads 3, physical reads 0, read-ahead reads 0. Table 'date_test2'. Scan count 1, logical reads 9343, physical reads 0, read-ahead reads 0. select a.dt, a.code from (select dt from dumy where dt like cast(datepart(yyyy, getdate()) as varchar) + '%') b inner loop join date_test2 a on a.dt = b.dt and a.code like '10208%' option (force order) (5742 row(s) affected) Table 'date_test2'. Scan count 366, logical reads 1146, physical reads 0, read-ahead reads 0. Table 'dumy'. Scan count 1, logical reads 4, physical reads 0, read-ahead reads 0. select * from date_test where code like '10208%' (5742 row(s) affected) Table 'date_test'. Scan count 1, logical reads 10199, physical reads 0, read-ahead reads 0. [edit]
10 설계속성과 다중값 속성 #설계속성은 실제 개체집합이 가지고 있는 속성이 아니라 모델러가 의도적(성능 등의 이유)으로 개체집합에 포함한 속성이다. 다중값 속성은 해당되는 속성이 단일값을 가지는 것이 아니라 다중값을 가지는 것을 의미한다. 많은 경우 순차적으로 DBMS에서 부여해주는 순번과 같은 속성을 추가해 주는 경우가 있다. 모델링에서는 이러한 설계속성은 식별자를 선정 할 때 고려하는 것이 바람직하다.
[edit]
11 정규화와 물리적인 요소 #정규화에 대한 정확한 내용은 다시 다루도록 하고, 여기에서는 개념만을 다루도록 하겠다. 데이터베이스를 다룬다면 “정규화”라는 말을 들어보았을 것이다. 정규화는 성능 튜닝의 도구이자 무결성을 지켜주는 데이터베이스 최고의 도구이다. 이 모든 것은 “데이터의 중복”을 없애는데 그 목적이 있다고 할 수 있겠다. (결점이 생기는 대부분의 원인도 데이터의 중복에 있다.)
--1번 쿼리 SELECT * FROM TAB WHERE ID BETWEEN 1 AND 1000 --2번 쿼리 SELECT * FROM TAB WHERE ID BETWEEN 1001 AND 5000 대부분의 사람은 당연히 1번이 빠를 것이라고 대답할 것이다. 그러나 이것은 물리적은 상황을 고려하지 않은 상태의 얘기이다. 최악의 경우에는 응답시간이 1번보다 2번이 훨씬 빠를 수도 있다. 다음의 그림을 보자.
![]() 1 ~ 5000 은 1개의 페이지에 들어가 있고, 1, 2, 3 … 1000 은 각각의 페이지에 저장되어 있다면 1000개의 페이지에 접근해야 원하는 결과를 얻을 수 있다는 것이다. 위와 같은 물리적은 상황이라면 1001 ~ 5000의 로우는 MSSQL SERVER 2000의 경우 8KB만 읽으면 되고, 1 ~ 1000개의 로우는 모두 8KB * 1000인 약 8MB를 읽어야 처리를 할 수 있다는 것이다. 이 경우 데이터의 양이 작으므로 응답시간의 차는 눈에 띄게 차이가 나지 않을 것이다. 그러나 이러한 상황을 더 많은 데이터를 가지고 있는 테이블에 적용시킨다면 문제는 커진다.
![]() 비정규화 테이블은 일반적으로 컬럼의 개수가 많다. 컬럼의 개수가 많다는 이야기는 하나의 데이터 행의 크기가 크다는 의미이다. 위에서 살펴본 바와 같이 하나의 데이터 행의 크기가 크다면 당연히 최소 입출력 단위에는 데이터 행이 많이 들어가지 못한다. 최악의 경우 입출력은 매우 많이 읽어나고, 데이터 행이 각각의 페이지에 존재할 수 있다. 다음과 같이 테이블이 만들었다고 가정하자.
CREATE TABLE 차량관리비내역( 차량번호 VARCHAR(10) , 관리부서 NUMBER , 연료 NUMBER , 주차비 NUMBER , 세차비 NUMBER , 윤활유 NUMBER , 부품대 NUMBER , 수리비 NUMBER , 타이어교환비 NUMBER , 자동차세 NUMBER , 환경세 NUMBER , 보험료 NUMBER ) 잘 보면 이와 같은 경우는 1차 정규화 위배나 똑같다. 연료, 주차비, 세차비, 윤활유 등은 “차량관리비” 정도로 만들고, “구분” 컬럼을 만들면 된다. 즉, “차량관리비”는 다중값 속성이되어 다른 테이블로 떨어져 나가게 된다. 그래서 다음과 같은 논리 ERD가 그려질 것이다.
![]() 각각의 테이블에 할당 받은 저장 공간을 효율적으로 사용하기 위해서는 테이블이 좁게 설계되어야 한다. 즉, 데이터 행을 하나의 최소 입출력 단위에 꽉꽉 채울 수 있다는 것이다.
[edit]
12 식별자와 설계속성 그리고 대리키 #개체집합을 대표하고 각각의 개체를 구별지을 수 있는 속성(들)이 식별자이다. 이러한 식별자는 설계속성으로 가려져 있는 경우가 많이 있다. 부모-자식 관계를 계속 찾아가다 보면 최 하위 레벨의 개체집합의 식별자는 커지기 마련이다. 그렇다면 어떤 레벨에서 비식별자 관계를 전환할 것인가에 대한 고민이 필요하다.
![]() 고개ID는 가변길이 문자형으로 12바이트이다. 만약 “캐릭터_아이템”의 크기가 매우 클 것으로 예상된다면 12바이트는 매우 큰 부담이 될 수도 있다. 이를 위해 “고객ID” 대신에 대리키를 두는 방법도 있다. 다음의 그림을 보자.
![]() “고객”의 크기는 “캐릭터_아이템”의 크기보다 월등히 작다. 그러므로 대리키(int형)으로 두면 Row 당 8바이트를 줄일 수 있다. 단 고객의 수가 int 형의 Max 값인 2,147,483,647 건 이하라고 가정해야 한다. 여기서 대리키는 설계속성이고, 고객ID를 대신하는 대리키이다.
[edit]
13 유도속성 #유도속성은 일반적으로 계산된 속성이라고도 한다. 대부분의 유도된 속성들은 필요 없다. 예를 들어 어떤 상품의 가격과 “부가세포함가”는 틀리다. 만약 “부가세포함가”를 하나의 속성으로 둔다면 상품의 가격에 부가세를 더해줘야 할 것이다. 그러나 유도속성을 쓰면 변화에 약해진다. 모든 상품의 가격을 10% 인상했다고 하면 그 동안 들어가 있던 유도속성 컬럼들까지 모두 변경해주어야 한다. 즉, 상품의 가격만 갱신해줘서 되는 게 아니라는 이야기다. 그러므로 정합성을 유지해야 하는 부담이 있다. 대부분은 유도속성을 쓰는 것은 금기사항으로 보면 된다.
|
친구를 사귐에 있어 실패하는 이유는 남을 칭찬하는 데 인색하기 때문이다. |