应使用 point 类型存储经纬度:支持 spatial 索引,配合 st_distance_sphere() 可高效、准确计算球面距离;必须指定 srid 4326,插入用 st_pointfromtext('point(lng lat)', 4326),查询需用空间函数而非 abs 差值。

用 POINT 类型存经纬度,别用两个 DOUBLE
直接存 lat 和 lng 字段看着简单,但查附近线路、算距离、做范围筛选时会非常痛苦——没空间索引、函数慢、SQL 写起来反直觉。
MySQL 5.7+ 原生支持地理类型,POINT 是最合适的:它能建 SPATIAL 索引,配合 ST_Distance_Sphere() 算球面距离又准又快。
-
POINT必须用SRID 4326(WGS84 坐标系),否则ST_Distance_Sphere()会返回 0 或报错 - 插入时用
ST_PointFromText('POINT(116.404 39.915)', 4326),不能直接写字符串或数组 - 字段定义示例:
location POINT SRID 4326 NOT NULL,记得加SPATIAL INDEX(location)
查“50 公里内出发地为北京的线路”怎么写 SQL
核心是用 ST_Distance_Sphere() 比较,不是 ABS(lat - ?) 这类粗略估算——后者在高纬度偏差极大,且无法走空间索引。
实际要两步:先用 MBRContains() 快速过滤矩形范围(走索引),再用精确距离函数二次筛选(不走索引但数据量已大幅减少)。
- 别单独依赖
ST_Distance_Sphere()在大表上WHERE,会全表扫描 - 正确写法示例:
SELECT * FROM routes WHERE MBRContains( ST_Buffer(ST_PointFromText('POINT(116.404 39.915)', 4326), 0.5), location ) AND ST_Distance_Sphere(location, ST_PointFromText('POINT(116.404 39.915)', 4326)) <= 50000; -
ST_Buffer(..., 0.5)的 0.5 单位是“度”,对应约 ±55 公里(赤道附近),足够兜住 50km 圆形范围;高纬度可微调,但没必要精确到小数点后三位
PHP/Python 插入坐标时常见报错 Cannot get geometry object from data you send to the GEOMETRY field
这错误基本等于你传了个空值、格式错的字符串,或者忘了 SRID。
- PHP PDO 中,必须用
ST_GeomFromText('POINT(116.404 39.915)', 4326)包一层再插入,不能直接 bind['116.404', '39.915'] - Python MySQLdb / PyMySQL 同理,字符串拼接或参数化都要确保完整函数调用,例如:
"INSERT INTO routes (location) VALUES (ST_PointFromText(%s, 4326))",然后传'POINT(116.404 39.915)' - 如果前端传的是
{lat: 39.915, lng: 116.404},后端拼字符串前务必检查字段是否存在、是否为数字,空值会导致整个POINT构造失败
线路含多个坐标点(如途经城市)怎么存
单个 POINT 只能存一个位置。如果要表示整条线路轨迹或多个停靠点,LINESTRING 更合适,但要注意查询逻辑完全不同。
- 存为
LINESTRING:用ST_LineFromText('LINESTRING(116.404 39.915,116.300 39.950,116.200 40.000)', 4326) - 查“经过某城市的线路”不能用
ST_Within(),得用ST_Intersects()判断线与点是否相交(注意:浮点精度下点几乎不可能严格在线上,建议用ST_Distance() - 如果只是存几个关键节点(起点、终点、1–2 个中转),拆成多个字段(
start_point,end_point,via_point1)反而更简单、易索引、好查询
实际用下来,POINT + SPATIAL 索引 + ST_Distance_Sphere() 组合已经覆盖 90% 的旅游线路地理查询场景。真正麻烦的是坐标来源质量——用户手输、第三方 API 返回、地图点击获取,三者精度和坐标系可能不一致,这个得在入库前做校验,而不是靠数据库兜底。










