多租户数据库设计:共享系统的实体关系图(ERD)方法

为多租户环境设计数据库架构需要仔细考虑数据隔离、可扩展性和维护开销。实体关系图(ERD)作为这些决策的蓝图,规定了数据在租户间的结构方式。选择合适的方案会影响性能、安全性以及系统随时间演进的能力。本指南将探讨主要的架构模式、它们对ERD的影响,以及每种策略所涉及的权衡。

Whimsical infographic illustrating four multi-tenant database design strategies: Database Per Tenant (separate cottages on islands), Schema Per Tenant (apartment building with colored floors), Shared Schema (co-working space with tenant_id name tags), and Hybrid Model (modular castle), with visual comparisons of isolation, cost, and maintenance trade-offs for SaaS architecture planning

🔍 理解数据建模中的多租户

多租户允许单个软件实例服务于多个客户,这些客户通常被称为租户。在数据库设计的背景下,核心挑战在于如何在保持效率的同时决定如何分离租户数据。ERD必须清晰地反映这些分离边界。

  • 租户:使用系统的单个客户或组织。
  • 共享系统:应用程序逻辑以及潜在的底层基础设施。
  • 数据隔离:确保一个租户无法访问另一个租户的数据。

设计选择主要围绕隔离边界的位置展开。它存在于数据库级别、模式级别还是行级别?每种选择都需要特定的ERD结构。

🏗️ 策略一:每个租户一个数据库

在此模型中,每个租户都获得一个专用的数据库实例。这提供了最高级别的隔离性和安全性。从ERD角度来看,所有数据库的模式完全相同,但物理分离是绝对的。

📊 ERD结构

单个租户数据库的ERD图与标准的单租户设计完全相同。不需要一个“租户ID"“列,因为数据库边界本身即充当过滤器。

  • 表结构:表仅包含与特定租户相关的数据。
  • 外键:应用标准引用完整性,无需感知租户。
  • 索引:针对该租户的特定数据量进行优化。

✅ 优势

  • 完全隔离:一个数据库的漏洞不会影响其他数据库。
  • 定制化:模式修改可以应用于特定租户,而不影响其他租户。
  • 性能:同一连接池或磁盘I/O上无其他租户的争用。

❌ 缺点

  • 成本:由于存在多个实例,基础设施成本较高。
  • 维护:架构更新需要部署到每个数据库实例。
  • 复杂性:在大规模下,管理连接和编排变得困难。

🏗️ 策略 2:每个租户一个架构

该方法介于前两种方案之间。每个租户在同一个数据库服务器中获得一个专用架构。这减少了管理多个数据库连接的开销,同时保持了逻辑隔离。

📊 ERD 结构

ERD 与单租户模型保持一致,但命名空间发生了变化。表存在于特定的架构命名空间中,而不是公共命名空间。

  • 表名:标准命名约定(例如:”users, orders).
  • 架构名:唯一标识符(例如:”schema_tenant_a, schema_tenant_b).
  • 连接性:应用程序连接到当前租户的特定架构。

✅ 优点

  • 隔离性:比共享架构模型具有更强的隔离性。
  • 管理:比独立的数据库实例更容易管理。
  • 备份:可以独立地还原或备份各个模式。

❌ 缺点

  • 资源使用情况:仍然比完全共享模型消耗更多资源。
  • 查询复杂性:跨租户聚合数据需要动态切换模式。
  • 模式漂移:在多个租户之间保持模式同步非常耗费人力。

🏗️ 策略 3:共享数据库,共享模式

这是 SaaS 应用中最常见的方案。所有租户共享同一个数据库和相同的表。数据分离通过一个唯一标识符列在逻辑上实现。

📊 ERD 结构

ERD 必须明确包含一个“tenant_id“列,用于存储租户特定数据的每张表中。该列作为分区键。

  • 核心表: users, orders, products都包含一个“tenant_id.
  • 共享表:共享表:roles“或“permissions“等表可能会在所有租户之间共享。
  • 约束条件:外键可能需要限定范围,以确保在租户上下文中维护引用完整性。

✅ 优势

  • 成本效益:基础设施成本最低。
  • 维护:架构变更会立即应用于所有租户。
  • 分析:更容易聚合数据以进行系统范围的报告。

❌ 劣势

  • 复杂查询:每个查询都需要按租户 ID.
  • 性能:如果某个租户消耗过多资源,则存在较高的争用风险。
  • 安全性:逻辑错误导致数据泄露的风险更高。

🏗️ 策略 4:混合模型

混合方法结合了上述策略的要素。例如,为标准数据使用共享架构,而为高级层级或特定高价值租户使用专用架构。

📊 ERD 结构

ERD 变得更加复杂,区分共享表和租户特定表。

  • 全局表:存储配置或共享元数据。
  • 租户表:存储带有租户 ID的租户数据,或存储在单独的架构中。
  • 关联:连接操作必须考虑数据的范围。

🛡️ 数据隔离与安全考量

无论选择何种策略,数据隔离都至关重要。实体关系图(ERD)必须支持能够防止意外数据访问的机制。

🔒 行级安全

在共享模式架构中,可以定义行级安全(RLS)策略。数据库引擎将限制对满足以下条件的行的访问:tenant_id与经过认证的上下文相匹配。

  • 实现方式:策略会对每次SELECT, UPDATE以及DELETE操作强制执行检查。
  • 优势:防止应用程序级别的错误导致数据泄露。
  • 对 ERD 的影响:需要在所有相关表中显式添加tenant_id列。

🔒 外键约束

在共享模型中,确保跨租户的引用完整性可能较为复杂。理想情况下,外键不应指向跨越多个租户的表,除非该关系被明确定义为全局关系。

  • 自引用:如果一张表引用自身(例如,parent_id),tenant_id必须在两侧保持一致。
  • 全局引用:类别可能是全局的,允许任何租户引用它们。

⚡ 性能与扩展策略

随着租户数量的增长,性能成为一个关键问题。实体关系图(ERD)设计直接影响系统的扩展能力。

📈 索引策略

索引对查询性能至关重要。在共享架构中,tenant_id列应作为主复合键的一部分或进行重度索引。

  • 复合索引: (tenant_id, created_at)可高效地按租户和时间进行过滤。
  • 部分索引:索引可以仅针对特定条件创建,从而减小索引大小。
  • 避免:对无助于租户过滤的列进行索引。

📦 分区

表分区有助于管理大型数据集。数据可按tenant_id或按租户内的时间范围进行分区。

  • 范围分区:按日期范围拆分数据。
  • 列表分区:按特定租户 ID 拆分数据。
  • 管理:可以分离或归档分区以提升性能。

🔧 维护与架构演进

软件不断演进。需要添加表、修改列或更改类型。所选架构决定了这些变更所需的工作量。

🔄 架构更新

  • 共享架构:单个迁移脚本即可更新所有租户的架构。这是最简单的路径。
  • 每个租户一个数据库:迁移脚本必须针对每个数据库实例执行。需要实现自动化。
  • 每个租户一个模式:类似于每个租户一个数据库,但在同一实例内进行管理。

📝 向后兼容性

修改实体关系图(ERD)时,请确保向后兼容性,以避免停机。

  • 添加列:首先使用可为空的列,然后填充数据,最后将其设为非空。
  • 删除列:在删除列之前先重命名,以防止破坏性变更。
  • 版本控制:如果租户可以选择退出更新,请考虑对模式本身进行版本控制。

📋 架构方案比较

特性 每个租户一个数据库 每个租户一个模式 共享模式
隔离性
成本
维护 复杂 简单
查询性能 高(无过滤) 可变(需要过滤)
ERD 复杂度 简单(按数据库) 简单(按模式) 复杂(需要 tenant_id)
可扩展性 水平 垂直 垂直/水平

✅ 最佳实践检查清单

在最终确定多租户系统的 ERD 之前,请确保满足以下标准。

  • 定义租户范围:明确识别哪些数据属于特定租户,哪些是全局数据。
  • 标准化命名:在所有表中对 tenant_id列使用一致的命名约定。
  • 实施约束:尽可能使用数据库约束来防止跨租户的数据访问。
  • 规划租户流失:设计支持租户入驻和退订(数据删除或归档)。
  • 测试隔离性:定期测试以确保一个租户无法查询另一个租户的数据。
  • 记录关系:在 ERD 文档中清晰记录外键关系。
  • 监控性能:为可能表明特定租户瓶颈的慢查询设置警报。

🧩 处理边界情况

现实场景往往会引入标准实体关系图(ERD)无法立即涵盖的复杂性。

🔄 租户合并

有时,两个租户会合并为一个。在共享模式架构下,这需要将行从一个tenant_id移动到另一个。在每租户一个数据库的模型中,这涉及合并两个完整的数据库。

  • 数据一致性:确保合并过程中没有数据丢失。
  • 去重:处理合并过程中可能产生的重复记录。

📉 租户流失

租户会离开。选择删除数据还是归档数据会影响实体关系图(ERD)。

  • 软删除:添加一个is_deleted标志以保留数据以满足合规要求。
  • 硬删除:彻底删除行。确保级联删除配置正确,以避免产生孤立记录。
  • 归档:将旧租户数据移至冷存储表,同时保持架构完整。

🔗 与应用逻辑集成

实体关系图(ERD)并非孤立存在,它必须与应用层无缝集成。

  • 中间件:使用应用级中间件自动注入tenant_id到每个查询中。
  • ORM 配置:配置对象关系映射(ORM)工具以处理租户作用域。
  • API 设计:确保 API 端点在返回数据前验证租户上下文。

🎯 关于设计的最终思考

为多租户环境选择适当的数据库设计,是在隔离性与效率之间寻求平衡。实体关系图(ERD)充当定义这些边界的契约。不存在单一的完美解决方案;选择取决于对安全性、成本和规模的具体要求。通过理解每种策略的影响,架构师可以构建出稳健、可扩展且安全的系统。

专注于清晰的数据建模实践,可确保随着租户数量的增长,系统仍易于维护。定期将实体关系图(ERD)与实际使用模式进行对比审查,有助于在瓶颈或安全漏洞演变为关键问题之前将其识别出来。

最终目标是设计出一个既能支持业务发展,又不损害数据完整性的方案。在实体关系图(ERD)阶段进行周密规划,可避免后期出现昂贵的重构工作。