IndexedDB 在 IM 聊天中高效缓存的关键是“存得准、取得快、同步稳”:按会话+时间建复合主键与索引,用游标分页、后台预加载和轻量裁剪策略实现高性能本地消息管理。

IndexedDB 在 IM 聊天场景中适合做本地消息缓存,但要真正高效,关键不在“存得多”,而在“存得准、取得快、同步稳”——核心是按会话 + 时间范围建索引,配合增量拉取策略与后台预加载机制。
按会话+时间建复合主键与索引
不要用自增数字 ID 作主键。每条消息应以 [sessionId, timestamp, msgId] 组合作为主键(用 IDBKeyRange 查询更精准),同时为 sessionId + timestamp 建唯一索引(如 index_session_time)。这样拉取某会话某时间段的消息时,可直接用 index.openCursor(IDBKeyRange.bound([sid, startTs], [sid, endTs])),避免全表扫描。
- 消息写入前先用
timestamp截断毫秒(如Math.floor(Date.now() / 1000)),减少索引粒度,提升范围查询效率 - 对同一会话的重复消息(如撤回后重发),靠
[sessionId, timestamp, msgId]主键天然去重,无需额外查重逻辑
分页拉取 + 增量游标续传
前端不依赖“页码+大小”,改用“游标式分页”:首次拉取用 openCursor 向前/向后遍历,记录最后一条消息的 主键数组(如 [sid, ts, id])作为下次起始点。服务端也需支持基于客户端传来的游标参数(非 offset)返回下一批数据。
- 向历史滚动(加载更早消息):用
IDBKeyRange.upperBound([sid, lastTs], true)+direction: 'prev' - 向新消息滚动(加载更新消息):用
IDBKeyRange.lowerBound([sid, lastTs])+direction: 'next' - 每次最多取 50 条,避免单次事务过大阻塞 UI;游标值存在内存即可,无需存库
后台静默预加载最近 3 个活跃会话
用户进入聊天列表后,立即在 requestIdleCallback 或 setTimeout(..., 0) 中启动低优先级 IndexedDB 读取,查出最近访问的 3 个会话的最新 20 条消息(按 sessionId 索引 + timestamp 降序),存入内存缓存(如 Map<sessionId, Message[]>)。这样点击任一会话时,界面可秒级渲染,再异步补全剩余历史。
立即学习“Java免费学习笔记(深入)”;
- 预加载用只读事务(
readonly),不影响其他写操作 - 若某会话本地无数据,跳过,不报错;后续点击时再触发首次拉取
- 预加载结果设 5 分钟有效期,超时后下次访问重新查
清理策略:按会话保留最近 N 条 + 按时间保留 M 天
不做全局定期清理,而是在每次写入新消息时,对当前会话做轻量裁剪:先查该会话总条数,若超限(如 > 500 条),则用游标删掉最旧的 50 条;同时定时任务(每天一次)清理所有会话中 timestamp < Date.now() - 30 * 24 * 3600 * 1000 的消息。
- 裁剪用
delete()配合游标,比批量 openCursor + get + delete 更省内存 - 删除操作放在单独事务中,失败不中断主流程,记日志供后续诊断
- 用户手动“清空会话”时,直接调
objectStore.delete(IDBKeyRange.only(sid))(需主键含 sid)


















