云数据库使用模型
云数据库本质是托管在云端的文档型数据库,集合里存文档,每个文档就是一条 JSON 记录。客户端 SDK 和云函数都能读写,但权限模型不一样,这点要先分清。
客户端直接读写适合数据量小、规则简单的场景,安全规则写在数据库权限里;云函数读写适合要校验、要聚合、要跨集合处理的场景,绕过客户端权限限制,逻辑更可控。
很多团队把读写都收到云函数里,客户端只调接口。代价是多一层开发量,换来的是权限、日志、限流都统一在一处。小型项目可以先客户端直连,业务复杂了再收口。
- 批量批量写入与大数据导入。
- 事务多文档一致性与回滚。
- 聚合分组统计与复杂查询。
- 实时watch 监听数据变化。
批量写入与导入
一次性要写入成百上千条数据,一条条 add 会又慢又费配额。云数据库提供批量写入接口,一次调用能写多条,适合初始化数据、迁移数据这类场景。
const cloud = require('wx-server-sdk');
cloud.init();
exports.main = async function (event) {
const db = cloud.database();
const rows = event.rows || [];
const batch = [];
for (let i = 0; i < rows.length; i += 100) {
batch.push(db.collection('orders').add({
data: rows.slice(i, i + 100)
}));
}
const results = await Promise.all(batch);
return { inserted: results.length };
};
批量写入有单次数量上限,超过要分批。分批的时候记得处理部分失败:哪批失败了、成功了几批,都写进日志,方便重试,别整批静默丢弃。
导入还要考虑幂等:给数据带上唯一键,写入前先查重,重复导入才不会把库存、订单这类数据弄花。
事务与数据一致性
扣库存、转账、改订单状态,这类操作要求多文档一起成功或一起失败,云函数里可以用事务接口保证一致性。
const db = cloud.database();
const transaction = await db.startTransaction();
try {
await transaction.collection('stock').doc('sku-001').update({
data: { count: db.command.inc(-1) }
});
await transaction.collection('orders').add({
data: { sku: 'sku-001', amount: 99 }
});
await transaction.commit();
} catch (err) {
await transaction.rollback();
throw err;
}
事务有超时和重试的限制,事务里不要做耗时的外部请求,比如调第三方支付、等用户输入。能放事务里的操作就尽量精简,事务外做准备工作,事务内只做关键更新。
并发扣减要注意:库存这种高频字段,用 db.command.inc 原子自增比读出来再写回去安全得多,事务里也一样。先想清楚并发模型,再决定要不要上事务,别所有接口都套一层。
聚合与复杂查询
统计订单总额、按分类分组、算用户留存,这些不能靠客户端拉全量数据再自己算,要用聚合管道在服务端算好,只把结果返回给客户端。
const result = await db.collection('orders').aggregate()
.match({ status: 'paid' })
.group({
_id: '$category',
total: db.command.aggregate.sum('$amount'),
count: db.command.aggregate.count()
})
.sort({ total: -1 })
.end();
聚合操作放在云函数里跑,客户端直连的聚合权限和性能都不好控制。聚合结果如果变化不频繁,缓存到另一个集合,查询接口直接读缓存,能省大量配额和延迟。
复杂查询的另一个关键是索引。查询字段没建索引,数据一多就慢,控制台会提示慢查询。按查询模式把组合索引建好,查询速度能差出数量级。
实时数据推送
聊天消息、协作文档、订单状态变更,这类要实时刷新的数据,用 watch 监听集合或文档的变化,变化推给前端,比轮询省流量也更及时。
const db = wx.cloud.database();
const watcher = db.collection('messages')
.where({ to: openid })
.watch({
onChange: function (snapshot) {
// snapshot.docs 是最新数据,snapshot.docChanges 是变更明细
renderMessages(snapshot.docs);
},
onError: function (err) {
console.log('watch error', err);
}
});
// 页面卸载时关闭监听
function onUnload() {
watcher.close();
}
watch 有监听数量上限,也要记得在页面卸载时 close,不然会一直占用连接。实时数据要在服务端配合权限规则,只推用户有权限看的数据,别把全表数据推给任何客户端。
实时推送不适合大数据量场景。几十条消息没问题,几万条文档全量推就不现实了,前端要做增量合并、节流渲染,否则页面直接卡死。
性能与配额治理
| 问题 | 现象 | 应对 |
|---|---|---|
| 慢查询 | 接口耗时突增 | 按查询模式补组合索引 |
| 配额超限 | 调用返回配额错误 | 读缓存、合并批量、分页 |
| 数据倾斜 | 单文档过大读写慢 | 拆文档、拆集合归档 |
| 误删风险 | 批量删错数据 | 先备份、软删除标记 |
配额治理的思路是先分清楚读多写多:读多写少的数据上缓存,写多读少的数据做批量合并。控制台有配额用量和调用趋势,定期看一眼,别等触发限流才排查。
数据安全上,云函数里也要做输入校验和越权检查:openid 要和服务端会话核对,批量接口限制单次条数,删除操作留操作日志,重要数据定时导出备份。
落地清单
- 批量写入分批执行,记录部分失败可重试。
- 事务只放关键更新,外部请求不进事务。
- 聚合放云函数,结果缓存到集合复用。
- watch 记得关闭,实时数据配合权限规则。
- 按读多写多分治,配额与索引定期治理。
数据层稳了,上层业务才敢往上堆。批量、事务、实时,一个都不能少。