所有共享业务数据表(如orders、products、customers)必须添加tenant_id字段并强制过滤,租户元数据表(tenants等)不加;字段需NOT NULL、加索引;PHP层应通过全局作用域或查询构建器自动注入tenant_id,确保读写隔离。

在 PHP 多租户平台中,为数据库表添加 tenant_id 字段是实现数据隔离的关键一步。核心原则是:**所有共享表(非系统全局表)都应包含 tenant_id,且该字段需参与查询过滤和写入约束**。
哪些表需要加 tenant_id?
不是所有表都要加,重点是「业务数据表」:
- 用户订单表(
orders)、商品表(products)、客户信息表(customers)——必须加 - 租户自身元数据表(如
tenants、tenant_settings)——不加,它们按租户主键区分 - 系统日志、审计表(如
system_logs)——可选加,若需按租户归档则加,否则可不加 - 权限相关表(如
roles、permissions)——视模式而定:若权限体系租户内独立,则加;若全平台统一,则不加
字段定义与索引建议
tenant_id 通常为整型或 UUID,需与租户标识方式一致。推荐定义如下(以 MySQL 为例):
ALTER TABLE orders ADD COLUMN tenant_id BIGINT UNSIGNED NOT NULL, ADD INDEX idx_tenant_id (tenant_id);
说明:
- 类型建议用
BIGINT UNSIGNED(对应自增 ID 租户)或CHAR(36)(对应 UUID 租户),避免使用VARCHAR增加索引开销 - 务必设为
NOT NULL,禁止空值导致隔离失效 - 必须加普通索引;高频按租户查的场景(如后台租户看板),可考虑联合索引,例如
(tenant_id, created_at)
PHP 层如何自动注入 tenant_id?
不能靠开发人员每次手写 WHERE tenant_id = ?,应在框架层统一拦截:
- Laravel:利用 Eloquent 的 全局作用域,在
boot()中强制添加where('tenant_id', tenant()->id) - ThinkPHP:通过模型的
scope或中间件在查询前调用where('tenant_id', app('tenant')->id) - 原生 PDO / 自研 ORM:封装
QueryBuilder,重写select()、insert()等方法,在 SQL 构建阶段自动补全tenant_id条件或值 - 写操作也要校验:插入/更新时,显式设置
tenant_id值(不可依赖前端传入),并校验当前请求租户是否有权操作该租户上下文
迁移历史表与存量数据处理
已有生产表加 tenant_id 需谨慎:
- 先确认该表当前是否已实际支持多租户(即数据是否混存),若尚未启用多租户,可将全部存量数据标记为默认租户(如
tenant_id = 1) - 执行
ALTER TABLE ... ADD COLUMN时,大表注意锁表时间;MySQL 5.6+ 可用ALGORITHM=INPLACE减少影响 - 补充数据后,立刻在应用层启用全局过滤,防止新老逻辑混用导致数据越界
- 建议配合数据库行级安全策略(如 PostgreSQL 的 RLS)或应用层熔断开关,上线初期监控
tenant_id IS NULL的异常写入
不复杂但容易忽略的是:加了字段≠完成隔离。关键在查询路径每一步(包括关联查询、子查询、软删除 scope)都必须携带 tenant_id 过滤,否则一个漏洞就可能泄露其他租户数据。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











