Row vs Column Oriented Databases¶
예시 테이블¶
| rowid | id | first_name | last_name | ssn | salary | dob | title | joined |
|---|---|---|---|---|---|---|---|---|
| 1001 | 1 | John | Smith | 111 | 101,000 | 1/1/1991 | eng | 1/1/2011 |
| 1002 | 2 | Kary | White | 222 | 102,000 | 2/2/1992 | mgr | 2/1/2012 |
| 1003 | 3 | Norman | Freeman | 333 | 103,000 | 3/3/1993 | mkt | 3/1/2013 |
| 1004 | 4 | Nole | Smith | 444 | 104,000 | 4/4/1994 | adm | 4/1/2014 |
| 1005 | 5 | Dar | Sol | 555 | 105,000 | 5/5/1995 | adm | 5/1/2015 |
| 1006 | 6 | Yan | Thee | 666 | 106,000 | 6/6/1996 | mkt | 6/1/2016 |
| 1007 | 7 | Hasan | Ali | 777 | 107,000 | 7/7/1997 | acc | 7/1/2017 |
| 1008 | 8 | Ali | Bilal | 888 | 108,000 | 8/8/1998 | acc | 8/1/2018 |
테스트 쿼리 (인덱스 없음)¶
SELECT first_name FROM emp WHERE ssn = 666;
SELECT * FROM emp WHERE id = 1;
SELECT SUM(salary) FROM emp;
Row-Oriented Database (행 기반)¶
- 테이블이 행(Row) 단위로 디스크에 저장됨
- 한 번의 블록 IO로 여러 행의 모든 컬럼을 가져옴
- 특정 행을 찾기 위해 더 많은 IO가 필요하지만, 행을 찾으면 모든 컬럼을 즉시 얻을 수 있음
디스크 저장 구조¶
[Page 1] 1001,1,John,Smith,111,101000,1/1/1991,eng,1/1/2011 ||| 1002,2,Kary,White,222,102000,2/2/1992,mgr,2/1/2012
[Page 2] 1003,3,Norman,Freeman,333,103000,3/3/1993,mkt,3/1/2013 ||| 1004,4,Nole,Smith,444,104000,4/4/1994,adm,4/1/2014
[Page 3] 1005,5,Dar,Sol,555,105000,5/5/1995,adm,5/1/2015 ||| 1006,6,Yan,Thee,666,106000,6/6/1996,mkt,6/1/2016
[Page 4] 1007,7,Hasan,Ali,777,107000,7/7/1997,acc,7/1/2017 ||| 1008,8,Ali,Bilal,888,108000,8/8/1998,acc,8/1/2018
쿼리 예시 1: SELECT first_name FROM emp WHERE ssn = 666¶
- Page 1 읽기 → ssn 666 없음 ❌
- Page 2 읽기 → ssn 666 없음 ❌
- Page 3 읽기 → ssn 666 발견! ✅ →
Yan반환 - 3번의 IO 필요
쿼리 예시 2: SELECT * FROM emp WHERE id = 1¶
- Page 1 읽기 → id=1 발견! ✅
- 모든 컬럼을 이미 가지고 있음 → 즉시 반환
- 1번의 IO로 충분
쿼리 예시 3: SELECT SUM(salary) FROM emp¶
- 모든 페이지를 읽어야 함 (4번의 IO)
- 각 행에서 salary 값만 필요하지만, 불필요한 다른 컬럼 데이터도 함께 읽힘
Column-Oriented Database (열 기반)¶
- 테이블이 열(Column) 단위로 디스크에 저장됨
- 한 번의 블록 IO로 하나의 컬럼에 대한 여러 행의 값을 가져옴
- 특정 컬럼의 값을 더 적은 IO로 가져올 수 있음. 하지만 여러 컬럼을 다루려면 더 많은 IO 필요
- OLAP에 적합
디스크 저장 구조¶
[id] 1:1001, 2:1002, 3:1003, 4:1004, 5:1005, 6:1006, 7:1007, 8:1008
[first_name] John:1001, Kary:1002, Norman:1003, Nole:1004 ||| Dar:1005, Yan:1006, Hasan:1007, Ali:1008
[last_name] Smith:1001, White:1002, Freeman:1003, Sol:1004 ||| Thee:1005, Ali:1006, Bilal:1007, Ali:1008
[ssn] 111:1001, 222:1002, 333:1003, 444:1004, 555:1005 ||| 666:1006, 777:1007, 888:1008
[salary] 101000:1001, 102000:1002, 103000:1003, 104000:1004, 105000:1005, 106000:1006, 107000:1007, 108000:1008
[dob] 1/1/1991:1001, 2/2/1992:1002, ... 8/8/1998:1008
[title] eng:1001, mgr:1002, mkt:1003, adm:1004, adm:1005, mkt:1006, acc:1007, acc:1008
[joined] 1/1/2011:1001, 2/1/2012:1002, ... 8/1/2018:1008
- 각 값은
값:rowid형식으로 저장
쿼리 예시 1: SELECT first_name FROM emp WHERE ssn = 666¶
- ssn 컬럼 스캔 →
666:1006발견 ✅ → rowid = 1006 - first_name 컬럼에서 rowid 1006 찾기 →
Yan:1006반환 - 2개의 컬럼만 읽으면 됨 (다른 컬럼은 건드리지 않음)
쿼리 예시 2: SELECT * FROM emp WHERE id = 1¶
- id 컬럼에서
1:1001찾기 ✅ - 모든 컬럼에서 rowid 1001에 해당하는 값을 각각 찾아야 함
- 모든 컬럼 파일을 읽어야 하므로 많은 IO 필요 ❌
쿼리 예시 3: SELECT SUM(salary) FROM emp¶
- salary 컬럼 하나만 읽으면 됨
- 모든 salary 값이 연속적으로 저장되어 있어 1번의 IO로 충분
- 매우 효율적 ✅
Pros & Cons 비교¶
| 항목 | Row-Based | Column-Based |
|---|---|---|
| 읽기/쓰기 | 최적화됨 | 쓰기가 느림 |
| 적합한 워크로드 | OLTP | OLAP |
| 압축 | 비효율적 | 매우 효율적 |
| 집계 (Aggregation) | 비효율적 | 매우 효율적 |
| 다중 컬럼 쿼리 | 효율적 | 비효율적 |