必须使用SPATIAL INDEX,普通B+Tree索引对POINT字段无效;空间查询需R-tree结构的SPATIAL INDEX配合MBR粗筛(如ST_Contains、ST_DWithin)才能生效,仅建索引不改写查询仍全表扫描。

必须用 SPATIAL INDEX,普通索引对 POINT 字段完全无效
给 POINT 字段加 INDEX(B+Tree 类型)毫无作用。MySQL 会把它当二进制 WKB 数据按字典序比较,而经纬度点的 WKB 表示在字典序上完全无地理意义——EXPLAIN 显示 type: ALL 就是铁证。
空间查询必须依赖 R-tree 结构的 SPATIAL INDEX,它能把地理空间递归划分成嵌套矩形块,快速跳过不重叠区域。
- 建表时直接声明:
SPATIAL INDEX (location),且字段必须NOT NULL - 已有表补加:
ALTER TABLE merchants ADD SPATIAL INDEX (location) - 一张表只能有一个
SPATIAL INDEX(MySQL 硬限制,不是你写错了) - 不能在
DOUBLE、VARCHAR或TEXT字段上创建,会报错ERROR 1170
查询写法决定索引是否真正生效
只建了 SPATIAL INDEX 不代表查询就快。MySQL 无法直接用空间索引加速 ST_Distance_Sphere() 这类函数的全量计算,必须先做边界框(MBR)粗筛,再做精确过滤。
错误写法(全表扫描):SELECT * FROM merchants WHERE ST_Distance_Sphere(location, ST_PointFromText('POINT(116.397428 39.90923)'))
正确写法(两步走):SELECT * FROM merchants WHERE MBRContains(LineString(Point(116.397428 - 0.045, 39.90923 - 0.045), Point(116.397428 + 0.045, 39.90923 + 0.045)), location) AND ST_Distance_Sphere(location, ST_PointFromText('POINT(116.397428 39.90923)'))
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
-
MBRContains()利用空间索引快速排除明显远的点(经度±0.045°、纬度±0.045°约对应 5km 边界框) - 后续
ST_Distance_Sphere()只在粗筛后的少量行上执行,避免全表计算开销 - 边界框范围要略大于目标半径,否则可能漏掉边缘点;太大会降低粗筛效果
POINT 存储 vs DOUBLE 双字段:选型要看查询复杂度
如果业务只要“附近 5km 商家”,且数据量不大(lng DOUBLE 和 lat DOUBLE 加普通 B+Tree 索引反而更轻量、易调试。
但一旦涉及多边形覆盖、路径相交、行政区域聚合等操作,或者单表超 50 万行且高频查“方圆 X 公里”,POINT + SPATIAL INDEX 是唯一能扛住的方案。
-
POINT插入必须用ST_GeomFromText('POINT(lng lat)'),直接插字符串或数字会存成NULL - 经度在前、纬度在后,顺序写反会导致坐标落在地球对面(比如北京变阿根廷)
- 双字段方案若想提速,可对
(lng, lat)建复合索引,但仅对矩形范围查询有效,球面距离仍需 Haversine 公式全量计算
ST_Distance_Sphere() 的单位和精度陷阱
ST_Distance_Sphere() 返回单位是**米**,不是公里。传入 5000 表示 5km,传 5 就是 5 米——这是线上事故高发点。
它默认使用 WGS84 椭球模型,精度足够日常使用(误差
- 确认坐标系一致性:入库前统一转 WGS84,或在查询时用自定义函数校正
- 不要在
WHERE中对location字段用任何函数(如ST_X(location)),这会让空间索引失效 - 如果只需要排序不需精确距离,可用
ST_Distance()(平面欧氏距离),更快但仅适合小范围(
实际部署时最容易被忽略的是:空间索引不支持 ORDER BY 优化。即使你按距离排序,MySQL 仍需把所有匹配行取出后再计算并排序。大结果集下,LIMIT 必须配合边界框使用,否则性能断崖下跌。

















