中小型系统最实际的方案是database-per-tenant(每个租户独立数据库),因其天然规避SQL注入误查、WHERE漏写tenant_id、权限错配等高频事故,而共享表模式虽省事但70%数据越界问题源于此。

租户数据隔离选哪一种方案最实际
直接说结论:中小型系统优先用 database-per-tenant(每个租户独立数据库),不是因为“高大上”,而是它天然规避了 SQL 注入误查其他租户、WHERE 条件漏写 tenant_id、权限配置错位等高频事故。共享数据库+共享表(即只靠 tenant_id 字段区分)看着省事,但上线后 70% 的数据越界问题都出在这儿。
database-per-tenant 方案下如何动态切换 PDO 连接
核心是不让 PDO 实例全局复用,每次请求根据租户标识(如子域名、请求头、JWT 声明)生成专属连接。别碰 mysql_select_db() 那套老逻辑——它不安全且无法跨库 JOIN。
- 从请求中提取租户标识,例如
$_SERVER['HTTP_HOST']解析出acme.example.com→ 租户名acme - 连接字符串拼成
mysql:host=localhost;dbname=tenant_acme;charset=utf8mb4,注意库名必须做白名单校验(正则/^[a-z0-9_]+$/),防止路径遍历或 SQL 注入式库名 - 绝不缓存
PDO实例到静态变量或容器单例里;若用 Laravel,需在DatabaseManager中重写makeConnection(),传入动态database配置项 - 连接失败时明确抛出
InvalidArgumentException而非静默 fallback 到默认库,避免租户 404 却返回他人数据
共享表模式下 WHERE tenant_id 漏加怎么防
靠人写代码永远会漏。必须把 tenant_id 绑定进查询生命周期,而不是靠开发自觉加条件。
- 使用 Doctrine 或 Eloquent 时,全局注册
GlobalScope,所有模型查询自动注入where('tenant_id', $current_tenant_id);注意 scope 必须支持排除(如管理员后台查全量时用withoutGlobalScopes()) - 原生 SQL 查询一律走封装函数,例如
db_query($sql, $params, $tenant_id),内部自动 prependAND tenant_id = ?并把$tenant_id塞进$params末尾 - 禁止在 migration 或 seed 中硬编码
tenant_id;租户专属数据初始化必须走租户上下文触发,比如Tenant::find('acme')->seedDefaultPages() - 用 MySQL 8.0+ 的
ROW POLICY做最后兜底(需开启check_constraint和row level security),但 PHP 层不能依赖它——策略失效时你不会收到 PHP 异常
租户元数据怎么存才不扯皮
必须单独建一个无租户归属的「系统库」,里面只放租户注册信息,其他所有业务库都不存 tenants 表。否则迁移、备份、审计全乱套。
- 系统库表结构极简:
id(主键)、slug(用于 URL/DB 名)、db_host、db_name、status(active/archived),不存任何业务字段 - 租户开通时,PHP 脚本执行
CREATE DATABASE tenant_{$slug}+ 导入基础 schema(用mysqldump --no-data提前导出模板),而非靠 ORM migrate 自动建表——后者容易因版本差异导致结构不一致 - 删除租户不是
DROP DATABASE就完事;先停写流量(改 DNS 或 API 网关路由),再异步归档数据(mysqldump > /archive/tenant_xxx_202405.sql.gz),最后删库。跳过归档=违反 GDPR 类合规要求
真正麻烦的从来不是“怎么切库”,而是租户间资源争抢(比如共用 Redis 连接池导致 key 冲突)、日志混杂、监控指标没按租户维度聚合——这些在第一个租户上线前就得想清楚,不然第二十个租户进来时,你得重写整个中间件层。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











