tenant_id必须加普通索引,常与created_at或deleted_at组成联合索引;laravel全局作用域可靠但需请求内租户上下文唯一且及时重置;队列任务须显式传参并重初始化上下文。

多租户场景下,tenant_id 字段加不加索引?
不加索引会直接拖垮查询性能,尤其当租户数据混存在一张表里、且 tenant_id 出现在 WHERE 或 JOIN 条件中时。
常见错误现象:单租户查 50ms,10 个租户并发查就飙到 2s+,EXPLAIN 显示全表扫描。
-
tenant_id必须加普通索引(INDEX tenant_id),不是唯一索引——除非你确定该字段本身业务上就唯一 - 如果常和时间范围一起查(比如查某租户最近订单),考虑联合索引:
INDEX tenant_id_created_at (tenant_id, created_at) - Laravel 的
withTrashed()或软删除字段deleted_at若参与过滤,也要纳入联合索引,否则索引失效
Laravel 中用 tenant_id 做全局作用域是否可靠?
可靠,但必须配合「请求生命周期内租户上下文唯一」这个前提,否则会漏过滤或错过滤。
使用场景:后台管理多个租户、SaaS 控制台、API 网关透传租户标识后分发请求。
PHP中文网提供Laravel 13.2.0版本下载,Laravel框架 是基于 PHP 8.3+ 的高性能框架,官方推荐通过 Composer 安装。它内置 AI SDK、JSON:API Resources 及原生向量搜索,支持属性驱动开发与队列路由,大幅提升开发效率。相比旧版,13.2.0 优化了缓存 TTL 管理与实时通信,无需 Redis 即可横向扩展。作为现代 Web 开发首选,它兼顾安全与极速体验,助您快速构建企业级应用。
- 在中间件里解析
tenant_id(从子域名、请求头、JWT payload 或路由参数),存入 Laravel 的request()->attributes或自定义TenantManager单例 - 全局作用域里读取该上下文,而不是从 session 或 auth 用户里硬取——session 可能跨租户复用,auth 用户未必绑定租户
- 务必在每个 HTTP 请求开始时重置租户上下文,避免 FPM 进程复用导致上一个请求的
tenant_id污染下一个
DB::connection('tenant') 切换连接 vs 多数据库隔离,怎么选?
切换连接适合租户数少(
性能与兼容性影响明显:
- 切换连接:Laravel 连接池无法复用不同租户的连接,
DB::reconnect()频繁触发会增加握手开销;迁移、工厂、测试桩都得动态适配连接名 - 多数据库:每个租户一个库,
.env无法配置,必须运行时调用Config::set('database.connections.tenant.database', $dbName);注意 MySQL max_connections 限制 - 不要用
use Database\MyTenantSchema这类硬编码库名的方式——一旦租户名含特殊字符或大小写,就会报SQLSTATE[42000]
租户数据误查/越权访问最常出在哪?
不在模型层,而在关系加载、原始查询、事件监听器和队列任务里。
典型错误现象:用户 A 能看到用户 B 所属租户的订单列表,或队列里处理了错误租户的邮件模板。
-
with('orders')不会自动继承父模型的租户过滤,必须显式加约束:->with(['orders' => fn ($q) => $q->where('tenant_id', tenant()->id)]) - 用
DB::table()写原生查询时,90% 的人会忘记手动加WHERE tenant_id = ? - 队列任务里不能依赖闭包捕获的
$tenantId,因为反序列化时上下文已丢失——必须把tenant_id当作参数传进去,并在handle()开头重新初始化租户上下文
租户隔离不是加个字段、套个作用域就完事;真正的难点在所有隐式执行路径是否都被覆盖,而这些路径往往只在压测或审计时才暴露出来。










