지도 기능을 구현하기 위해서 데이터베이스에 위도, 경도 좌표 저장이 필요했다.
효율적으로 좌표를 저장하기 위해서 "공간데이터" 타입을 설정해줄 수 있다.
공간데이터를 적용하기 전에, 공간데이터 타입의 종류와 데이터베이스에 따른 차이점 등을 알아보자.
R-tree 자료구조 기반으로 공간인덱싱이 활용이 되는데, 데이터베이스에 따라 실제 R-tree 기반의 종류도 조금씩 다르다. 이에 대해 더 자세히 살펴보자.
MySQL에서 제공하는 공간 데이터의 종류는 총 7가지이다.
단일 타입으로는 Point, LineString, Polygon 세 가지이다.
나머지 타입들은 이 세 가지 타입의 조합이다.
TypeORM 공식문서를 참고해보면 MySQL, PostgreSQL 등 DB에서 공간데이터를 생성하는 방법으로 크게 두 가지가 나온다.
(먼저 공간 인덱스를 사용하는 열에 spatial: true 옵션을 사용하여 인덱스를 추가한다.)
@Entity()
export class Thing {
@Column("point")
@Index({ spatial: true })
point: string
}
⚙️ 내부 동작
ST_Distance(), MBRContains() 등 Bounding Box 기반 연산이 가능특징
export interface Geometry {
type: "Point"
coordinates: [Number, Number]
}
@Entity()
export class Thing {
@Column("geometry", {
spatialFeatureType: "Point",
srid: 4326,
})
@Index({ spatial: true })
point: Geometry
}
⚙️ 내부 동작
geometry는 공간객체를 추상화된 타입으로 저장srid: 4326을 지정하면, MySQL이 해당 좌표계를 기준으로 지구구면 연산(거리, 면적 등)을 수행특징
POINT(lon lat)) 또는 WKB(Binary)GEOMETRY
├── POINT
├── LINESTRING
├── POLYGON
└── MULTI* (복수형 버전)
| 비교 항목 | point | geometry |
|---|---|---|
| 데이터타입 | 단일 Point만 저장 | Point, Line, Polygon 등 다양한 공간 객체 |
| 공간 인덱스 | 단순 R-Tree (Bounding Box) | 고급 GiST / R-Tree 기반 |
| 공간 연산 함수 지원 | MBRContains, MBRWithin 등 단순 직사각형 기반, (ST_Distance, ST_Within 일부만) | ST_Contains, ST_Intersects, ST_Distance 등 모든 ST_* 함수 완전 지원 (정밀 공간 연산) |
| 좌표계 변환 | 불가능 (단순 숫자좌표) | 가능 (예: EPSG:3857 ↔ EPSG:4326) |
| 사용 목적 | 내부 좌표 계산, 단순 위치정렬 | 지도/GIS용 실제 좌표계 기반 분석 |
어떤 상황에 어떤 데이터 타입이 적합할까?
| 상황 | 적합한 타입 |
|---|---|
| “유저가 마지막으로 클릭한 위치를 기록” | point |
| “서울시 내 카페를 반경 2km 이내 검색” | geometry + SRID=4326 |
| “행정구역 폴리곤과 포함 여부 판정” | geometry |
| “지도 렌더링 / 거리 계산 / 좌표 변환” | geometry |
하지만 어떤 데이터베이스를 사용하느냐에 따라 같은 point, geometry 타입이어도 내부 동작이 다르다.
POINT + SRID가 부분지원(메타데이터 수준) POINT + SRID가 완전한 공간객체 지원| 항목 | MySQL | PostGIS |
|---|---|---|
POINT + SRID | SRID 저장만 가능, 좌표계 변환/지구거리 불가 | 완전한 geometry 연산 지원 |
GEOMETRY(Point) + SRID | 동일하게 SRID 적용, 모든 공간연산 가능 | 완전 동일 (내부적으로도 같은 구조) |
| 권장 방식 | GIS 분석 목적이면 geometry(Point, srid) | 둘 다 가능 |
정리하자면
POINT + SRID는 단순히 좌표계 정보만 메타데이터로 포함한 버전GEOMETRY(Point, SRID)는 GIS 전용 연산, 좌표계 변환, 거리 계산 등 고급 기능까지 가능한 완전한 공간객체👉 “지도 서비스 수준의 공간 연산”을 한다면 geometry(Point, 4326)
👉 반면 “그냥 위치좌표만 저장”하는 경우라면 POINT SRID 4326으로 충분하다.
두 가지 타입은 공간 인덱스를 만들기 위해 R-Tree를 기반으로 동작한다.
하지만 그 구현 방식과 제한사항도 DB 엔진(MyISAM, InnoDB, PostGIS)에 따라 다르다.
| DB 엔진 | POINT 인덱스 구조 | 내부 알고리즘 | 한계 |
|---|---|---|---|
| MyISAM (MySQL) | R-Tree | 전통적 R-Tree | SRID/좌표계, 트랜잭션 미지원, 단순 Bounding Box, InnoDB 대비 안정성 낮음 |
| InnoDB (MySQL 5.7+) | “R-Tree-like” (B-Tree + Spatial extension) | B-Tree 기반으로 구현된 공간인덱스 구조 (R-Tree와 유사한 구조로 최적화) | MBR(최소경계사각형) 단위 비교, 진짜 R-Tree는 아님 |
| PostGIS (PostgreSQL) | GiST 기반 R-Tree variant | R-Tree의 일반화 버전 (GiST) | 완전한 공간 연산, SRID/좌표계 지원 |
MBRContains, MBRIntersects, MBRWithin 등) 시 R-Tree 탐색 과정을 통해 MBR이 겹치는 후보군을 빠르게 필터링한다.✏️ 내부 연산 과정
1. 쿼리(예: MBRContains()) 시, R-Tree 인덱스가 각 객체의 MBR을 계층적으로 탐색
2. MBR이 겹치는 후보군만 빠르게 반환
3. 필요 시 애플리케이션 레벨에서 추가적인 geometry 연산 수행
➡️ 즉, 진짜 R-Tree 구조를 구현한 MySQL의 고전적 공간 인덱스 엔진으로,
MBR 단위의 공간 필터링을 빠르게 수행하는 구조이다.
"x_min, y_min, x_max, y_max" → 1차원 key로 변환해서 B-Tree에 저장➡️ R-Tree의 개념(공간 분할, bounding box) 은 차용하지만,
알고리즘과 트리 구조 자체는 B-Tree이다.
✏️ 내부 연산 과정
1. 쿼리(예: ST_Within()) 시, 인덱스가 각 포인트의 MBR을 조회
2. 후보군을 MBR 기준으로 필터링
3. 그 다음 “정확한 계산” 없이 결과 반환 (MBR 기반만 수행)
즉, “Bounding box overlap check”까지만 하는 1단계 필터이다.
✏️ GiST의 구조
Bounding Box + Consistency Functiondoes overlap?, is within?, distance < threshold?)ST_Within, ST_Intersects 등)➡️ 즉, R-Tree의 공간 계층 구조 + 정확한 geometry 연산까지 포함된 진짜 공간 인덱스
MySQL InnoDB
└── B-Tree Page → MBR(x_min, y_min, x_max, y_max)
└── Record → WKB binary of geometry
(정렬 기준: Bounding Box 좌표)
PostGIS GiST
└── GiST Node → Bounding Box + Consistency Function
├── Leaf → geometry binary (w/ SRID)
└── 연산 단계: Rough Filter → Exact Geometry Check