数据库多租户设计:创业早期也别把 tenant_id 当摆设

📅 2026/7/4 6:40:27
数据库多租户设计:创业早期也别把 tenant_id 当摆设
数据库多租户设计创业早期也别把 tenant_id 当摆设企业 SaaS 从第一天就会遇到多租户问题。早期为了快团队可能只在用户表里加一个 tenant_id查询时凭记忆手写条件。短期看没问题长期风险很大漏加条件导致数据串租户权限判断散落各处后续做企业审计和数据导出会非常痛苦。创业早期可以简单但不能把 tenant_id 当摆设。多租户设计的底线是任何数据访问都必须明确属于哪个租户。租户隔离是 SaaS 产品的生命线一旦出问题就是品牌危机。真实案例某 SaaS 创业公司上线 8 个月后客户投诉看到其他公司的数据。排查发现是搜索接口漏了 tenant_id 条件。团队连夜排查所有查询发现 12 处类似问题。此后建立统一查询层所有数据库访问强制带 tenant_id。数据隔离问题是企业客户最敏感的合规红线。一、租户边界要进入数据模型flowchart TD A[Tenant] -- B[User] A -- C[Project] A -- D[Document] A -- E[Usage Event]核心业务表都应该有租户归属尤其是项目、文档、知识库、用量事件、审计日志、任务记录。不要只在用户表里存 tenant_id然后通过用户间接推导所有数据。间接推导越多越容易出现绕过路径。有些全局表可以不带 tenant_id例如系统配置、公开模板、模型供应商配置。但只要数据和客户业务有关就应该明确租户边界。这个原则越早建立后续越少补洞。建议在数据模型评审时把 tenant_id 作为必检项。对比分析user 表存 tenant_id vs 各业务表存 tenant_id。user 表方案查询需要 join users容易漏条件性能差串租户风险高。各表存 tenant_id查询简单不容易漏审计方便合规成本低。企业 SaaS 几乎都选择各表存 tenant_id 方案。代价是存储稍大但安全和合规价值远超成本。二、查询层要统一注入条件async function findDocuments(ctx, filter) { return db.document.findMany({ where: { tenantId: ctx.tenantId, ...filter } }); }不要依赖每个开发者记得加 tenant_id。可以通过 Repository、ORM middleware、数据库 Row Level Security 或统一查询上下文注入租户条件。手写条件越多漏掉的概率越高。统一查询层还方便做审计。每次跨租户访问、管理员访问、后台任务访问都可以在同一个入口记录原因和操作者。企业客户问谁看过我的数据时系统要能回答。跨租户访问要单独记录审计日志并支持导出审计记录。跨租户访问要单独记录审计日志。三、后台任务也要带租户{ job_type: generate_report, tenant_id: t_1001, resource_id: doc_7788 }多租户漏洞常出现在后台任务。前端请求时有用户上下文任务入队后如果只保存 resource_idworker 执行时可能绕过租户校验。任务 payload 必须带 tenant_id并在执行前重新验证资源归属。模型调用、文件导出、邮件发送、账单统计都属于高风险任务。它们往往跨多个系统一旦租户上下文丢失排查会很困难。把 tenant_id 当成任务的一等字段是早期就该养成的习惯。所有后台任务都要有租户上下文传递测试CI 中强制检查。所有后台任务都要有租户上下文传递测试。四、测试要覆盖串租户风险it(does not return documents from another tenant, async () { const docs await findDocuments(ctxOfTenantA, {}); expect(docs.every(d d.tenantId tenant_a)).toBe(true); });多租户隔离不能只靠代码评审。核心查询、搜索、导出、批处理、管理后台都要有串租户测试。尤其是新增筛选条件和关联查询时最容易因为 join 或 where 顺序出错。测试数据要至少包含两个租户并故意使用相似资源名。这样才能发现查询条件遗漏。只用单租户测试环境永远测不出串租户问题。建议 CI 中强制运行多租户隔离测试作为上线必经检查。五、总结数据库多租户设计要让 tenant_id 成为真实边界而不是装饰字段。数据模型、查询层、后台任务和测试都要围绕租户隔离设计。创业早期可以不做复杂架构但不能在数据边界上偷懒。租户隔离一旦出错修复的不只是代码还有客户信任。数据边界是 SaaS 的底线。要点提炼tenant_id 不是装饰是安全边界核心业务表都要有 tenant_id不要只放 user 表查询层统一注入条件不依赖开发者记忆后台任务必须带 tenant_id执行前重新校验测试覆盖串租户风险至少两个租户的数据数据隔离是企业客户最敏感的合规红线串租户是 P0 级事故零容忍