在低空飞服项目开发中,轨迹数据量通常非常大,一个飞行器一次飞行就可能产生数万条轨迹点。如果直接返回给前端,地图渲染会卡顿,因此必须做 抽稀(down sampling)。
很多同学第一反应是:在 SQL 中抽稀 。
例如经典写法:
SELECT *
FROM (
SELECT t.*, ROW_NUMBER() OVER(ORDER BY update_time) AS rn
FROM th_track t
WHERE callsign = #{callsign}
) tmp
WHERE rn % 10 = 0
ORDER BY update_time
看上去减少了返回数据量,但实际是否真的减少了数据库压力?
查询真的会更快吗?
本文以实际项目中的 56800 条轨迹数据规模 进行分析。
❗ 结论先行:这种 SQL 抽稀方式不会减少数据库查询耗时
原因很简单:
数据库仍然必须执行:
-
全表扫描(56800 行)
-
ORDER BY 排序(消耗 CPU)
-
ROW_NUMBER() 窗口函数计算(消耗更多 CPU)
-
最后才过滤掉 90% 的数据
也就是说:
你只减少了前端接收的数据量,但数据库的工作量反而变大了。
特别是在 DM8 / DM7 上,窗口函数性能并不算强,这类 SQL 抽稀往往比简单查询更慢。
📊 56800 行轨迹数据的规模分析
轨迹表 th_track 一般包含:
-
经纬度
-
高度
-
航向
-
速度
-
UAV 状态
-
JSON 信号包
-
扩展字段
如果 SELECT *,每条记录约 0.3KB~1.2KB。
56800 条数据约:
约 20MB ~ 60MB I/O 扫描量
这种 I/O 才是数据库最耗时的部分,
抽稀并不能减少这部分的消耗。
🚀 那应该怎么优化?
为了提升整体性能,我们必须明确目标:
| 层级 | 目标 |
|---|---|
| 数据库 | 尽可能减少扫描和排序 |
| 后端 | 控制前端数据量 |
| 前端 | 不卡顿、不掉帧 |
下面是几个行之有效的优化方案。
⭐ 方案 1:抽秒式 SQL 抽稀(数据库真正减少扫描量)【推荐】
如果 update_time 较规律(精确到秒),
可以用"按秒取样"代替窗口函数:
SELECT id, lon, lat, altitude, update_time
FROM th_track
WHERE callsign = #{callsign}
AND MOD(EXTRACT(SECOND FROM update_time), 10) = 0
ORDER BY update_time
优势:
-
不需要排序
-
不需要窗口函数
-
扫描量明显减少
-
DM 数据库执行极快
这是 DM 的最佳实践之一。
⭐ 方案 2:只查询必要字段(最有效)
替换 SELECT *:
SELECT id, lon, lat, altitude, speed, update_time
FROM th_track
WHERE callsign = #{callsign}
ORDER BY update_time
好处:
数据从 60MB → 10MB
DB I/O 直接降低 3~8 倍
这是所有优化中 影响最大 的。
⭐ 方案 3:Java 层抽稀(最稳健,推荐给 DM)
数据库只管查,不管抽稀:
if (list.size() > 20000) {
List<Track> slim = new ArrayList<>();
for (int i = 0; i < list.size(); i += interval) {
slim.add(list.get(i));
}
list = slim;
}
特点:
-
不给数据库增加额外负担
-
控制逻辑更灵活
-
性能受 JVM 控制,易评估
-
前端数据量仍然可控
对于 56800 条数据,Java 抽稀耗时只有 1~3ms。
⭐ 方案 4:SQL + FETCH 限流(保障数据库不炸)
SELECT t.*, ROW_NUMBER() OVER(ORDER BY update_time) AS rn
FROM th_track t
WHERE callsign = #{callsign}
FETCH FIRST 20000 ROWS ONLY
效果:
-
限制最大扫描行数
-
防止异常轨迹量导致数据库卡顿
不过仍然属于"治标不治本"。
🔥 最佳策略(推荐优先级)
① 数据库减负 > ② 后端控制 > ③ 前端不卡
最终最佳组合:
✔ 1. 不用窗口函数抽稀
(对 DM 性能不友好)
✔ 2. 不用 SELECT *
(避免大量字段 I/O)
✔ 3. 使用"按秒抽稀"或 Java 抽稀
(稳定、高效、无副作用)
✔ 4. back-end 抽稀控制最终返回数据量
(例如不超过 5000 点)
🎯 最后总结(非常重要)
| 方式 | 是否减轻 DB 压力 | 是否减少前端数据 | 是否推荐 |
|---|---|---|---|
| 窗口函数 SQL 抽稀 | ❌ 否,反而更慢 | ✔ 是 | ⚠️ 不推荐 |
| 按秒抽稀 SQL | ✔ 大幅降低 | ✔ 是 | ⭐ 强烈推荐 |
| Java 抽稀 | ✔ 不增加 DB 负担 | ✔ 是 | ⭐ 强烈推荐 |
| LIMIT 限流 | ✔ 避免爆表 | ➖ 一般 | 可选 |