必须用 updateone + $set + $currentdate 原子更新司机位置,仅修改 location 和自动生成 updatedat,配合 upsert 和 2dsphere 索引,结合 $geonear 实现高效派单。

用 updateOne + $set + $currentDate 更新司机位置,别用 save 或全量替换
直接覆盖整个文档会丢失并发写入的其他字段(比如接单状态、服务评分),而且无法自动记录最后更新时间。必须用原子更新操作。
实操建议:
- 司机上线/移动时,只更新
location字段(GeoJSONPoint格式)和updatedAt - 用
$currentDate: { updatedAt: true }确保时间戳由服务端生成,避免客户端时间不一致 - 带上
upsert: true处理司机首次上报位置的场景,但注意要设好唯一索引(如driverId)防止重复插入 - 示例:
db.drivers.updateOne( { driverId: "drv_abc123" }, { $set: { location: { type: "Point", coordinates: [116.32, 39.98] } }, $currentDate: { updatedAt: true } }, { upsert: true } )
给 location 字段建 2dsphere 索引,否则 $near 查询会全表扫
没建对索引,db.orders.aggregate([{ $lookup: ... }, { $geoNear: ... }]) 这类派单查询可能从毫秒级退化到秒级,且 CPU 持续飙高。
实操建议:
- 索引必须建在完整 GeoJSON 对象上,不是单独的数组字段:
db.drivers.createIndex({ location: "2dsphere" }) - 如果司机有“服务区域”限制(比如只跑朝阳区),可加复合索引:
db.drivers.createIndex({ serviceArea: 1, location: "2dsphere" }),配合$geoWithin快速过滤 - 删除旧的错误索引(比如对
location.coordinates建的普通索引),它对地理查询无效,还拖慢写入
用 $geoNear 聚合阶段做派单,别在应用层算欧氏距离
把经纬度当平面坐标用勾股定理算距离,5km 以内误差可能达 300 米;跨纬度时更离谱。MongoDB 的 $geoNear 默认用球面几何,结果可靠。
实操建议:
-
$geoNear必须是 pipeline 第一阶段,且只能有一个;需要排序+分页时,跟$limit配合即可 - 设
maxDistance严格限制搜索半径(比如 3000 米),避免返回过多候选司机导致后续逻辑卡顿 - 用
distanceField: "distance"把距离带出来,下游可按距离加权(如距离越近权重越高) - 示例:
db.drivers.aggregate([ { $geoNear: { near: { type: "Point", coordinates: [116.31, 39.99] }, distanceField: "distance", maxDistance: 3000, spherical: true } }, { $match: { status: "available", isOnline: true } }, { $limit: 10 } ])
司机位置更新频次和 TTL 索引要平衡——太勤浪费资源,太久导致派单不准
司机每秒报一次位置,数据库写入压力翻倍,但超过 15 秒未更新的位置,在高峰期可能导致把单派给已驶离的司机。
实操建议:
- 客户端按场景调节频率:静止时 30s 一次,车速 >10km/h 时缩至 5–10s 一次
- 用 TTL 索引自动清理过期数据:
db.drivers.createIndex({ updatedAt: 1 }, { expireAfterSeconds: 60 }),60 秒没更新就认为司机离线 - 不要依赖 TTL 触发“下线”逻辑,应用层仍需监听
updatedAt做实时判断——TTL 删除有延迟,且不可回滚
location 字段的结构一致性、索引是否命中、以及 updatedAt 的时效性,这三个点出问题的概率远高于语法或聚合写法。先盯死它们,再优化算法。











