SaaS数据库设计的三种模式对比:哪种最适合你的业务?
SaaS数据库设计的三种模式对比:哪种最适合你的业务? 核心结论 当你在开发一个面向多客户的SaaS系统(比如小程序CRM)时,数据库架构的选择会直接决定系统的隔离性、扩展成本与运维复杂度。三种主流模式各有适用场景: 独立数据库(Database per Tenant) :每个租户独享一个数据库实例,隔离性最强,适合对数据安全要求极高或客户愿意为独立资源付费
核心结论
当你在开发一个面向多客户的SaaS系统(比如小程序CRM)时,数据库架构的选择会直接决定系统的隔离性、扩展成本与运维复杂度。三种主流模式各有适用场景:
- 独立数据库(Database per Tenant):每个租户独享一个数据库实例,隔离性最强,适合对数据安全要求极高或客户愿意为独立资源付费的场景。
- 共享数据库,独立Schema(Schema per Tenant):同一数据库实例内为每个租户创建独立Schema,兼顾隔离与资源利用率,适合中型SaaS。
- 共享数据库,共享表(Shared Table with Tenant Column):所有租户数据混存在同一套表中,通过租户ID字段区分,成本最低但隔离最弱,适合小微租户或内部系统。
本文将以一个可落地的小程序CRM系统为例,从最终产品形态、功能模块、数据库设计、API与架构等维度拆解三种模式的实现方式,并给出开发路线建议。
一、这个系统最终可以做成什么样
以“小程序CRM”为例,最终交付的是一个支持多企业租户使用的客户关系管理平台。每个租户(企业)登录后只能看到自己的客户、订单、跟进记录等数据,管理员可以配置字段、流程和权限。系统支持在插件市场购买行业扩展包(如律所CRM、生鲜电商版),一键安装后自动扩展数据表和业务逻辑。
用户端形态:
- 租户管理员通过Web管理后台配置组织架构、角色权限、自定义字段。
- 员工在小程序端查看客户列表、写跟进、创建订单。
- 支持消息通知、审批流、数据导出等通用插件。
技术侧形态:
- 后端为Spring Boot微服务,前端管理后台用Vue3,移动端为UniApp小程序。
- 通过Jiey这类低代码平台,可以用自然语言描述需求,自动生成标准CRUD、权限和校验代码,将开发周期从2周压缩到3天。
二、核心功能模块拆解
1. 租户与权限管理
- 功能职责:租户注册、开通、到期管理;租户内角色(RBAC)、部门、用户管理。
- 核心页面:租户列表、租户详情(绑定数据库/Schema信息)、套餐配置。
- 数据结构:
tenants表:租户ID、名称、数据库连接信息(独立数据库模式时)、Schema名称(Schema模式)、状态、到期时间。users表:用户ID、租户ID、部门ID、角色ID、登录凭证。
- 业务流程:新租户注册 → 自动创建数据库/Schema → 初始化默认管理员 → 管理员登录后创建部门和员工。
2. 客户管理模块
- 功能职责:客户基本信息、标签、跟进记录、公海池。
- 核心页面:客户列表(支持自定义筛选)、客户详情(时间线)、导入导出。
- 数据结构:
customers表:客户ID、租户ID、姓名、电话、来源、负责人ID、状态等。customer_tags表:标签ID、租户ID、标签名。customer_follow_ups表:跟进ID、客户ID、内容、跟进方式、下次跟进时间。
- 注意:在共享表模式下,所有查询必须带上
tenant_id条件,否则会造成数据泄露。
3. 订单与商品模块
- 功能职责:商品管理、订单创建、支付、退款、发货。
- 核心页面:商品列表、订单工作台、退款审核。
- 数据结构:
products表:商品ID、租户ID、名称、价格、库存。orders表:订单ID、租户ID、客户ID、金额、状态(待支付/已支付/已退款)、创建时间。
- 状态流转:待支付 → 已支付 → 已发货 → 已完成;或 → 已退款。
4. 插件市场与行业扩展
- 功能职责:支持租户从市场购买并安装业务插件(如审批流、进销存、行业DNA包)。
- 核心页面:插件市场(列表、详情)、已安装插件管理。
- 数据结构:
plugins表:插件ID、名称、版本、适用数据库模式。tenant_plugins表:租户ID、插件ID、安装时间、配置JSON。
- 技术实现:插件安装时动态执行DDL脚本(在对应租户数据库/Schema中建表),并注册前端路由和菜单。Jiey平台已实现此类插件化架构,开发者可将完整业务系统打包为插件发布。
三、数据库设计思路
下面针对三种模式分别给出核心表设计,并说明关联关系。
模式一:独立数据库
每个租户拥有完全独立的数据库实例,表结构相同但数据物理隔离。
核心表(以租户A为例):
| 表名 | 作用 | 关键字段 |
|---|---|---|
| users | 租户内用户 | id, username, password_hash, role_id |
| customers | 客户 | id, name, phone, owner_id |
| orders | 订单 | id, customer_id, amount, status |
关联关系:orders.customer_id → customers.id,customers.owner_id → users.id。
优势:隔离性最强,备份恢复简单,可针对高价值客户单独扩容。 劣势:数据库实例数量多,运维成本高,跨租户统计困难。
模式二:共享数据库,独立Schema
同一MySQL/PostgreSQL实例中,为每个租户创建独立Schema(如 tenant_001、tenant_002),表名和结构一致。
实现方式:
- 在连接字符串中动态指定
search_path(PostgreSQL)或使用USE database后接CREATE SCHEMA(MySQL)。 - 应用层通过拦截器根据当前租户ID设置Schema上下文。
核心表(Schema tenant_001):
| 表名 | 作用 |
|---|---|
| users | 用户 |
| customers | 客户 |
| orders | 订单 |
关联关系:同独立数据库,所有查询默认在对应Schema内,无需额外租户过滤。
优势:资源利用率高,仍具备较好的逻辑隔离,支持租户级备份。 劣势:Schema数量过多时性能可能下降,跨租户查询需跨Schema联合。
模式三:共享表
所有租户数据存在同一套表中,通过 tenant_id 列区分。
核心表设计:
| 表名 | 作用 | 关键字段 |
|---|---|---|
| users | 所有租户用户 | id, tenant_id, username, password_hash |
| customers | 所有租户客户 | id, tenant_id, name, phone |
| orders | 所有租户订单 | id, tenant_id, customer_id, amount |
查询示例:
SELECT * FROM customers WHERE tenant_id = 123 AND name LIKE '%张%';
优势:成本最低,单表维护简单,便于全局统计。
劣势:数据隔离靠应用层保证,一旦漏加 tenant_id 条件即发生数据泄露;大租户数据量可能拖慢全表扫描。
四、API设计方案
以小程序CRM的客户管理为例,展示RESTful API设计,并说明多租户上下文传递方式。
1. 认证与租户识别
POST /api/auth/login
Content-Type: application/json
{
"username": "admin@tenant1.com",
"password": "xxx"
}
返回JWT令牌,令牌中包含 tenant_id 和 user_id。后续请求在Header中携带:
Authorization: Bearer <token>
2. 客户列表(共享表模式)
GET /api/customers?page=1&size=20&keyword=张
后端从JWT中提取 tenant_id,自动拼入查询条件:
// 伪代码
List<Customer> customers = customerMapper.selectList(
new LambdaQueryWrapper<Customer>()
.eq(Customer::getTenantId, currentTenantId)
.like(StringUtils.isNotBlank(keyword), Customer::getName, keyword)
);
3. 创建客户
POST /api/customers
Content-Type: application/json
{
"name": "张三",
"phone": "13800138000",
"source": "小程序"
}
后端强制设置 tenant_id,不允许前端传入:
Customer customer = new Customer();
customer.setTenantId(currentTenantId);
customer.setName(dto.getName());
// ...
4. 跨租户管理接口(仅平台管理员)
GET /api/admin/tenants
GET /api/admin/tenants/{tenantId}/customers
这些接口需要最高权限,用于平台运营。
在独立数据库或独立Schema模式下,API接口定义完全一致,区别仅在于数据源路由方式:独立数据库模式通过动态数据源切换,Schema模式通过设置Schema上下文。
五、系统架构设计
┌─────────────────────────────────────────────┐
│ 客户端 │
│ 小程序 (UniApp) │ Web管理后台 (Vue3) │
└──────────────────┬──────────────────────────┘
│ HTTPS
┌──────────────────▼──────────────────────────┐
│ API网关 (Nginx/Kong) │
│ - 限流、鉴权、日志 │
└──────────────────┬──────────────────────────┘
│
┌──────────────────▼──────────────────────────┐
│ 业务服务 (Spring Boot) │
│ ┌───────────┐ ┌──────────┐ ┌────────────┐ │
│ │ 租户上下文 │ │ 客户服务 │ │ 订单服务 │ │
│ │ 拦截器 │ │ │ │ │ │
│ └───────────┘ └──────────┘ └────────────┘ │
│ ┌──────────────────────────────────────┐ │
│ │ 动态数据源路由模块 │ │
│ │ - 独立数据库:切换DataSource │ │
│ │ - Schema模式:设置search_path │ │
│ │ - 共享表:注入tenant_id过滤 │ │
│ └──────────────────────────────────────┘ │
└──────────────────┬──────────────────────────┘
│
┌──────────────────▼──────────────────────────┐
│ 数据层 │
│ ┌─────────┐ ┌─────────┐ ┌───────────────┐ │
│ │ MySQL │ │ Redis │ │ Elasticsearch │ │
│ │ (主库) │ │ (缓存) │ │ (搜索) │ │
│ └─────────┘ └─────────┘ └───────────────┘ │
└─────────────────────────────────────────────┘
- 租户上下文拦截器:从JWT中解析租户ID,存入ThreadLocal,并在请求结束时清理。
- 动态数据源路由:根据租户配置,自动选择对应的数据库连接或Schema。共享表模式无需切换数据源,但需要全局注入租户过滤条件(可使用MyBatis-Plus的多租户插件)。
- 缓存设计:Redis Key 前缀必须包含租户ID,如
tenant:123:customer:456,避免缓存冲突。
六、开发路线规划
第一阶段:MVP(最小可行产品)
- 采用共享表模式,快速上线验证业务。
- 实现租户注册、用户登录、客户增删改查、简单订单功能。
- 使用Jiey平台描述需求,自动生成基础CRUD代码,3天内完成原型。
第二阶段:商业版
- 引入独立Schema模式,为付费客户提供更好的隔离。
- 开发插件市场,支持安装审批流、消息通知等通用插件。
- 增加数据导出、自定义字段、简单报表。
第三阶段:企业版
- 支持独立数据库模式,面向金融、医疗等高安全需求客户。
- 实现行业DNA包(如律所CRM、少儿培训),通过插件市场分发,价格区间 ¥199-¥999。
- 完善监控、自动扩容、租户级备份恢复。
七、关键技术难点
- 数据源动态切换:在独立数据库/Schema模式下,需要实现无感的动态路由。可使用AbstractRoutingDataSource + 租户上下文,并在事务管理上注意切换时机。
- 插件安装的DDL执行:插件市场安装时,需在租户对应的数据库/Schema中执行建表语句,同时要处理版本升级时的Schema迁移。建议使用Flyway或Liquibase,并在租户上下文中执行。
- 共享表模式的安全防护:必须通过AOP或ORM插件强制注入租户条件,避免开发人员遗漏。同时定期扫描SQL日志,检测无租户过滤的查询。
- 跨租户数据统计:平台需要全局分析时,共享表模式最简单;Schema模式需跨Schema查询;独立数据库模式则需要数据汇聚(如通过CDC同步到数据仓库)。
- 租户计费与资源隔离:独立数据库模式容易按实例计费;共享表模式需限制单租户数据量或请求频率,防止“吵闹邻居”问题。
八、FAQ
Q1:小程序CRM初期只有几十个客户,选哪种模式?
建议从共享表模式起步,开发快、成本低。当有客户愿意为数据隔离付费时,再迁移到独立Schema或独立数据库。迁移可通过脚本将指定租户的数据导出并导入新库。
Q2:插件市场如何兼容三种数据库模式?
插件需声明所依赖的表结构,安装时由平台根据当前租户的数据库模式执行对应操作:独立数据库/Schema模式下直接执行DDL;共享表模式下,所有插件表都需包含 tenant_id 列,并注册全局过滤。
Q3:使用Jiey生成代码时,如何支持多租户?
在描述需求时明确告知AI:“这是一个多租户SaaS系统,所有业务表需包含tenant_id字段,查询时自动过滤”。Jiey会自动生成带租户过滤的Spring Boot + Vue3代码,并集成MyBatis-Plus多租户插件。
九、总结
三种SaaS数据库模式没有绝对的优劣,核心是根据业务阶段和客户需求做取舍:
- 共享表:适合验证期、小微客户、内部工具,成本最低。
- 独立Schema:适合成长型SaaS,平衡了隔离与成本,是多数商业SaaS的默认选择。
- 独立数据库:适合高价值客户或强合规行业,提供物理隔离和独立运维能力。
在实际开发中,可以借助Jiey这类低代码平台快速生成基础架构,再根据租户等级动态切换数据源,实现混合模式部署。插件市场则让业务扩展像安装App一样简单,从通用CRM到行业垂直方案,只需一键安装即可完成数据库和代码的自动扩展。