一句话:LingCode Cloud 原型迭代很快,在开发期也很宽容——但有几个习惯,决定了你做出来的是一个 demo,还是一个能真正上线的后端。按「一次读一张表」来做数据建模,分页与计数都别全表扫描,为你用来过滤的列加索引,在 anon key 能触达的每个地方打开行级安全,把私有密钥放进保险库、把服务端逻辑放进函数,并守住所在套餐的配额。然后跑一遍上线前检查清单。
这些规则大多源自同一个事实:客户端 SDK 对话的是一层轻量的表级 CRUD 网关,而不是直接连 Postgres。正是这一点,决定了你该如何建模、如何读取,以及安全该放在哪里。下面每一节都是一段简短的为什么,加上具体的操作指引,并附上相关指南链接,方便你深入。
运行时数据 API 一次只操作一张表——不支持 JOIN,不支持 upsert,每页最多 200 行。如果你设计了一套规范化的 schema,再试图在客户端用扇出请求把它拼回去,那么每个界面都会因为每个关联而多花一次往返。请改为按你的读取路径来建模。
select()。JOIN 在 Postgres 里执行,客户端看到的是一个扁平结果。bigint generated always as identity 或 uuid——永远不要用可变的自然键。user_id 列,这样 RLS 才有可依据的键。-- 一个读取路径视图:在数据库里 join 一次,像查表一样 select() 它
create view order_lines_expanded as
select l.id, l.order_id, l.qty, p.name as product_name, p.price
from order_lines l
join products p on p.id = l.product_id;
完整的查询构造器接口,以及「不支持 JOIN/upsert」这条规则,见数据库指南。
select() 最多返回 200 行。早点用上分页——它不是一项优化,而是一道上限。按一个已建索引的列排序,并用 .limit() 和 .range() 翻页。
const { data } = await lingcode
.from('posts')
.order('created_at', { ascending: false }) // 已建索引的列
.range(0, 49) // 前 50 条
.select();
计数才是陷阱。不要靠把每一页都取回来再把长度加起来来计数——那等于在每个视图上对整张表做一次 O(n) 扫描。应该这样:
select() 那一行聚合结果。关于过滤、排序和范围的更多内容,见数据库指南。
没有索引的过滤或排序会扫描整张表。这在 50 行时看不出来,到了 50,000 行就是致命的。在迁移里,为每一个会在规模上用来过滤或排序的列,以及每一个外键列,都加上索引。
create index if not exists idx_posts_author on posts (author_id);
create index if not exists idx_posts_created_at on posts (created_at desc);
pgvector 列在表变大后需要一个向量索引——见向量检索指南。而且你不必靠手工去找这些:advisors 扫描器会替你标出缺失的索引和未建索引的外键。上线前先跑一遍——见用 advisors 扫描后端。迁移的基础知识见数据库指南。
anon key 会随你的浏览器打包产物一起发出去,所以服务端才是安全真正发生的唯一地方。把数据库当成默认怀有敌意的,让 RLS 对每一次读和写都把关。
完整的策略模型见安全指南。
如果一次调用需要私有密钥,它就不能跑在浏览器里——任何打开开发者工具的人都能看到那个密钥。厂商 API 调用以及任何携带密钥的逻辑,都该放进读取 ctx.secrets 的函数里:
// 函数 —— 密钥永远不会到达客户端
export default async (req, ctx) => {
const key = ctx.secrets.STRIPE_SECRET_KEY;
// ...在服务端调用厂商,只返回安全的部分...
};
不过函数是短生命周期的。有两种情况应该放进托管的 Worker 路由,而不是函数:
每个套餐都有上限,在生产环境里撞上某条上限,比提前规划要难受得多。上线前先弄清你的上限:
3s / 10s / 30s。后端会在存储达到 80% 和 95% 时发出告警——盯住这些警告,别等到被硬性截停。又因为函数是短生命周期的(≤30s),任何更长的任务都必须卸载出去——拆分它、排队它,或者把它挪到托管的 Worker 上。各套餐上限列在定价页。
环境之间的 schema 漂移,是「在我的后端上能跑」这句话背后悄无声息的杀手。让迁移成为 schema 变更的唯一途径——永远不要临时手点着改结构。
apply_migration 执行。create table if not exists、create index if not exists——这样重复执行才安全。create table if not exists invites (
id uuid primary key default gen_random_uuid(),
email text not null,
created_at timestamptz not null default now()
);
create index if not exists idx_invites_email on invites (email);
迁移的具体机制见数据库指南。
三个习惯能在不增加额外基础设施的前提下让应用保持响应迅速:
subscribe() 通过 SSE 把行变更推送过来,而不是定时去重新拉取——更少的请求,更低的延迟。见实时指南。在把一个应用切到生产之前,确认: