查字段是否被租户ID约束需先确认表中tenant_id字段存在且非空,再检查是否有索引、外键,查询中是否强制绑定;全局作用域仅对Eloquent生效,需在早期中间件设租户上下文,并用查询日志验证SQL是否含tenant_id条件。
查字段是否被租户ID约束
租户隔离失效,往往不是逻辑没写,而是某个表漏加了 tenant_id 字段,或者加了但没建索引、没加外键、没在查询中强制绑定。最直接的办法是翻数据库结构,而不是看代码里有没有 where('tenant_id', ...)。
实操建议:
- 用
SHOW CREATE TABLE users(MySQL)或\d users(PostgreSQL)确认表里是否存在tenant_id字段,类型是否为非空(NOT NULL) - 检查该字段是否有索引:
SHOW INDEX FROM users WHERE Column_name = 'tenant_id';没索引的tenant_id在大表 JOIN 或分页时会拖慢所有租户查询 - 确认是否设了外键指向
tenants表——没外键不报错,但数据可能残留脏租户记录 - 特别注意软删除表(如带
deleted_at的),tenant_id必须在WHERE条件里显式出现,不能依赖模型全局作用域自动注入(某些场景下作用域不生效)
Laravel 模型中 tenant_id 是否被强制作用于 every query
靠开发者每次手写 where('tenant_id', ...) 不可靠。Laravel 提供了全局作用域(Global Scopes)机制,但容易踩坑:作用域只对 Eloquent 查询生效,对 DB::table(...)->where(...) 或原生 SQL 无效。
实操建议:
- 在基类模型(如
TenantModel)中定义boot方法,用static::addGlobalScope(...)注入tenant_id条件 - 确保租户上下文在请求生命周期早期就已确定(比如中间件中调用
Tenant::setForRequest($id)),否则作用域读到的是空值或旧租户 - 对需要跨租户操作的场景(如后台管理),必须显式移除作用域:
User::withoutGlobalScopes()->get(),不能靠注释掉作用域代码来“临时绕过” - 验证是否生效:开启查询日志(
DB::enableQueryLog()),执行一个简单User::first(),看生成的 SQL 是否含AND `tenant_id` = ?
迁移文件里 tenant_id 字段是否被正确声明
很多项目初期加了 tenant_id,但迁移写成 $table->unsignedBigInteger('tenant_id') 却忘了加 ->nullable(false) 和 ->index(),后期补索引又忘了跑 php artisan migrate,导致线上字段存在但无约束。
实操建议:
- 迁移中必须明确指定非空和索引:
$table->foreignId('tenant_id')->constrained()->cascadeOnDelete()->index() - 如果用的是老版本 Laravel(foreignId() 可能不支持
cascadeOnDelete(),得手动写->references('id')->on('tenants')->onDelete('cascade') - 已有数据的表追加
tenant_id时,先用->nullable()加字段,再用DB::table(...)->update(...)补值,最后才改->nullable(false),否则迁移失败 - 别信 IDE 自动补全的“默认非空”——MySQL 默认允许 NULL,除非你写了
NOT NULL
多数据库方案下 tenant_id 是否多余
用独立数据库(per-tenant DB)时,有人觉得不需要 tenant_id 字段。这没错,但容易忽略连接切换时机问题:如果租户识别晚于模型首次加载(比如在控制器里才解析 subdomain),Eloquent 可能已经从默认连接查了数据。
实操建议:
- 连接切换必须在服务提供者(如
AppServiceProvider::boot)或中间件早期完成,且要覆盖所有可能触发模型加载的路径(包括队列任务、Artisan 命令、API 资源构造) - 即使不用
tenant_id字段,也建议在核心模型中保留该字段并设为fillable = [],防止未来切回共享库模式时漏改 - 检查
config/database.php中的prefix配置——不同租户共用前缀会导致表名冲突,应改为动态拼接(如tenant_123_users)或彻底分离连接配置 - 测试时别只测 HTTP 请求,用
php artisan tinker手动模拟不同租户上下文,验证模型是否真的连到了对应库
最容易被忽略的点是:租户上下文丢失往往不报错,只是查到了别人的数据。上线前务必用两个租户账号交叉操作,直接查数据库验证每张表的 tenant_id 值是否严格隔离。











