必须用spatial index,普通b-tree对point无效;r-tree通过mbr支持空间查询;建表需显式指定srid 4326和spatial index;st_distance_sphere()不走索引,应改用st_dwithin()(仅限平面坐标)。

必须用 SPATIAL INDEX,普通 B-Tree 对 POINT 完全无效
MySQL 的 POINT 字段底层是 WKB 二进制格式,B-Tree 按字典序比较,而经纬度的二进制表示毫无地理顺序——加了普通 INDEX 也白加,EXPLAIN 里永远是 type: ALL。R-Tree 是唯一能理解二维空间关系的索引结构,它把地理空间切分成嵌套矩形(MBR),查询时跳过明显不重叠的区域。
建表时必须显式声明:
CREATE TABLE poi ( id INT PRIMARY KEY, name VARCHAR(100), location POINT SRID 4326, SPATIAL INDEX(location) );注意两点:一是
SRID 4326 必须明确指定(WGS84 坐标系),二是 SPATIAL INDEX 不能写成 INDEX 或 KEY,否则 MySQL 不认。查询必须用 MBR 函数触发 R-Tree,ST_Distance_Sphere() 自己不走索引
ST_Distance_Sphere() 是球面距离精算函数,但直到 MySQL 8.0.17 才支持在 WHERE 中用 ST_DWithin() 下推索引——且仅限平面坐标(如 Web Mercator),对真实经纬度(SRID 4326)依然无效。所以你写的 WHERE ST_Distance_Sphere(location, @p) 一定全表扫。
真正能走 R-Tree 的只有 MBR 类函数:
ST_Intersects(location, ST_MakeEnvelope(...))MBRContains(ST_MakeEnvelope(...), location)-
ST_Contains(ST_Buffer(...), location)(注意单位是度,不是米)
ST_MakeEnvelope 的边界必须手工换算,别直接用“5km”
地球是球面,1° 经度的距离随纬度变化:赤道约 111km,北京(纬度≈40°)约 85km;1° 纬度则基本恒定为 111km。所以“附近 5km”不能简单写成 ST_MakeEnvelope(lng-0.05, lat-0.05, lng+0.05, lat+0.05, 4326)。
推荐做法:
- 纬度方向偏移 ≈
5000 / 111000 ≈ 0.045° - 经度方向偏移 ≈
5000 / (111000 * COS(RADIANS(lat))),比如 lat=39.92 时,cos≈0.77,结果≈0.058° - 最终包围盒:
ST_MakeEnvelope(116.48-0.058, 39.92-0.045, 116.48+0.058, 39.92+0.045, 4326)
GeoHash 是更稳的备选,尤其当 R-Tree 表现反常时
R-Tree 在“大范围稀疏分布”或“大量重叠区域”下可能退化——索引节点覆盖太广,实际仍要回表校验大量记录。这时 ST_GeoHash()(MySQL 5.7+ 内置)反而更可靠。
用法:
- 新增字段
geohash CHAR(9),用ST_GeoHash(location, 9)填充(精度 7–9 较平衡) - 给该字段加普通 B-Tree 索引:
INDEX(geohash) - 查“附近”时先取目标点 geohash 前缀(如
'w33f'),再用LIKE 'w33f%'过滤,最后精排
R-Tree 真正起效的关键不在建索引动作本身,而在查询是否严格匹配它的能力边界:它只响应矩形范围判定,不理解球面、不处理距离函数、也不自动做单位换算。任何一步绕开 MBR 逻辑,索引就等于没建。











