很多 SaaS 项目在开发初期看起来都很简单。注册一个企业账号,创建几个用户,用户登录以后操作订单、客户、项目或者财务数据,数据库把这些东西保存下来,服务器负责把页面显示出来。开发团队往往把大量精力放在功能什么时候上线,却很少在第一天认真讨论一个看似朴素的问题:这个数据库里的每一条数据,到底属于谁,谁可以看到,谁可以修改,谁可以删除,企业停止使用 SaaS 服务以后又应该怎么办?
这恰恰是 SaaS 数据库建模最容易留下隐患的地方。
SaaS 和传统软件最大的区别之一,是软件提供方通常同时服务大量彼此独立的企业客户,也就是所谓的多租户环境。这里的“用户”和“企业”并不是同一个层级。一个 SaaS 租户通常代表一个企业、组织或者账户边界,而一个租户下面又可能拥有管理员、普通员工、财务人员、客服人员等多个用户。数据库如果从一开始没有把这些关系设计清楚,系统规模扩大以后,再补救往往比最初设计困难得多。
一个比较合理的思路,是把 Tenant、User、Role、Permission 和业务数据之间的关系明确下来。
例如,一家公司可以是一个 Tenant,一个 Tenant 可以拥有多个 User;用户可以通过 Role 获得权限,而订单、客户、项目、发票等业务记录则属于某一个 Tenant。这样,用户登录之后,系统首先确定他属于哪个租户,再根据他的角色和权限决定他可以操作哪些数据。
这听起来像常识,却是多租户系统最重要的一道安全边界。
很多数据库表可能只需要增加一个 tenant_id,事情似乎就解决了。但真正危险的地方就在这里:开发人员很容易在某个查询中忘记这个条件。
例如正常查询应该是根据 tenant_id 和订单 ID 查找订单,如果代码只根据订单 ID 查询,那么理论上只要知道另一个租户的订单编号,就可能拿到不属于自己的数据。数据库结构本身没有崩溃,SQL 也完全合法,系统却出现了严重的数据隔离问题。
所以,tenant_id 不应该被理解成“以后查询的时候记得加一个字段”这么简单。它应该成为整个应用架构中的安全边界。数据库索引、唯一约束、外键关系、ORM 查询、缓存 Key、后台任务、导出功能以及 API 权限检查,都需要考虑租户边界。
例如订单编号如果只在整个数据库中唯一,系统可以使用全局唯一 ID;如果业务要求订单号在每个企业内部独立编号,那么唯一约束就应该考虑 (tenant_id, order_number) 的组合,而不是简单地给 order_number 建一个全局唯一索引。
这类细节平时几乎没人讨论,但它们决定了系统能不能安全地从 100 个企业扩展到 10 万个企业。
多租户隔离也并非只有一种方案。
一种方式是每个租户使用独立数据库或者独立数据库 Schema。这种模式隔离程度较高,某些企业客户甚至可能要求自己的数据拥有独立环境,但随着租户数量增加,数据库实例、备份、升级、监控和迁移都会变得更加复杂。
另一种常见模式是共享数据库、共享表,每条业务记录带有 tenant_id。这种方案资源利用率高,也容易规模化,但对应用层和数据库安全策略提出了更高要求。
还有介于两者之间的混合模式,例如普通客户共享基础设施,大型企业客户使用独立数据库。商业模式、合规要求和客户规模不同,完全可以采用不同的隔离等级。
因此,没有一种多租户数据库架构能够被称为“唯一正确答案”。真正需要解决的是隔离边界、运营成本、性能、备份恢复以及合规要求之间的平衡。
还有一个非常容易被忽略的问题,是“用户属于企业”并不意味着“用户就是数据所有者”。
SaaS 平台的数据库中可能同时存在平台自己的数据、租户业务数据、用户个人信息、系统日志、计费记录以及技术运行数据。它们的法律属性和合同关系可能完全不同。企业客户通常会关心自己上传的数据如何使用、谁能够访问、服务终止后如何导出以及删除政策是什么;SaaS 提供商则需要维护平台运行、计费、安全审计和技术支持所必需的数据。
因此,在数据库建模之前,最好先把数据分类,而不是把所有东西都扔进几张“大表”。
数据生命周期同样容易被低估。
创建数据只是数据库生命周期的开始。之后还有修改、归档、删除、恢复、导出以及可能的法律保留要求。比如客户删除一个员工账号,到底意味着把这个用户从数据库里物理删除,还是禁止登录但保留历史订单中的操作者信息?一个客户删除项目以后,相关发票是否也必须删除?已经进入财务记录的数据是否存在不同的保存要求?
这些问题不是单纯的 DELETE FROM 能解决的。
很多系统还需要审计日志,但审计日志与业务数据本身应该有所区分。业务表记录“现在是什么状态”,审计记录则回答“它以前是什么状态、是谁在什么时候进行了什么操作”。例如管理员修改了某个账户权限,系统至少应该能够追溯操作人、时间、对象和操作类型。
如果所有修改都直接覆盖原值而没有任何审计信息,几个月以后出现数据争议,管理员面对的可能不是一个技术问题,而是一场侦探小说。
软删除也不能简单等同于合规删除。给表增加 deleted_at,只是让应用暂时看不到记录,数据本身仍然存在。如果用户拥有依法要求删除个人数据的权利,那么系统还必须考虑备份、缓存、搜索索引、日志和其他副本中的数据如何处理。数据库建模从一开始就应该考虑这些数据副本,而不是等客户要求删除时才发现数据已经散落到十几个系统里。
安全问题也经常在数据库设计阶段被错误理解。
例如身份证件号码、银行卡信息、个人联系方式等敏感数据,并不是“加密以后就万事大吉”。数据安全通常涉及传输加密、静态存储加密、密钥管理、访问控制、最小权限、日志审计以及应用层安全等多个层面。
而且,并不是所有敏感字段都适合直接做可逆加密。
假设系统需要根据用户邮箱进行精确查询,如果把邮箱使用随机初始化向量进行标准加密,那么数据库无法直接利用密文进行普通的等值查询。工程上可能采用哈希索引、盲索引、确定性加密等方案,具体选择取决于数据类型和威胁模型。同态加密则属于更加特殊的密码学技术,并不是普通 SaaS 数据库解决字段查询问题的默认方案。
数据库性能也是同样的道理。
SaaS 系统刚开始只有几百个客户时,一条查询可能只需要几毫秒。几年以后,数据库里可能已经积累数亿条订单记录。如果数据库设计没有考虑租户规模差异、查询模式和索引策略,系统就会出现一种非常典型的情况:小客户觉得系统很快,大客户却觉得系统像在用拨号上网。
因此,索引设计不能只看字段有没有被查询,还要看查询组合。
如果系统经常按照 tenant_id + created_at 查询某个企业最近的数据,那么索引应该围绕真实查询模式设计。对于订单、日志、消息等不断增长的大型表,还需要考虑时间范围查询、分页方式以及历史数据归档。
多租户系统还有一个非常现实的问题叫“噪声邻居”。
一个 SaaS 平台可能拥有几千个普通客户,却突然有一个大型企业一次性导入几千万条数据,或者某个客户突然运行一个复杂报表。如果所有租户共享同一套数据库资源,那么这个客户可能影响其他客户。
这时候问题已经不只是 SQL 是否优化,而是资源隔离。数据库连接数、CPU、IO、缓存、队列甚至后台任务都可能需要进行限制。对于大型 SaaS 平台,租户级别的限流和资源配额有时与数据库索引同样重要。
缓存则是另一个容易制造数据泄露的地方。
开发人员往往记得数据库查询需要 tenant_id,却忘记 Redis Key 也需要租户边界。假设缓存 Key 只是 user:12345 或 orders:12345,一旦不同租户之间出现 ID 冲突或者缓存逻辑设计不当,就可能把本来隔离良好的数据库数据重新通过缓存暴露出去。
因此,多租户隔离绝不是数据库一个层面的任务,而应该贯穿 API、应用、缓存、消息队列、搜索引擎和对象存储。
跨服务的数据一致性也是 SaaS 规模扩大后经常出现的问题。
例如订单服务创建订单以后,库存服务需要扣减库存,支付服务又需要记录付款状态。如果这些服务各自拥有自己的数据库,就不能简单依赖单个数据库事务把整个流程包起来。
这时候通常需要根据业务选择事务消息、Outbox Pattern、幂等机制、重试、补偿以及最终一致性等方案。并不是所有分布式系统都需要传统意义上的分布式事务,更不能把“ACID 做不到”简单理解成“数据库设计失败”。关键在于明确哪些数据必须强一致,哪些业务允许短时间的不一致,以及出现失败以后如何恢复。
文件和非结构化数据也经常在数据库建模时被处理得过于随意。
用户上传的图片、PDF、视频和文档通常不应该直接塞进关系型数据库的大字段里作为唯一存储方案。更常见的设计是把文件放进对象存储,而数据库保存文件 ID、租户 ID、对象路径、大小、类型、上传时间和访问权限等元数据。
这样做的好处是数据库继续负责结构化数据,对象存储负责大型文件,两者职责更加清晰。
最后还有一个跨地区 SaaS 特别容易踩到的坑:数据位置。
“欧洲客户的数据必须永远存在欧洲”“中国客户的数据必须全部放在中国”这样的说法听起来简单,实际却取决于具体法律、行业规定、合同条款和数据处理方式。数据存储在哪里只是其中一个问题,备份在哪里、日志在哪里、技术人员从哪里访问、跨境传输如何进行,也可能涉及合规要求。
所以,数据库设计不能等系统完成以后再找法律顾问补一份隐私政策。对于面向多个国家和地区的 SaaS 产品,数据分类、数据流向、保留期限、删除机制以及跨境传输要求,都应该尽早进入架构设计。
一个成熟的 SaaS 数据库,并不是表设计得越漂亮越好,也不是把所有数据都拆成几十张表就代表专业。它首先应该回答几个非常朴素的问题:这条数据属于哪个租户,谁可以访问,谁可以修改,什么时候应该删除,删除以后还有哪些副本,数据量扩大十倍以后查询是否还能工作,一个客户出现异常流量时会不会拖垮其他客户,以及当服务停止以后客户能不能把自己的数据完整带走。
数据库最麻烦的地方,是它在项目刚开始的时候往往显得特别安静。几十个客户的时候,一个设计上的小疏忽可能完全没有感觉;等系统增长到几万家企业,那个当年被认为“以后再处理”的字段、索引、权限和数据生命周期问题,往往会变成最昂贵的技术债务。SaaS 的核心并不只是把软件放到云端然后按月收费,而是建立一套能够在大量陌生客户之间保持清晰边界的数据系统。数据库建模做得好,用户看到的是一个简单顺滑的产品;建模做得差,后台最终会用非常昂贵的方式提醒所有人,当初少设计一个字段,到底能有多贵。
SaaS 用户和企业之间是什么关系,数据库建模时容易忽略哪些问题
喜欢这篇报道?
使用下面的功能,方便以后继续阅读和分享 MNewsTV
关于文章收藏
收藏不需要注册帐号,收藏信息仅保存在当前浏览器中。
删除收藏请进入「我的收藏」进行管理。
分享 Facebook | X | WhatsApp | LinkedIn
捐助(Paypal): https://www.paypal.me/observeccp 订阅中国观察电报 Telegram : https://t.me/s/ObserveCCP