优化 Cloudbase 云数据库查询性能
一句话定义:Cloudbase 的文档型数据库底层基于 MongoDB 协议,这篇按定位 → 加索引 → 调结构 → 改翻页的顺序,把常见的慢查询场景一遍过,讲清每一步的机制(为什么这么做有效)和它在数据量增长时的极限,不编具体毫秒数字。
预计耗时:35 分钟 | 难度:进阶
适用场景
- 适用:已经能正常读写数据库(完成 add-database-wechat-miniprogram 即可),开始关心查询性能
- 适用:数据量从几千条涨到十几万 / 百万级,查询变慢
- 适用:做日报 / 列表 / 搜索这类典型读多写少场景
- 不适用:数据量超过千万级、跨集合 join 复杂的强关系业务。文档型数据库不擅长这个量级的关系运算,见第七步「何时换关系数据库」
- 不适用:写性能是瓶颈的场景(本篇重点讨论读)。索引加多了反而拖慢写,这点第二步会展开
环境要求
| 依赖 | 版本 |
|---|---|
| Cloudbase 环境 | 任意,文档型数据库默认就支持 |
@cloudbase/js-sdk 或 @cloudbase/node-sdk | 任意当前版本 |
| 控制台权限 | 「文档型数据库 → 集合管理 / 索引管理 / 慢查询」可访问 |
第一步:先找慢查询,再决定优化谁
性能优化的第一原则:先证明慢,再说优化。盲目加索引经常加错地方,反而拖慢写入。
定位入口三个:
1. 控制台慢查询日志
控制台 → 环境总览 → 高级 → CLS 日志(确保已开启)→ 搜索 module:database AND eventType:MongoSlowQuery,能看到所有耗时偏高的查询条件、命中索引情况、扫描文档数。
如果偏好用 SDK 拉取:
const CloudBase = require('@cloudbase/manager-node');
const manager = CloudBase.init({ secretId, secretKey, envId });
const res = await manager.log.searchClsLog({
queryString: 'module:database AND eventType:MongoSlowQuery',
StartTime: '2024-04-01 00:00:00',
EndTime: '2024-04-01 23:59:59',
Limit: 100,
Sort: 'desc',
});
for (const log of res.Results || []) {
console.log(log.Timestamp, log.Content);
}
完整接口签名:searchClsLog。
2. 看具体调用的耗时
在云函数里 Date.now() 卡一下时间窗口,把 RequestId 也打日志。返回慢了去查这个 RequestId 对应的具体执行情况。
3. 控制台「监控告警」
控制台 → 数据库 → 监控告警,有读 / 写延迟、慢查询占比、连接数等指标,出全局趋势图比单条样本更能看到问题面。
慢的判断标准:不要纠结「多少 ms 算慢」。看相对值就行 — 如果一类查询的耗时是同集合大多数查询的 5-10 倍,或者扫描文档数远大于返回文档数(比如返回 20 条扫了 10 万条),就值得看。
第二步:加索引
文档型数据库在没有索引的字段上做 where 是「全表扫描」(collection scan),时间随集合大小线性增长。建索引后能把这个降到对数级别(B-Tree / 类似结构上的查找)。
在控制台加索引
控制台 → 数据库 → 集合 → 索引管理 → 新建索引:
- 单字段索引:针对一个字段查询 / 排序。比如
where({status: 'pending'}),给status加单字段索引 - 组合索引:多字段联合查询,如
where({userId, status}).orderBy('createdAt', 'desc'),加(userId, status, createdAt)三字段组合索引 - 唯一索引:除了加速,还能在写入时强制唯一性约束
完整规则见 data-index。
组合索引的「最左前缀」原则
组合索引 (a, b, c) 能命中:
| 查询条件 | 能否命中 |
|---|---|
where({a: x}) | 命中 |
where({a: x, b: y}) | 命中 |
where({a: x, b: y, c: z}) | 命中 |
where({b: y}) | 不命中 |
where({a: x, c: z}) | 部分命中(只走 a) |
机制:索引在底层是按字段顺序拼接的有序结构,跳过前面的字段就没法定位。
设计组合索引时把等值条件放在前,范围条件放在后(gt / lt / in):
// 推荐:userId 等值在前,createdAt 范围在后
await db.collection('orders')
.where({
userId: 'u1', // 等值
createdAt: db.command.gte(yesterday), // 范围
})
.orderBy('createdAt', 'desc')
.get();
// 索引:(userId, createdAt)
索引不是越多越好
- 写入时每个索引都要更新,索引数量直接影响写延迟
- 索引会占空间,内存压力大时整体性能下降
- Cloudbase 文档建议单集合不超过 20 个索引,见 data-index 索引数量限制一节
- 没在用的索引及时删掉。控制台「索引管理」能看每个索引的命中频率
排序方向也要对上
组合索引 (age: 升序, score: 降序) 能命中:
orderBy('age', 'asc').orderBy('score', 'desc')(完全一致)orderBy('age', 'desc').orderBy('score', 'asc')(完全反向,索引可反向遍历)