> ## Documentation Index
> Fetch the complete documentation index at: https://villagesql.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# MySQL에서 IP로 지리 위치 조회

> MySQL에서 IP 지리 위치 조회를 하는 방법 — MaxMind GeoLite2 또는 API 기반 데이터 소스 가져오기, 국가와 지역에 대한 IP 범위 조회, 정확도 트레이드오프, 통합 IPv4/IPv6 조회를 위한 VillageSQL의 INET 타입 사용.

> [card] [VillageSQL is a drop-in replacement for MySQL with extensions.](https://villagesql.com/docs/en/8.4/stable/install-server.md)
>
> All examples on this page work on VillageSQL. Install Now →

MySQL에서의 IP 지리 위치는 IP 범위를 위치에 매핑하는 데이터베이스를 가져온 다음 범위 검사로 조회하는 방식으로 동작합니다. 표준 접근 방식은 IP 범위를 정수로 저장하고 `BETWEEN`을 사용합니다. VillageSQL의 `INET` 타입은 스키마를 더 표현력 있게 만들고 같은 테이블에서 IPv4와 IPv6을 처리합니다.

## IP 지리 위치의 동작 방식

IP를 국가에 매핑하는 MySQL 함수는 없습니다. 지리 위치는 조회 문제입니다: IP 범위 테이블이 있고 — 각 행은 "X부터 Y까지의 주소는 국가 Z에 있다"고 말합니다 — 들어오는 IP를 포함하는 범위를 찾는 것입니다.

MaxMind GeoLite2 같은 무료 데이터베이스는 이런 범위를 MySQL로 가져올 수 있는 CSV 파일로 제공합니다.

## 표준 접근 방식: 정수 범위

MaxMind GeoLite2(및 유사한 데이터베이스)는 `network`를 CIDR 문자열로, 각 범위의 시작/끝을 정수로 제공합니다. 정수 접근 방식은 크기가 작고 인덱스 친화적입니다:

```sql theme={null}
CREATE TABLE geoip_ranges (
    start_ip   INT UNSIGNED NOT NULL,
    end_ip     INT UNSIGNED NOT NULL,
    country    CHAR(2),
    INDEX idx_start (start_ip),
    INDEX idx_end   (end_ip)
);

-- Import from GeoLite2-Country-Blocks-IPv4.csv after converting to int ranges
-- Then query:
SELECT country FROM geoip_ranges
WHERE INET_ATON('203.0.113.45') BETWEEN start_ip AND end_ip
LIMIT 1;
```

이것은 빠릅니다 — `start_ip`의 인덱스가 효율적인 범위 스캔을 가능하게 합니다. 제한은 IPv4에서만 동작한다는 것입니다. IPv6에는 별도의 테이블(`VARBINARY(16)` 범위)이 필요합니다.

## VillageSQL과 함께: INET으로 IPv4/IPv6 통합

VillageSQL의 `INET` 타입은 IPv4와 IPv6 주소를 모두 자연스러운 순서로 저장하므로, 두 주소 체계에 하나의 테이블을 사용할 수 있습니다. 범위 검사에서는 `inet_compare()`가 `BETWEEN`의 자리를 대신합니다.

```sql theme={null}
INSTALL EXTENSION vsql_network_address;
```

```sql theme={null}
CREATE TABLE geoip_ranges (
    start_ip   INET NOT NULL,
    end_ip     INET NOT NULL,
    country    CHAR(2),
    region     VARCHAR(100),
    INDEX idx_start (start_ip)
);

-- Import IPv4 ranges
INSERT INTO geoip_ranges (start_ip, end_ip, country) VALUES
    (inet_from_string('203.0.113.0'), inet_from_string('203.0.113.255'), 'US'),
    (inet_from_string('198.51.100.0'), inet_from_string('198.51.100.255'), 'DE');

-- Import IPv6 ranges in the same table
INSERT INTO geoip_ranges (start_ip, end_ip, country) VALUES
    (inet_from_string('2001:db8::'), inet_from_string('2001:db8::ffff'), 'US');

-- Look up a country for an IPv4 address
SELECT country, region FROM geoip_ranges
WHERE inet_compare(inet_from_string('203.0.113.45'), start_ip) >= 0
  AND inet_compare(inet_from_string('203.0.113.45'), end_ip)   <= 0
LIMIT 1;

-- Same query works for IPv6
SELECT country FROM geoip_ranges
WHERE inet_compare(inet_from_string('2001:db8::42'), start_ip) >= 0
  AND inet_compare(inet_from_string('2001:db8::42'), end_ip)   <= 0
LIMIT 1;
```

### CIDR 네트워크로 저장(대안 스키마)

GeoIP 데이터가 시작/끝 쌍이 아니라 CIDR 블록을 제공한다면, 네트워크 열에 `CIDR`을 사용하고 서브넷 쿼리로 포함 여부를 확인하세요:

```sql theme={null}
CREATE TABLE geoip_cidrs (
    network    CIDR NOT NULL,
    country    CHAR(2),
    INDEX idx_network (network)
);

INSERT INTO geoip_cidrs (network, country) VALUES
    (cidr_from_string('203.0.113.0/24'), 'US'),
    (cidr_from_string('198.51.100.0/24'), 'DE');

-- Look up by subnet match
SELECT country FROM geoip_cidrs
WHERE cidr_to_string(inet_network(inet_set_masklen(
    inet_from_string('203.0.113.45'),
    inet_masklen(inet_from_string(cidr_to_string(network)))
))) = cidr_to_string(network)
LIMIT 1;
```

## 접근 방식 선택

| 접근 방식 | IPv6 | 스키마 | 성능 |
| :- | :- | :- | :- |
| `INT UNSIGNED` 범위 + `BETWEEN` | 아니요(별도 테이블) | 별도의 IPv4/IPv6 테이블 두 개 | 빠름 — 정수 인덱스 범위 스캔 |
| `INET` 범위 + `inet_compare` | 예(통합) | 두 체계에 단일 테이블 | 좋음 — `start_ip`에 인덱스 |
| `CIDR` 네트워크 + 서브넷 매치 | 예(통합) | 단일 테이블 | 느림 — 행마다 네트워크 계산 |

순수 IPv4와 최대 쿼리 속도에는 `INT UNSIGNED` + `BETWEEN` 접근 방식을 이기기 어렵습니다. 한 테이블에서 IPv4와 IPv6을 모두 처리해야 하는 스키마에는 `inet_compare`를 쓰는 `INET`이 더 깔끔하고 두 개의 병렬 테이블을 유지하는 수고를 덜어줍니다.

## 지리 위치 데이터 소스 비교

쿼리 스키마는 결정의 절반에 불과합니다 — 데이터 소스가 정확도, 최신성, 비용을 결정합니다. 흔한 선택지를 비교하면 다음과 같습니다:

| 데이터 소스 | 국가 정확도 | 도시 정확도 | 지연 시간 | 비용 | MySQL 통합 |
| :- | :- | :- | :- | :- | :- |
| MaxMind GeoLite2(무료) | \~98% | \~80% | 프로세스 내(SQL 또는 `.mmdb`) | 무료, 등록 필요 | CSV 가져오기; 매주 갱신 |
| MaxMind GeoIP2(유료) | \~99.8% | \~85–90% | 프로세스 내 | 월 \$20부터 | GeoLite2와 같은 가져오기 흐름 |
| ip-api.com(무료 티어) | \~99% | \~83% | HTTP, \~30–100 ms | 무료(분당 45 요청); 유료는 제한 해제 | 애플리케이션 계층 HTTP 호출; 결과는 MySQL에 저장 |
| ip-api.com(프로) | \~99% | \~83% | HTTP, \~30–100 ms | 월 \$15부터 | 동일; HTTPS + 속도 제한 없음 |
| ipinfo.io | \~99% | \~85% | HTTP, \~30–100 ms | 무료(월 5만 요청); 그 이상은 유료 티어 | 애플리케이션 계층; 결과를 MySQL에 캐시 가능 |

**국가 수준 정확도** 수치는 제공자가 자체 보고한 것이며 지역에 따라 다릅니다 — 아시아 태평양과 라틴 아메리카의 커버리지는 모든 소스에서 북미와 유럽보다 낮은 경향이 있습니다.

**어느 것을 고를지:** MaxMind GeoLite2가 표준 출발점입니다 — 무료이고, 한 번 가져오는 플랫 파일로 제공되며, 조회가 외부 의존성 없이 MySQL 안에서 완전히 실행됩니다. 프로덕션에서 도시 수준 정확도가 필요하면 GeoIP2 Precision이 업그레이드 경로입니다. API 기반 소스(ip-api.com, ipinfo.io)는 소량 사용에 편리하지만 모든 조회에 HTTP 지연 시간과 속도 제한 의존성을 추가합니다.

## 각 접근 방식을 사용할 때

| 시나리오 | 권장 접근 방식 |
| :- | :- |
| 저장된 접근 로그에 대한 배치 분석 | MySQL의 GeoLite2 — SQL에서 직접 조인 |
| 모든 HTTP 요청에 대한 실시간 조회(대량) | 애플리케이션 코드의 MaxMind `.mmdb` 리더 — SQL 왕복 없음 |
| 소량 보강(분당 \<45 요청), DB 가져오기 오버헤드 없음 | HTTP를 통한 ip-api.com 무료 티어 |
| IPv4 전용, 최대 쿼리 속도 | GeoLite2와 함께 `INT UNSIGNED` + `BETWEEN` |
| 단일 테이블의 IPv4 + IPv6 | GeoLite2와 함께 VillageSQL `INET` + `inet_compare` |
| 도시, ASN 또는 ISP 데이터 필요 | GeoIP2 Precision(유료) 또는 ipinfo.io |
| 외부 API 호출이 없는 격리 환경 | MaxMind GeoLite2(로컬 가져오기) |
| 설정 없는 프로토타이핑/테스트 | ip-api.com 무료 티어 |

## GeoLite2 데이터 가져오기

MaxMind GeoLite2(무료, 등록 필요)는 CSV 파일을 제공합니다. 범위 기반 스키마의 경우:

```sql theme={null}
-- After downloading GeoLite2-Country-Blocks-IPv4.csv
-- Convert the network column to start/end ranges and load:
LOAD DATA INFILE '/path/to/ipv4_ranges.csv'
INTO TABLE geoip_ranges
FIELDS TERMINATED BY ',' ENCLOSED BY '"'
LINES TERMINATED BY '\n'
IGNORE 1 ROWS
(@start_ip_str, @end_ip_str, country)
SET start_ip = inet_from_string(@start_ip_str),
    end_ip   = inet_from_string(@end_ip_str);
```

MaxMind는 바이너리 `.mmdb` 형식과 Python, Node.js, Go 같은 언어용 공식 클라이언트 라이브러리도 제공하며, 대량 시나리오에서는 SQL 조회보다 빠릅니다.

## 자주 묻는 질문

#### 지리 위치는 MySQL에서 해야 하나요, 애플리케이션 코드에서 해야 하나요?

대량 조회(모든 HTTP 요청)에는 MaxMind의 `.mmdb` 리더 라이브러리를 사용하는 애플리케이션 계층 지리 위치가 더 빠릅니다 — 메모리 내 이진 탐색이며 SQL 왕복이 없습니다. SQL 기반 지리 위치는 배치 분석, 보고 쿼리, 이미 MySQL에서 실행되는 데이터 파이프라인에 적합합니다.

#### GeoIP 데이터베이스를 어떻게 최신으로 유지하나요?

MaxMind는 GeoLite2를 매주 갱신합니다. 주기적인 다운로드와 지리 위치 테이블의 `TRUNCATE` + 다시 로드를 스크립트로 작성하세요. 또는 MaxMind의 mmdbinspect나 새로 고침 스크립트를 사용하여 델타 갱신을 적용하세요.

#### MySQL에서 IP 지리 위치는 얼마나 정확한가요?

정확도는 MySQL 스키마가 아니라 전적으로 데이터 소스에 달려 있습니다. 국가 수준에서 MaxMind GeoLite2(무료)는 IPv4 주소의 약 98%에 대해 정확합니다. 도시 수준에서는 약 80%로 떨어집니다. 더 높은 정확도를 위해 MaxMind의 유료 GeoIP2 Precision 제품은 국가 \~99.8%, 도시 \~85–90%에 이릅니다. 완벽한 데이터베이스는 없습니다 — 모바일 네트워크, VPN, 일부 아시아 태평양 범위에서는 정확도가 보통 더 낮습니다. MySQL 쿼리 접근 방식은 정확도에 영향을 주지 않습니다. 가져온 데이터가 말하는 그대로를 얻습니다.

#### MySQL이 플러그인이나 외부 API 없이 IP 지리 위치를 조회할 수 있나요?

예. 표준 접근 방식 — 이 가이드가 다루는 것 — 은 GeoIP 데이터베이스(MaxMind GeoLite2 같은)를 정수 범위로 일반 MySQL 테이블에 가져옵니다. 조회는 평범한 SQL `BETWEEN` 쿼리입니다. 플러그인도, 외부 API도, 쿼리 시점의 네트워크 호출도 없습니다. 유일한 의존성은 MaxMind의 플랫 CSV 파일을 가져오고 주기적으로 새로 고치는 것입니다. VillageSQL의 `vsql_network_address` 확장은 더 깔끔한 IPv4/IPv6 처리를 위한 `INET` 타입을 추가하지만, 기본 접근 방식에 필수는 아닙니다.

#### 지리 위치를 접근 로그와 한 쿼리에서 조인할 수 있나요?

예 — 그것이 GeoIP 데이터를 MySQL에 두는 주요 이유 중 하나입니다:

```sql theme={null}
SELECT
    g.country,
    COUNT(*) AS request_count
FROM access_log a
JOIN geoip_ranges g
  ON inet_compare(a.ip_address, g.start_ip) >= 0
 AND inet_compare(a.ip_address, g.end_ip)   <= 0
GROUP BY g.country
ORDER BY request_count DESC;
```

## 문제 해결

| 문제 | 해결 방법 |
| :- | :- |
| `FUNCTION <database>.inet_from_string does not exist` | `INSTALL EXTENSION vsql_network_address`를 실행하세요 |
| 조회 결과가 없음 | 그 블록의 IP 범위를 가져왔는지 확인하세요. `SELECT COUNT(*) FROM geoip_ranges`를 시도해 보세요 |
| 조회가 잘못된 국가를 반환함 | GeoIP 데이터베이스에는 커버리지 공백이 있습니다 — MaxMind의 웹 조회 도구와 대조해 확인하세요 |
| `LOAD DATA INFILE`이 실패함 | `secure_file_priv` 설정을 확인하세요. 또는 애플리케이션 쪽 배치 삽입을 사용하세요 |

## 참고

* [MySQL에서 IP 주소 저장하기](https://villagesql.com/docs/ko/guides/storing-ip-addresses.md) — 지리 위치 쿼리 전의 효율적인 IP 저장
* [MySQL에서 서브넷으로 조회하기](https://villagesql.com/docs/ko/guides/subnet-queries.md) — 지리 위치가 기반으로 삼는 범위 기반 IP 조회


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.