PostGIS Spatial Index是什么
Spatial Index(空间索引)是GIS数据库中用于提高空间查询效率的数据结构。
在PostGIS中,空间索引最常见的实现方式是:
GiST
例如一个城市数据库中有:
100万
个地点。
如果没有空间索引,查询附近地点时可能需要:
100万个Geometry
↓
逐个进行空间计算
↓
得到结果
数据量越大,查询速度越慢。
使用空间索引以后:
100万个Geometry
↓
Spatial Index
↓
快速筛选候选数据
↓
精确空间计算
↓
得到结果
因此:
PostGIS空间索引的核心作用,是减少空间查询需要检查的数据量。
为什么需要空间索引
普通数据库中的索引可以快速查找:
id = 100
name = 北京
age > 30
空间查询则更加复杂。
例如:
查询距离某个位置500米以内的所有商店。
查询条件可能是:
距离
范围
相交
包含
重叠
相邻
如果直接对所有Geometry进行计算:
Store 1
Store 2
Store 3
...
Store 1,000,000
数据库需要处理大量空间对象。
空间索引可以先快速缩小搜索范围。
Spatial Index的基本原理
空间索引通常不会直接判断最终的精确空间关系。
它更像是:
第一阶段
快速筛选
↓
候选Geometry
第二阶段
精确计算
↓
最终结果
例如查询:
某点500米范围内的商店
可以理解为:
Point
↓
搜索空间索引
↓
找到可能相关的Geometry
↓
ST_DWithin精确判断
↓
返回结果
因此:
空间索引主要负责“快速缩小范围”,空间函数负责“精确判断”。
Bounding Box
PostGIS空间索引的重要基础之一是:
Bounding Box
即最小外接矩形。
例如一个Polygon:
Polygon
/------\
/ \
/ \
/------------\
可以使用一个矩形包围:
+----------------+
| |
| Polygon |
| |
+----------------+
这个矩形就是Geometry的Bounding Box。
可以理解为:
复杂Geometry
↓
Bounding Box
↓
简单矩形
↓
快速空间判断
矩形判断通常比复杂Geometry之间的精确计算更加高效。
GiST是什么
GiST是PostgreSQL提供的一种通用索引框架。
全称是:
Generalized Search Tree
PostGIS使用GiST来构建非常重要的空间索引。
例如:
CREATE INDEX idx_places_geom
ON places
USING GIST (geom);
其中:
CREATE INDEX
↓
创建索引
USING GIST
↓
使用GiST
geom
↓
空间字段
因此可以简单记住:
PostGIS中最常见的空间索引就是基于GiST创建的。
创建空间索引
假设有一个表:
CREATE TABLE places (
id SERIAL PRIMARY KEY,
name TEXT,
geom geometry(Point, 4326)
);
创建空间索引:
CREATE INDEX idx_places_geom
ON places
USING GIST (geom);
创建完成以后:
places
├── id
├── name
└── geom
↓
GiST Index
数据库就可以在空间查询中利用这个索引。
建议给Geometry创建索引
如果表中存在大量Geometry:
geom geometry(Point, 4326)
通常应该考虑:
CREATE INDEX idx_places_geom
ON places
USING GIST (geom);
尤其是存在以下查询时:
附近查询
范围查询
空间相交
空间包含
空间距离
空间连接
空间索引通常非常重要。
ST_Intersects与空间索引
例如:
SELECT *
FROM roads
WHERE ST_Intersects(
geom,
ST_GeomFromText(
'POLYGON ((...))',
4326
)
);
如果Geometry字段建立了合适的空间索引:
roads.geom
↓
GiST
↓
快速筛选
↓
ST_Intersects
↓
精确判断
这样可以避免大量无意义的Geometry计算。
ST_DWithin与空间索引
ST_DWithin常用于附近查询。
例如:
SELECT *
FROM places
WHERE ST_DWithin(
geom,
ST_SetSRID(
ST_Point(116.397, 39.908),
4326
),
0.01
);
它可以用于查询指定距离范围内的数据。
典型应用:
附近商店
附近医院
附近停车场
附近充电站
附近设备
空间索引可以帮助数据库快速缩小搜索范围。
ST_Contains与空间索引
例如:
查询某个行政区内的所有POI。
可以:
SELECT *
FROM places
WHERE ST_Contains(
district.geom,
places.geom
);
空间索引可以帮助减少需要进行精确空间判断的对象。
典型结构:
行政区Polygon
↓
Spatial Index
↓
候选POI
↓
ST_Contains
↓
最终结果
ST_Within与空间索引
例如:
SELECT *
FROM places
WHERE ST_Within(
places.geom,
district.geom
);
表示:
places
↓
是否位于
↓
district
对于大量空间数据:
Spatial Index
↓
快速筛选
↓
ST_Within
可以明显减少计算范围。
空间索引与普通索引的区别
普通B-Tree索引:
id
name
price
date
更适合:
=
>
<
BETWEEN
ORDER BY
空间索引:
Geometry
Geography
更适合:
相交
包含
范围
距离
空间关系
可以简单理解:
| 索引类型 | 主要处理 |
|---|---|
| B-Tree | 数值、字符串、排序 |
| GiST | 空间数据等复杂类型 |
| 空间索引 | 空间范围与空间关系 |
GiST索引示例
完整示例:
CREATE TABLE buildings (
id SERIAL PRIMARY KEY,
name TEXT,
geom geometry(Polygon, 4326)
);
创建空间索引:
CREATE INDEX idx_buildings_geom
ON buildings
USING GIST (geom);
查询:
SELECT *
FROM buildings
WHERE ST_Intersects(
geom,
ST_GeomFromText(
'POLYGON ((...))',
4326
)
);
数据库可以利用:
idx_buildings_geom
来加速查询。
查看索引
可以使用:
SELECT
indexname,
indexdef
FROM pg_indexes
WHERE tablename = 'buildings';
可以看到类似:
idx_buildings_geom
CREATE INDEX ...
USING gist (geom)
EXPLAIN查看查询计划
可以使用:
EXPLAIN
SELECT *
FROM buildings
WHERE ST_Intersects(
geom,
ST_GeomFromText(
'POLYGON ((...))',
4326
)
);
如果需要看到实际执行情况:
EXPLAIN ANALYZE
SELECT *
FROM buildings
WHERE ST_Intersects(
geom,
ST_GeomFromText(
'POLYGON ((...))',
4326
)
);
可以观察数据库是否使用了:
Index Scan
Bitmap Index Scan
Seq Scan
如果数据量较大,而查询始终使用:
Seq Scan
就需要进一步检查:
空间索引
查询条件
数据量
统计信息
函数写法
坐标系
Index Scan
理想情况下,数据库可能使用:
Index Scan
或者:
Bitmap Index Scan
例如:
Query
↓
GiST Index
↓
Candidate Rows
↓
Geometry Check
↓
Result
这通常比:
Query
↓
Sequential Scan
↓
逐条检查全部数据
更加高效。
Sequential Scan
Sequential Scan也叫:
Seq Scan
表示数据库逐行扫描表。
例如:
1
2
3
4
...
1,000,000
如果空间数据非常多:
100万Geometry
就可能产生大量计算。
但需要注意:
Seq Scan并不一定代表查询有问题。
如果表非常小:
100条
数据库可能认为直接扫描比使用索引更加高效。
所以:
没有使用索引
≠
一定有问题
应该结合:
数据量
查询成本
执行计划
进行判断。
空间索引不是越多越好
索引可以提高查询速度,但也会增加:
磁盘空间
INSERT成本
UPDATE成本
DELETE成本
维护成本
例如:
INSERT
↓
写入Geometry
↓
更新空间索引
因此不应该为每个字段都建立索引。
通常应该根据实际查询场景建立。
Geometry字段修改后的影响
如果Geometry发生大量更新:
UPDATE buildings
SET geom = ...;
空间索引也需要维护。
大量数据更新后,可以考虑:
VACUUM ANALYZE buildings;
或者:
ANALYZE buildings;
帮助数据库获得更加准确的统计信息。
空间索引与坐标系
空间索引本身并不能解决坐标系错误。
例如:
数据
EPSG:4326
查询Geometry
EPSG:3857
如果没有正确进行坐标转换:
Spatial Query
↓
错误坐标
↓
错误结果
因此空间查询之前需要确认:
Geometry类型
SRID
坐标单位
查询Geometry
ST_SetSRID与空间索引
需要特别注意:
ST_SetSRID
和:
ST_Transform
并不是一回事。
例如:
ST_SetSRID(
ST_Point(116.397, 39.908),
4326
)
只是告诉PostGIS:
这些坐标属于4326
而:
ST_Transform(
geom,
3857
)
才是真正进行坐标转换。
如果在查询中大量对索引字段执行:
ST_Transform(geom, ...)
需要特别注意查询计划和索引使用情况。
不要随意对索引字段进行函数包装
例如索引建立在:
geom
但查询写成:
WHERE ST_Transform(geom, 3857) ...
此时数据库是否能够有效利用原有索引,需要结合具体查询和索引情况判断。
更合理的方式通常是:
统一坐标系
↓
准备查询Geometry
↓
使用索引字段进行空间查询
对于确实需要经常使用转换后坐标进行查询的场景,也可以考虑针对表达式建立合适的索引。
Geography空间索引
PostGIS的Geography类型也可以建立空间索引。
例如:
CREATE TABLE places (
id SERIAL PRIMARY KEY,
name TEXT,
geog geography(Point, 4326)
);
创建索引:
CREATE INDEX idx_places_geog
ON places
USING GIST (geog);
之后可以用于:
距离查询
附近查询
空间关系查询
多列空间查询
实际项目中经常同时存在:
tenant_id
type
status
geom
例如:
查询某个租户
+
某种设备
+
某个区域内
查询:
SELECT *
FROM devices
WHERE tenant_id = 10
AND type = 'camera'
AND ST_Intersects(
geom,
:polygon
);
这时候可以根据实际数据规模和查询模式考虑:
普通索引
+
空间索引
例如:
CREATE INDEX idx_devices_tenant
ON devices (tenant_id);
CREATE INDEX idx_devices_geom
ON devices
USING GIST (geom);
然后让数据库根据查询计划选择合适的执行方式。
空间索引与空间连接
空间连接是GIS中非常常见的操作。
例如:
查询每个POI属于哪个行政区。
可以:
SELECT
p.id,
p.name,
d.name
FROM places p
JOIN districts d
ON ST_Within(
p.geom,
d.geom
);
数据量较大时:
places
+
districts
↓
Spatial Index
↓
候选空间对象
↓
ST_Within
↓
空间连接结果
空间索引对于这类操作非常重要。
空间索引与最近邻查询
PostGIS还支持利用GiST进行最近邻查询。
例如:
SELECT
id,
name
FROM places
ORDER BY geom <-> ST_SetSRID(
ST_Point(116.397, 39.908),
4326
)
LIMIT 10;
其中:
<->
是PostGIS中常用于距离排序的空间操作符。
可以用于:
最近商店
最近医院
最近设备
最近道路
最近POI
典型流程:
用户位置
↓
空间索引
↓
最近对象
↓
LIMIT
↓
返回结果
Spatial Index与KNN
KNN表示:
K-Nearest Neighbor
即:
K个最近邻对象。
例如:
SELECT *
FROM places
ORDER BY geom <-> :point
LIMIT 5;
表示查找:
距离point最近的5个对象
这在LBS系统中非常常见。
空间索引的典型应用
附近搜索
用户
↓
500米
↓
附近商店
行政区查询
Point
↓
属于哪个Polygon
道路查询
道路
↓
经过哪些区域
服务范围
医院
↓
Buffer
↓
服务区域
地图查询
当前地图视野
↓
Bounding Box
↓
查询范围内Feature
WebGIS
地图缩放
↓
当前Extent
↓
API
↓
PostGIS
↓
Spatial Index
↓
GeoJSON
↓
地图显示
WebGIS中的空间索引
WebGIS经常按照地图当前范围查询数据。
例如:
地图当前范围:
minX
minY
maxX
maxY
可以构造一个Polygon:
Bounding Box
↓
PostGIS
↓
ST_Intersects
↓
Spatial Index
↓
返回当前地图范围内的数据
这对于:
OpenLayers
Leaflet
MapLibre
Cesium
等地图应用都非常常见。
空间索引与海量数据
假设数据库有:
1000万
条POI。
用户查询:
某个城市范围内的POI
没有空间索引:
1000万
↓
逐条判断
↓
大量计算
有空间索引:
1000万
↓
GiST
↓
空间范围筛选
↓
少量候选数据
↓
精确判断
空间索引的价值会随着数据规模增长越来越明显。
空间索引的维护
创建索引:
CREATE INDEX idx_places_geom
ON places
USING GIST (geom);
删除索引:
DROP INDEX idx_places_geom;
重新创建:
REINDEX INDEX idx_places_geom;
查看表索引:
SELECT
indexname,
indexdef
FROM pg_indexes
WHERE tablename = 'places';
一个完整示例
创建数据表:
CREATE TABLE shops (
id SERIAL PRIMARY KEY,
name TEXT,
geom geometry(Point, 4326)
);
创建空间索引:
CREATE INDEX idx_shops_geom
ON shops
USING GIST (geom);
插入数据:
INSERT INTO shops (name, geom)
VALUES
(
'商店A',
ST_SetSRID(
ST_Point(116.397, 39.908),
4326
)
),
(
'商店B',
ST_SetSRID(
ST_Point(116.405, 39.915),
4326
)
);
查询空间范围:
SELECT *
FROM shops
WHERE ST_Intersects(
geom,
ST_MakeEnvelope(
116.38,
39.89,
116.42,
39.93,
4326
)
);
查询最近对象:
SELECT *
FROM shops
ORDER BY geom <-> ST_SetSRID(
ST_Point(116.397, 39.908),
4326
)
LIMIT 5;
整个过程:
创建Geometry字段
↓
创建GiST空间索引
↓
插入空间数据
↓
执行空间查询
↓
数据库使用空间索引
↓
快速获得候选数据
↓
返回最终结果
空间索引常见问题
为什么创建了索引却没有使用
可能原因包括:
数据量太小
查询成本不高
统计信息过期
查询条件不适合索引
函数写法影响索引
空间索引选择性不足
可以使用:
EXPLAIN ANALYZE
查看执行计划。
索引很大怎么办
空间索引本身需要占用磁盘。
可以检查:
SELECT
pg_size_pretty(
pg_relation_size('idx_shops_geom')
);
查看索引大小。
数据更新后查询变慢
可以检查:
ANALYZE shops;
或者:
VACUUM ANALYZE shops;
让PostgreSQL重新获取统计信息。
Spatial Index知识结构
可以这样理解:
Spatial Index
│
├── Bounding Box
│
├── GiST
│
├── Geometry Index
│
├── Geography Index
│
├── Spatial Query
│ ├── ST_Intersects
│ ├── ST_Contains
│ ├── ST_Within
│ ├── ST_DWithin
│ └── KNN
│
├── Query Plan
│ ├── Index Scan
│ ├── Bitmap Index Scan
│ └── Seq Scan
│
└── Optimization
├── EXPLAIN ANALYZE
├── ANALYZE
├── VACUUM
└── 合理建立索引
PostGIS空间索引完整流程
一个典型PostGIS空间查询可以理解为:
用户请求
↓
空间查询条件
↓
PostGIS
↓
GiST Spatial Index
↓
Bounding Box快速筛选
↓
候选Geometry
↓
空间函数精确计算
↓
最终结果
↓
GeoJSON
↓
WebGIS地图
Geometry、Spatial Index与Spatial Function
这三个概念需要区分:
Geometry
↓
保存空间数据
Spatial Index
↓
快速找到可能相关的数据
Spatial Function
↓
精确计算空间关系
例如:
Geometry
↓
GiST
↓
ST_Intersects
↓
结果
可以记住:
Geometry负责描述空间对象,Spatial Index负责快速查找,Spatial Function负责空间计算。
总结
PostGIS Spatial Index是PostGIS高性能空间查询的重要基础。
最常见的空间索引创建方式是:
CREATE INDEX idx_geom
ON table_name
USING GIST (geom);
其中:
GIST
↓
PostgreSQL通用索引框架
geom
↓
空间字段
空间索引可以用于加速:
ST_Intersects
ST_Contains
ST_Within
ST_DWithin
最近邻查询
空间连接
地图范围查询
核心原理可以概括为:
大量Geometry
↓
Spatial Index
↓
快速筛选候选对象
↓
空间函数精确计算
↓
最终结果
最重要的几个概念是:
Spatial Index
GiST
Bounding Box
ST_Intersects
ST_Contains
ST_Within
ST_DWithin
KNN
EXPLAIN ANALYZE
最终可以记住:
PostGIS空间索引不是用来替代空间计算,而是用来快速减少需要进行空间计算的数据量。
相关工具
使用 IYATools 在线工具快速处理 GIS 数据