跳转至

大量设备查询

单元 14|难度:中级|前置:数据库、Redis、在线状态|目标:按列表、统计、导出和实时推送分别设计

先澄清“大量查询”是哪一种

不同需求不能使用同一个方案:

需求 合理方式
页面显示设备列表 数据库分页 + Redis 批量状态
查看当前在线总数 读取预计算统计或集合 cardinality
导出全部设备 创建异步任务,游标/分批读取
分析历史遥测 时序聚合、降采样、异步报表
推送状态变化 事件 + WebSocket/SSE,而非不断全量刷新

页面列表

错误思路:

数据库加载 100,000 台设备
  → Redis 读取 100,000 个状态
  → 应用组装所有对象
  → 返回浏览器

正确思路:

数据库读取一页 100 台设备
  → MGET/HMGET 读取本页状态
  → 应用按 ID 合并
  → 返回 100 条

为什么在应用层合并并不一定浪费内存

应用层确实需要暂存数据库结果、Redis 结果和响应对象,但对象数量由页大小控制。真正危险的不是“应用层组装”,而是:

  • 没有分页;
  • 查询了不需要的字段;
  • value 过大;
  • 多份对象重复拷贝;
  • 并发请求数量失控;
  • 一次批量命令返回过多数据。

深分页

LIMIT 100 OFFSET 1000000 可能越来越慢,因为数据库仍需跳过大量记录。

更适合使用基于稳定排序键的游标分页:

SELECT id, name, model
FROM devices
WHERE tenant_id = :tenant_id
  AND id > :last_id
ORDER BY id
LIMIT 100;

需要为过滤与排序模式设计合适索引。

在线总数

不要为了一个数字装载所有设备。可以:

  • SCARD tenant:{id}:online_devices
  • 读取事件驱动维护的统计缓存;
  • 对允许分钟级延迟的看板使用定时聚合;
  • 定期校准,接受最终一致性。

全量导出

全量导出不是普通同步 HTTP 列表请求:

  1. 创建 export task;
  2. worker 按游标分批读取;
  3. 每批查询必要状态并写入文件流;
  4. 上传对象存储或保存临时文件;
  5. 通知用户下载;
  6. 设置文件过期和权限。

这既控制内存,也避免请求超时。

面试时诚实表达规模

我实际项目没有达到百万设备,但处理大量设备查询时会先区分列表、统计和全量任务。列表采用数据库游标分页,并对本页设备从 Redis 批量读取实时状态;统计读取预计算结果;全量导出则转为异步任务分批处理。这样不会把十万设备一次性加载到应用内存。

自测

  1. 列表、在线总数和全量导出为什么不能使用同一种查询?
  2. MGET 一次读取十万条为什么仍然危险?
  3. OFFSET 深分页为什么会越来越慢?
  4. 全量导出如何控制内存和请求超时?

完成标准

  • 能为四类“大量查询”分别选方案
  • 能解释应用层合并的内存边界
  • 能写出游标分页条件
  • 能设计异步导出流程

下一单元:设备影子与命令控制