普通B-tree索引对POINT字段完全无效,因其底层WKB二进制无空间序;必须用SPATIAL INDEX(R-tree)加速ST_*函数,且需满足空间类型、NOT NULL、InnoDB≥5.7.5等硬性前提。

不加 SPATIAL INDEX,所有 ST_Contains、ST_Distance_Sphere 类查询基本等于全表扫描——这是最常被忽略、代价最高的错误。
为什么普通 INDEX 对 POINT 字段完全无效
MySQL 的 POINT 字段底层是 WKB 二进制数据,B-tree 索引依赖字典序比较,而经纬度坐标的二进制表示毫无空间顺序可言。哪怕你给 location 加了 INDEX(location),EXPLAIN 里依然会显示 type: ALL。
R-tree 空间索引才是专为多维几何对象设计的:它把地理空间切分成嵌套矩形块(Minimum Bounding Rectangle, MBR),查询时能快速跳过明显不重叠的区域。
- 常见错误:建表时只写
INDEX(location),没写SPATIAL INDEX(location) - 常见错误:用
ALTER TABLE ADD INDEX添加普通索引,误以为能加速地理查询 - 常见错误:字段类型是
GEOMETRY但没加NOT NULL,导致SPATIAL INDEX创建失败(MySQL 强制要求)
创建 SPATIAL INDEX 的硬性前提
三要素缺一不可,否则报错或静默失效:
- 字段必须是空间类型(
POINT、POLYGON、GEOMETRY等),不能是VARCHAR或TEXT;否则报错ERROR 1170 (42000): BLOB/TEXT column 'xxx' used in key specification without a key length - 字段必须声明
NOT NULL(SPATIAL INDEX不接受 NULL 值) - 必须显式使用
SPATIAL INDEX关键字,不能省略或替换成INDEX
正确建表示例:
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);
注意:一张表只能有一个 SPATIAL INDEX(MySQL 内核限制)。
查询语句必须配合空间函数才能触发索引
光有索引不够,SQL 写法决定是否真用得上。以下写法能走索引:
-
WHERE ST_Contains(boundary, location)(boundary是多边形,location是点) WHERE ST_Distance_Sphere(location, ST_PointFromText('POINT(-74.0060 40.7128)', 4326)) < 1000-
WHERE MBRContains(boundary, location)(更快但粗略,基于外包矩形)
这些写法会失效(强制全表扫):
-
WHERE ST_X(location) BETWEEN -75 AND -73(拆解坐标后走不了 R-tree) -
WHERE location = ST_PointFromText(...)(等值比较对二进制 WKB 无意义) - 在
SELECT列表里用ST_AsText(location)不影响索引,但别在WHERE里对函数结果做条件
插入数据也必须规范:location 字段必须用 ST_GeomFromText('POINT(经度 纬度)', 4326) 或 ST_PointFromText() 构造;直接插字符串(如 'POINT(120 30)')会导致存成 NULL,索引彻底失效。
SRID 和坐标系不是可选项,而是精度前提
SRID 4326(WGS84 经纬度)是绝大多数场景的默认选择,但必须显式声明。漏掉 SRID 4326 会导致:
-
ST_Distance_Sphere返回 0 或报错(该函数强制要求球面坐标系) -
ST_Contains在跨坐标系时行为不可预测 - 不同 SRID 的数据无法安全参与同一空间计算
注意 POINT 的 WKT 顺序是 POINT(经度 纬度),和很多前端地图库(如 Leaflet)习惯相反,填反了会导致位置偏移几千公里。
真正容易被忽略的是:空间索引本身不解决“高并发小范围查询”的压力问题——它只减少扫描行数。如果单次查出几万点再算距离,瓶颈就转移到 CPU。这时得结合预计算(如 lat_floor/lon_floor 分桶)或应用层缓存,而不是只盯着索引。



















