空間インデックスについて

Reference:

🔍 空間インデックスの概要(spatial index)
- 地理的または空間的なデータ(位置情報や図形)を高速に検索・操作するためのインデックス。
- 地図、座標、形状、範囲などを扱うアプリケーションで用いられる。

項目 | 内容 | 例・備考 |
---|---|---|
📌 対象 | 緯度・経度、点、線、多角形などのジオメトリデータ | GPS位置情報、地図データなど |
🎯 目的 | 空間演算を高速化 | 距離検索、範囲検索、重なり判定など |
⚙️ インデックス構造 | R-Tree、Quadtree、GiST など | 多次元データ向けの最適化構造 |
🗂 対応RDBMS | PostgreSQL(PostGIS)、MySQL、Oracle Spatial など | 空間型をサポートするDBMS |

🧭 なぜ空間インデックスが必要?
通常のB-treeインデックスは線形なキー(数値や文字列)に最適ですが、空間データは2次元または多次元になっている。
例えば:
- 「この地点から半径5km以内のレストラン」
- 「このポリゴンと重なる建物の範囲」
↑上記のような空間的な条件では、単純な数値比較ではなく、空間的な関係(距離、包含、交差) を考慮する必要があり、空間インデックスはこれを効率化することを目的としている。

🧱 空間インデックスの例(MySQL)
MySQLでは、 SPATIAL INDEX を使って以下のように定義する。
latitudeとlongtitudeを表した位置情報を管理するテーブル
CREATE TABLE `location_lat_lng` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255),
`lat` double NOT NULL,
`lng` double NOT NULL,
PRIMARY KEY (`id`),
KEY `lat` (`lat`,`lng`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
このテーブルに対して以下のようなクエリを投げる
SELECT
*
FROM location_lat_lng
WHERE lat BETWEEN 35.681 AND 35.691
AND lng between 139.757 AND 139.767;
これだとインデックスがそこまで有効に使われない。
MySQLは普通のインデックスの複数の範囲指定には弱い。
検索を高速に行うには Spatial Data Types を利用するのが良い。※内部的にR-Treeインデックスが使われる
CREATE TABLE `location` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255),
`location` point NOT NULL, -- location_lat_lng テーブルのlatとlngを合わせて1つにしたようなデータ型、つまり座標を示すカラム
PRIMARY KEY (`id`),
SPATIAL KEY `loc` (`location`) -- これ
) ENGINE=InnoDB DEFAULT CHARSET=utf8
- location は POINT 型のカラム。(例: 緯度・経度)
-
SPATIAL INDEX
により空間検索を高速化する。 - [注意] InnoDB では、空間インデックスを利用するにはカラムに NOT NULL 制約 が必要。
ここでは POINT 型のカラムに空間インデックスを作成し、ST_Distance_Sphere() や ST_Within() などの関数で効率的に検索できるようにしている。

✅ 空間インデックスの利点と注意点
利点 | 注意点 | 備考 |
---|---|---|
空間検索が高速になる | インデックス作成に時間がかかる | 大量データでは初期構築コストあり |
ジオメトリ間の演算が最適化される | 複雑な形状には制限がある場合も | 実装依存の制限が存在することも |
範囲クエリや近接検索に強い | 対応関数を使わないと利用されない |
ST_Within や ST_DWithin など |

📌 範囲検索のクエリ例:指定した中心点から半径5km以内
例えば以下のようなデータが存在するとして、
INSERT INTO location (name, location)
VALUES
('渋谷駅', ST_GeomFromText('POINT(139.7013 35.6580)')),
('新宿駅', ST_GeomFromText('POINT(139.7004 35.6900)')),
('東京駅', ST_GeomFromText('POINT(139.7671 35.6812)'));
このテーブルに対して以下のようなクエリを実行する
SELECT name,
ST_Distance_Sphere(location, ST_GeomFromText('POINT(139.7000 35.6895)')) AS distance_m
FROM location
WHERE ST_Distance_Sphere(location, ST_GeomFromText('POINT(139.7000 35.6895)')) <= 5000;
memo: 新宿駅付近を中心とした5km圏内の地点を検索、 ST_Distance_Sphere()
で地球上の球面距離(メートル)を求める。

⚠️ パフォーマンス改善:MBRで候補を先に絞る
ST_Distance_Sphere()
はフルスキャンになってしまう。
よって、まずは バウンディングボックス(最小外接矩形) による簡易検索で候補を絞り込んだ上で、厳密な距離フィルタを行うのが推奨されるパターンとなる。
※要するに MBRWithin()
や MBRContains
で候補を絞り、その上で ST_Distance_Sphere()
で厳密にフィルタするのが良い。
SELECT name,
ST_Distance_Sphere(location, ST_GeomFromText('POINT(139.7000 35.6895)')) AS distance_m
FROM location
WHERE MBRWithin(
location,
ST_GeomFromText('POLYGON((139.66 35.64, 139.74 35.64, 139.74 35.74, 139.66 35.74, 139.66 35.64))')
)
AND ST_Distance_Sphere(location, ST_GeomFromText('POINT(139.7000 35.6895)')) <= 5000;
- MBRWithin(): 緯度経度による矩形(ポリゴン)範囲内のデータのみを抽出(インデックス使用可)
- ST_Distance_Sphere(): 抽出した候補から、実際の球面距離を計算してフィルタ