滚动新闻 →
PHP 更新数据时加入 version 字段,可以解决哪些并发问题 57个粉丝的博主发AI视频调侃问界 被索赔百万 笔记本电脑插电才能开机,拔掉充电器马上关机,电池可能出了什么问题 Windows 11 文件分组功能怎么用?让大量文件更加容易浏览 PHP PDO 预处理语句怎么写?从查询参数到安全执行完整演示 辛弃疾为什么能够把豪情写进词里 骗子被耍惨 澳洲初创公司用AI扮受害者收集情报 浙江男没工作杀人泄愤 进写字楼随机砍死女职员 枸杞在中国传统饮食文化中的历史 中国书法为什么强调“气韵” 韩国军事基地曝漏洞 近200辆比亚迪可进出 个人是养老第一责任人? 山东民政厅厅长言论惹议 传成都人从俄罗斯回国后死亡 官方称鼠疫信息保密 如何培养孩子对校园同学的尊重 视频压缩为什么会影响画面细节 川普乔治亚州助选 博顿斯拼首位黑人女州长 胡塞武装持续攻击沙特 油价飙破110美元 美拟升级中国旅行警告 提高“非法拘押”风险等级 中国人存钱不消费 IMF:因沉重医疗成本 伊朗镇压黑幕曝光 革命卫队陷缺钱危机 欧洲三国爆发抗议 比利时警民激烈冲突 双台风夹击太平洋 墨西哥和关岛紧急戒备 武则天为何能够登上皇帝之位 旧房改造是不是一定要大拆大建 官媒转向?《经济日报》下场批判“懂车帝” 川普救油价暂缓对俄制裁 泽连斯基:无法接受 加州多地爆发野火 数栋豪宅遭焚毁 豆瓣酱背后有什么样的地域饮食文化 喜欢自然风景的人怎样安排旅行 汽车启动机故障有哪些常见表现 固态电池为什么被认为是下一代电池技术 被警察盗走?浙江男出狱后发现账户近50万元消失 白俄罗斯恐参战!俄拟部署弹道导弹袭击乌西 沙特阿联酋联手反击胡塞 伊朗幕后援助被曝光 打破中共稀土垄断!六大国际银行重磅结盟 美国买房时如何计算每月真正的住房支出 北美家庭应该如何制定长期储蓄目标 湖南娄底公园暗夜砍人 两男子倒地不起 五中全会禅让李强?习近平恐成最后党魁! 美国和加拿大购买游艇有什么区别

用户注册以后如何自动创建自己的 SaaS 工作空间,数据库需要怎样配合

发布时间: 2026-09-26 10:00:02    最后更新: 2026-10-16 17:02:51    阅读:63  约16 分钟阅读     

用户注册以后如何自动创建自己的 SaaS 工作空间,数据库需要怎样配合
图片说明:示意图   图片来源:Public Domain(公有领域)
一个成熟的SaaS系统,用户点击“注册”以后,并不是简单地向users表插入一条记录,然后再创建一个数据库。注册动作背后通常需要建立一套完整的租户关系:用户是谁、属于哪个组织、当前进入哪个工作空间、在工作空间里拥有什么角色,以及这个工作空间能够访问哪些业务数据。

如果把这些概念混在一起,系统很快就会出现权限混乱。一个用户可能属于多个公司,一个公司也可能有几十甚至几千名成员;同一个用户可以在A公司的工作空间里是管理员,在B公司的工作空间里只是普通成员。因此,专业SaaS系统通常不会把“用户”和“工作空间”设计成一对一关系,而是把用户、组织、成员关系和业务数据分别建模。

最基本的数据结构可以从四个核心对象开始:

users
organizations
memberships
business tables

users保存身份信息,organizations或者workspaces保存租户信息,memberships保存用户与工作空间之间的关系,而具体业务表则通过organization_id或者tenant_id与工作空间建立归属关系。

例如:

users
---------
id
email
password_hash
created_at

organizations
-------------
id
name
created_at
status

memberships
-----------
id
user_id
organization_id
role_id
status
created_at

用户注册完成以后,应用服务可以在一个数据库事务中完成核心初始化:

创建用户
↓
创建 Organization / Workspace
↓
创建 Membership
↓
赋予 owner/admin 角色
↓
提交事务

这样,注册用户和他的第一个工作空间就建立了明确的数据库关系。

这里有一个非常重要的设计区别:工作空间不一定等于数据库。

大多数中小型SaaS并不会在每个新用户注册时执行CREATE DATABASE。如果有100万个客户,就创建100万个数据库,会产生完全不同于普通业务表设计的运维问题,包括连接管理、备份、迁移、监控、升级、权限、连接池以及跨租户分析。

更常见的方案是共享数据库、共享表,然后使用organization_id或者tenant_id区分租户数据。

例如一个项目表:

projects
--------
id
organization_id
name
created_at

查询项目时,服务器不是简单执行:

SELECT * FROM projects;

而是根据经过服务器端授权确认的当前组织执行:

SELECT *
FROM projects
WHERE organization_id = :organization_id;

这个条件不是为了“方便查询”,而是SaaS数据隔离的核心边界。

如果用户属于组织A,就只能在授权范围内访问组织A的数据;即使数据库中同时存在组织B的数据,也不能因为客户端修改一个organization_id参数就读取组织B的数据。

因此,不能相信浏览器提交的:

organization_id=123

就直接认为用户属于组织123。

正确流程应该是:

登录用户
↓
服务器取得user_id
↓
查询membership
↓
验证user_id是否属于organization_id
↓
验证role和resource权限
↓
执行tenant-scoped查询

这比单纯在数据库查询语句里增加一个WHERE user_id = ...更加准确,因为SaaS权限边界通常不是“某个用户拥有某条记录”,而是“某个用户通过membership进入某个组织,再根据角色访问组织中的资源”。

例如同一个人可能存在:

User A
├── Organization X → Owner
├── Organization Y → Admin
└── Organization Z → Member

这就是为什么用户表里直接增加一个organization_id往往会限制系统未来的发展。

工作空间创建完成后,还需要处理角色权限。最简单的系统可以在membership中直接保存:

role = owner

更复杂的系统可以建立:

roles
permissions
role_permissions
memberships

这样就可以把“谁属于哪个组织”和“这个人在组织里能做什么”分开。

例如:

Owner
├── billing.manage
├── members.manage
├── projects.create
├── projects.delete
└── settings.manage

Member
├── projects.view
└── projects.edit

这就是RBAC的典型结构。

如果业务进一步复杂,例如同一个成员只能管理某些项目,而不能管理整个组织,则还需要资源级权限,甚至ABAC等更细粒度的授权模型。

数据库层面也应该尽量帮助应用阻止跨租户访问。

以PostgreSQL为例,在适合的架构中可以使用Row-Level Security,把tenant isolation进一步落实到数据库层。这样即使应用代码某个查询遗漏了租户过滤条件,数据库策略也可以成为额外的防线。

不过RLS并不是所有SaaS都必须使用的方案。很多系统首先依靠应用层授权、统一repository/data-access层以及严格的tenant-scoped查询实现隔离,然后根据安全要求增加数据库级策略。

真正容易出事故的地方,是开发人员只给某些表增加organization_id,却忘记了其他关联数据。

例如:

organizations
↓
projects
↓
tasks
↓
comments
↓
attachments

如果projects按照organization隔离,但attachments只按照task_id查询,那么附件访问接口仍然必须确认task属于当前组织。

SaaS租户隔离不是在users表里增加一个字段就完成了,而应该沿着整个数据访问链建立一致的tenant boundary。

文件系统、缓存、搜索索引、消息队列同样如此。

例如Redis缓存不能只使用:

project:123

而应该考虑类似:

tenant:456:project:123

对象存储路径、搜索索引、后台任务、Webhook以及日志系统,也必须避免不同租户之间发生数据串线。

数据库架构选择则是另外一个问题。

SaaS常见的数据库隔离模式大致有四类。

第一种是共享数据库、共享表。

Database
├── users
├── organizations
├── projects
├── invoices
└── tasks

每张租户业务表包含 organization_id

这是最常见、成本也相对较低的模式。数据库迁移简单,连接池容易管理,跨租户统计也比较方便,但应用必须非常严格地执行tenant isolation。

第二种是共享数据库、独立Schema。

Database
├── tenant_a
├── tenant_b
└── tenant_c

这种方式可以增加数据库对象层面的隔离,但如果租户数量巨大,Schema数量、migration、权限管理以及运维复杂度都会迅速增加。它并不是“每个用户一个Schema”这么简单,也不一定比共享表方案更适合SaaS。

第三种是每个租户独立数据库。

Tenant A → Database A
Tenant B → Database B
Tenant C → Database C

这种方式提供更强的数据边界,也方便某些客户进行独立备份、恢复、迁移或者数据驻留管理,但代价是数据库数量、连接、迁移、监控和运维复杂度明显增加。

而且“每个客户一个数据库”和“每个客户一台数据库服务器”完全是两回事。多个数据库完全可以运行在共享数据库基础设施上。

第四种是混合架构。

普通客户使用共享数据库和共享表,大客户或者存在特殊合规要求的客户迁移到独立数据库。

┌─ Shared DB
SaaS Tenant ─────────┤
└─ Dedicated DB

对于商业SaaS来说,这种架构往往更加现实,因为它允许系统随着客户规模和需求变化逐渐升级隔离级别,而不是在第一天就为所有客户承担最高的基础设施成本。

用户注册时到底应该同步创建什么,则需要进一步区分。

核心身份数据,例如users、organizations、memberships,通常适合在一个本地数据库事务中完成。

如果注册成功以后还需要创建其他资源,例如对象存储目录、默认项目、搜索索引、第三方服务账户或者发送欢迎邮件,就不应该为了这些外部操作把一个超长事务一直保持打开。

更常见的设计是:

DB Transaction
↓
User
Organization
Membership
Subscription state
↓
Commit
↓
Outbox Event
↓
Background Worker
↓
初始化其他资源

这里可以使用Transactional Outbox模式。

例如事务中同时写入:

organizations
memberships
outbox_events

事务提交以后,后台worker读取outbox_events,再执行:

创建默认项目
创建对象存储前缀
初始化搜索索引
发送邮件
建立第三方集成

这样即使后台worker暂时宕机,也可以在恢复以后继续处理事件,而不会出现“用户已经注册成功,但是系统忘记初始化工作空间”的状态。

因此,原稿中“用户注册、工作空间创建、权限分配必须和所有资源创建放在同一个事务里”的说法需要修改。

数据库事务适合保证本地数据库中的原子性,不适合把数据库、Redis、对象存储、邮件系统、Kafka以及第三方API全部塞进一个所谓的“大事务”。

工作空间本身也最好具有明确的生命周期状态。

例如:

provisioning
active
suspended
deleting
deleted

注册事务完成以后,工作空间可以进入provisioning状态。

后台任务完成初始化以后变成active。

如果客户欠费,可以进入suspended。

删除流程则可以进入deleting,完成数据处理以后再进入deleted或者执行符合业务要求的保留策略。

这样前端和后台服务都不需要猜测“这个workspace到底有没有初始化完成”。

数据库还需要为这些核心关系建立正确索引。

例如:

CREATE UNIQUE INDEX
ON memberships(user_id, organization_id);

可以防止同一个用户在同一个组织产生重复membership。

而业务表通常需要类似:

CREATE INDEX
ON projects(organization_id, created_at);

如果经常根据组织和状态查询,还可以根据实际查询模式设计:

CREATE INDEX
ON projects(organization_id, status, created_at);

索引设计不能脱离真实SQL查询。SaaS数据库到了百万、千万甚至更大规模以后,最危险的事情之一就是所有查询都带着tenant条件,却没有相应索引,最终让数据库在大量数据中扫描寻找一个租户的记录。

还有一个经常被忽略的问题:密码不是AES-256加密保存。

用户密码应该使用专门的密码哈希算法,例如Argon2id或bcrypt,并保存password hash,而不是使用可以解密恢复原文的对称加密。AES适用于需要恢复原始数据的场景,例如某些API secret、第三方凭证等,但密码验证的设计原则不同。

API密钥、OAuth refresh token等敏感凭证则应该根据业务需要进行安全存储,并结合KMS、密钥轮换、访问控制和审计机制。数据库本身并不能替代应用层的密钥管理。

从系统架构来看,一个成熟的SaaS注册流程最终可以抽象成:

User Registration
↓
Authentication
↓
Create User
↓
Create Organization / Workspace
↓
Create Membership
↓
Assign Owner Role
↓
Commit Transaction
↓
Publish Outbox Event
↓
Async Provisioning
↓
Workspace Active

如果未来这个用户邀请同事加入,就不会重新创建一个工作空间,而是增加membership:

User A ── Owner ──┐
User B ── Admin ──┼── Organization X
User C ── Member ─┘

这套模型可以自然扩展到团队、部门、项目、计费和权限系统。

SaaS数据库设计最重要的原则,并不是“每个用户创建一个数据库”,而是把身份、租户、成员关系、角色和业务数据彻底分离。用户负责证明“我是谁”,Organization/Workspace负责定义“我属于哪个业务边界”,Membership负责定义“我以什么身份进入这个边界”,RBAC或更细粒度权限系统负责定义“我能做什么”,而业务表负责保存“这个组织拥有什么数据”。

当系统规模扩大以后,再根据客户数量、数据隔离要求、性能、合规、数据驻留和运维成本,在共享表、独立Schema、独立数据库和混合架构之间进行选择。这样设计出来的SaaS,注册一个用户只是创建租户关系的起点,而不是给每个新用户现场开一台数据库服务器。真正决定系统能否长期扩展的,是tenant boundary能否从数据库一直贯彻到缓存、文件、搜索、队列和权限系统。

喜欢这篇报道?

使用下面的功能,方便以后继续阅读和分享 MNewsTV

设为 Google 新闻首选来源 让 Google 新闻优先显示 MNewsTV 的最新报道 ›
★ 我的收藏 查看已经收藏的文章
关于文章收藏 收藏不需要注册帐号,收藏信息仅保存在当前浏览器中。 删除收藏请进入「我的收藏」进行管理。
分享这篇报道

捐助(Paypal): https://www.paypal.me/observeccp
订阅中国观察电报 Telegram : https://t.me/s/ObserveCCP