ST_Contains等空间查询慢的主因是未正确使用空间索引;需为geom字段创建GIST索引,确保坐标系统一、避免ST_Transform等导致索引失效的操作,ST_DWithin和ST_Intersects可走索引而ST_Distance单独使用不可。

为什么 ST_Contains 慢得像在查纸质地图
地理查询变慢,往往不是数据量大,而是没用对空间索引。PostGIS 里直接写 WHERE ST_DWithin(geom, ST_Point(lon, lat), 1000) 却没给 geom 字段建 GIST 索引,相当于每次全表扫描坐标点。R-Tree(PostGIS 的 GIST 实现)本质是把二维空间划分成嵌套矩形,让查询能快速跳过无关区域。但如果你的查询条件里混用了非空间函数——比如先 ST_Transform 再比较,索引就完全失效。
- 必须确保过滤字段(如
geom)上建了CREATE INDEX idx_locations_geom ON locations USING GIST (geom); -
ST_DWithin和ST_Intersects能走索引;ST_Distance单独用则不能 - 坐标系要统一:表中
geom是EPSG:4326,查询点也得用ST_SetSRID(ST_MakePoint(lon, lat), 4326),否则隐式转换会绕过索引
GeoHash 字符串能当主键用吗
能存、能查、能排序,但不能替代空间索引。GeoHash 把经纬度编码成字符串(如 "w33f"),前缀相同表示位置相近,适合做缓存分片或粗粒度筛选。但它有“边界断裂”问题:两个相邻格子可能哈希值天差地别(比如 "w33f" 和 "w33g" 实际挨着,但 "w33z" 和 "w340" 就是跨纬度断层),导致范围查询漏数据。
- 只适合“附近 N 个点”这类容忍误差的场景,别用来做精确围栏判断
- 用
geohash_encode(lat, lon, precision)生成时,精度选 6–8 位(对应 ~100m–1km)较平衡;超过 9 位字符串变长,索引效率反而下降 - MySQL 没原生 GeoHash 函数,得靠
ST_GeoHash()(5.7+)或自定义函数;PostgreSQL 需装postgis扩展后才支持ST_GeoHash(geom, precision)
R-Tree 索引在 MySQL 和 PostGIS 里的行为差异
MySQL 的 SPATIAL 索引底层是 R-Tree,但只支持 MBR(最小边界矩形)函数,比如 MBRContains()、MBRIntersects();它不理解真实几何形状,圆、多边形都会被拍平成矩形再比——所以一个细长 L 形区域,可能因 MBR 过大而召回大量误匹配点。
- PostGIS 的
GIST索引支持真实几何运算(ST_Within、ST_Covers),精度高,但构建和维护成本略高 - MySQL 8.0+ 支持
ST_DistanceSphere(),但该函数无法利用SPATIAL索引,必须配合MBR函数先缩小范围 - 如果业务只要“1km 内”,优先用
MBRWithin(geom, ST_Buffer(ST_Point(x,y), 0.01))(注意单位是度,需换算)而不是纯距离函数
查“周边 5km 商家”时,为什么加了索引还慢
常见陷阱是把计算逻辑塞进 WHERE 条件里,比如 WHERE ST_Distance(geom, ST_Point(:lon, :lat)) 。这个写法强制对每行都算一次球面距离,索引完全无用。正确做法是先用索引圈出候选集,再精细过滤。
- PostGIS 推荐写法:
WHERE ST_DWithin(geom, ST_SetSRID(ST_MakePoint(:lon, :lat), 4326), 5000, true)(true表示用米为单位,自动选球面计算) - MySQL 必须两步:
WHERE MBRWithin(geom, ST_Expand(ST_Point(:lon, :lat), 0.05)) AND ST_DistanceSphere(geom, ST_Point(:lon, :lat)) - 别忽略
VACUUM ANALYZE(PostgreSQL)或ANALYZE TABLE(MySQL):统计信息过期会导致查询计划选错索引
真正难的是权衡:R-Tree 精确但重,GeoHash 轻快但毛糙。线上服务往往两者并用——用 GeoHash 做第一层路由分流,再用 R-Tree 在局部库内精排。没人会只靠一种索引扛住所有地理查询,就像没人靠一把螺丝刀修完整辆车。










