必须显式声明spatial index,普通index对point字段无效;因point是wkb二进制数据,b-tree按字典序比较而地理坐标无序,仅r-tree空间索引支持多维几何范围划分。

SPATIAL INDEX 必须显式声明,普通 INDEX 完全无效
给 POINT 字段加 INDEX (location) 或用 ALTER TABLE ADD INDEX 添加普通索引,对 ST_Contains、ST_Distance_Sphere 等查询毫无加速作用。MySQL 的 POINT 是 WKB 二进制存储,B-tree 按字典序比较,而经纬度的二进制表示无空间顺序,所以执行 EXPLAIN 一定显示 type: ALL——就是全表扫。
真正起效的只有 SPATIAL INDEX,它底层是 R-tree,专为多维几何划分设计。建表时必须写明:
CREATE TABLE merchants ( id INT PRIMARY KEY, name VARCHAR(100), location POINT NOT NULL SRID 4326, SPATIAL INDEX (location) );
已有表补加,只能用:
ALTER TABLE merchants ADD SPATIAL INDEX (location);
- 字段必须是空间类型(如
POINT、POLYGON),不能是VARCHAR或TEXT - 字段必须带
NOT NULL,否则ADD SPATIAL INDEX会静默失败 - 一张表只能有一个
SPATIAL INDEX(MySQL 硬限制)
ST_Distance_Sphere 查询不走索引?先做 MBR 粗筛
ST_Distance_Sphere(location, ST_PointFromText('POINT(116.397428 39.90923)')) 这种写法永远无法命中 <code>SPATIAL INDEX,因为距离函数本身不支持索引下推。MySQL 的空间索引只加速基于最小边界矩形(MBR)的谓词,比如 MBRContains、MBRWithin。
正确做法是两步:先用 ST_Distance_Sphere 的等效边界框快速过滤,再精确计算。推荐写法:
SELECT * FROM merchants
WHERE MBRContains(
ST_Buffer(ST_PointFromText('POINT(116.397428 39.90923)'), 0.05),
location
)
AND ST_Distance_Sphere(location, ST_PointFromText('POINT(116.397428 39.90923)'))
-
ST_Buffer(..., 0.05)生成近似圆形的矩形包络(单位为度,WGS84 下 0.05° ≈ 5.5km),供 MBR 判断 - 必须把
MBRContains放在WHERE最前面,确保先走索引过滤 - 若查询高频且精度要求不高,可直接用
MBRWithin(location, ...)替代,性能更高
ALTER TABLE ADD SPATIAL INDEX 报 ERROR 1170 怎么办
报错 ERROR 1170 (42000): BLOB/TEXT column 'xxx' used in key specification without a key length 并不是因为你对空间字段操作错了,而是表里存在其他 TEXT 或 BLOB 字段(比如 description TEXT)。MySQL 在建任何索引前会校验整张表结构,遇到未指定前缀长度的 TEXT 字段就直接拒绝。
验证方式:
SHOW COLUMNS FROM merchants WHERE Type LIKE '%text%' OR Type LIKE '%blob%';
解决路径分两种:
- 临时方案:对
TEXT字段加前缀索引,例如ALTER TABLE merchants ADD INDEX idx_desc (description(255)); - 根本解法:升级到 MySQL 8.0.13+,该版本已允许在含
TEXT字段的表上直接建SPATIAL INDEX(前提是空间字段本身合规)
为什么 EXPLAIN 显示 key 为空或 type=ALL
即使建了 SPATIAL INDEX,EXPLAIN 仍显示 key: NULL 或 type: ALL,常见原因有三个:
- 查询中混用了非空间条件且没覆盖,例如
WHERE ST_Within(location, @poly) AND status = 'active',而status没有普通索引,导致优化器放弃使用空间索引 - 函数参数顺序反了:
ST_Contains(point_col, poly_col)是错的,正确是ST_Contains(poly_col, point_col);反过来不仅语法错,还可能让索引失效 - SRID 不一致:插入用
ST_GeomFromText('POINT(...)', 4326),但查询用ST_PointFromText('POINT(...)')(默认 SRID=0),跨 SRID 比较会绕过索引
最稳的验证方式是单独跑一句 EXPLAIN SELECT * FROM merchants WHERE MBRContains(ST_Envelope(@poly), location);,看 key 是否显示你的空间索引名。
空间索引不是“设了就快”,它极度依赖数据质量、函数选型和查询结构。一个 NULL 值、一个没设 SRID 的点、一次参数颠倒,都足以让整个索引形同虚设。











