NoSQL 常见反模式是什么
多数线上问题来自几类反复出现的反模式:文档内无界增长的数组、未锚定的正则全扫、把搜索库当主存储、深分页、单调分片键造成写热点,以及集合上堆积过量索引拖慢写入。识别手段是定期清理查询清单与观察数据增长趋势,而不是等出故障再补。这些反模式共同的根源是违背了引擎的设计假设,理解存储与索引机制后再对照自己的用法,问题往往一眼可见。
基本信息
| 无界数组 | 文档膨胀、写放大,最终撞上文档大小上限 |
|---|---|
| 未锚定正则 | 全集合扫描,CPU 占用高 |
| 搜索库当主库 | 刷新、合并与恢复语义不适合交易数据 |
| 深分页 | skip 十万时每页越翻越慢 |
| 单调分片键 | 写热点集中在单个分片 |
| 过量索引 | 写入放大、内存占用高 |
反模式与修正
| 反模式 | 后果 | 修正方向 |
|---|---|---|
| 文档内无界数组 | 文档膨胀、写放大、达到大小上限 | 拆子集合或按时间窗分桶 |
| 未锚定正则 | 全集合扫描,CPU 高 | 前缀锚定或改用全文检索 |
| 把搜索库当主库 | 刷新与恢复语义不适合交易数据 | 主存储用数据库,搜索库做检索副本 |
| 深分页 skip 十万 | 每页越翻越慢 | 键集或游标分页 |
| 单调键做分片键 | 写热点集中在单个分片 | 散列分片键或复合键 |
| 集合上建十几个索引 | 写入放大、内存占用高 | 按查询清单裁剪与合并索引 |
典型使用场景
- 建模评审:新增数组字段前先问它会增长到多大
- 查询审计:把线上真实查询按模式归类,找全表扫描来源
- 索引治理:定期清理未被使用的索引
- 分页改造:把深分页接口换成游标翻页
- 增长监控:对文档平均大小与集合数据量做趋势告警
无界数组的改造示意
无界数组的改造示意
// 反模式:events 数组无限追加
// { _id: 1, events: [ ... 长期增长 ... ] }
// 修正:拆成子集合,按 (ownerId, ts) 建索引
db.user_events.insertOne({ ownerId: 1, ts: new Date(), type: "login" })
db.user_events.createIndex({ ownerId: 1, ts: -1 }) // 便于按时间倒序查最近事件反模式的共同点是违背引擎的设计假设:先理解存储与索引机制,再对照自己的查询与增长模式排查,比逐条试错快得多。
用短生命周期的正则查询掩盖建模问题最危险:数据量上涨后扫描成本成倍增加,而查询写法看起来完全正常。
索引不是越多越好,每个索引都要在写入时维护,按真实查询清单保留必要的那几个即可。