神慧
Sun Mar 01 2026 08:00:00 GMT+0800 (China Standard Time)·6 min readSaaS

SaaS 多租户设计的三条底线

店铺/组织维度、权限与数据隔离,是跨境 SaaS 从 Demo 走向可售卖的关键分界。

#SaaS#多租户#PostgreSQL

做 SaaS 时最容易低估的是租户边界。页面先跑起来不难,难的是第二个客户进来以后数据不会串。我们接手过一个跨境 ERP 项目,早期没加 shop_id 过滤,测试环境 A 店铺的订单出现在 B 店铺报表里——这类事故一旦发生在生产,合同和口碑一起赔。Shopify App、独立站 ERP、AI 助手后台,本质都是同一套多租户问题。投资人看 Demo 只看 happy path,真正决定续费的是隔离、权限和审计能不能扛审计。

底线一:租户键无处不在

每一张业务表都要能回答:这行数据属于谁。shop_id 或 org_id 应出现在查询条件的第一位,而不是事后补 WHERE。PostgreSQL 推荐复合索引 tenant_id 加 created_at,大表可按 tenant 分区。ORM 层封装 TenantScope,禁止裸写 findAll。迁移脚本从第一天就带租户字段,后期拆表成本会低一个数量级。Webhook 落库、AI 生成记录、对账日志,全部要有租户键,否则跨店关联查询会拖垮性能。外键设计也要注意:子表引用父表时复合键带上 tenant,防止不同租户 ID 碰撞造成脏关联。

底线二:权限与租户解耦但对齐

角色权限决定能做什么,租户键决定能看见谁的数据。两者缺一都会出事故:只控权限不控租户,运营能跨店操作;只控租户不控权限,店员能改系统配置。我们采用 RBAC 加租户上下文:tenantId 从 JWT 注入,中间件统一校验,Service 层不再信任前端传的 shop 参数。平台管理员如需跨店运维,走单独 impersonate 流程并写审计日志。导出 Excel、批量 Webhook 重放这类高危操作,需要二次确认当前租户,避免 UI 缓存了上一店铺上下文。

底线三:异步任务也要带上下文

Webhook 回调、Bull 队列、定时对账任务必须携带 tenant_id,并做幂等键。否则重试一次就可能把 A 店库存写到 B 店。Worker 启动时从 job payload 恢复租户上下文,数据库连接使用 Row Level Security 或应用层强制过滤双保险。日志和链路追踪同样要带 tenant,否则线上排障像大海捞针。ERP 里夜间批量同步、AI 批量翻译,最容易在这一环翻车。Cron 任务不要写「全表扫描」,改为按 tenant 分片投递子任务,失败只影响单店。

从脚手架默认项开始

把这三条写进项目模板:建表模板、API 脚手架、队列封装默认带租户。集成测试用两个 shop 交叉断言,CI 里跑租户隔离用例。新同事 Code Review checklist 第一条就是「这条 SQL 有没有 tenant 条件」。比上线后补隔离便宜得多,也是跨境 SaaS 从 Demo 走向可售卖的分水岭。曾有一家 Shopify App 因忘记在定时任务里带 shop_id,凌晨批量任务把数百店库存归零,赔偿和回滚花了整整一周。多租户不是架构师的偏好,而是 SaaS 商业化的入场券;PostgreSQL 分区、RLS 和应用层过滤可以组合使用,但 tenant 键绝不能缺位。数据导出、GDPR 删除、店铺卸载清理都要按 tenant 编排,否则合规审计会卡住上架。建议在 ER 图评审时用红色标注「缺 tenant 的表」,比代码 review 阶段才发现便宜一个迭代。租户隔离测试应纳入发布门禁:任一 PR 改动 SQL 或队列消费逻辑,CI 必须跑跨租户用例,失败禁止合并。多租户做得好,第二个客户接入只需配 shop,而不是重构全库——这是 SaaS 规模化真正的杠杆。

小结

多租户的核心是隔离与成本之间的平衡。行级隔离适合大多数 SaaS 起步阶段,但查询、缓存、后台任务都必须强制带租户上下文。定期做「串租户」演练,比写一堆文档更能暴露漏洞。租户级限流与配额也要尽早纳入,避免大客户拖垮共享资源。