Django原生不支持多租户,最稳妥方案是django-tenants+PostgreSQL独立schema,需严格配置SHARED_APPS/TENANT_APPS、分步迁移,并通过中间件透传租户上下文贯穿全链路。

Django 本身不支持原生多租户,硬套 django.contrib.sites 或手动在每个查询加 tenant_id 过滤极易出错,真正可用的方案只有两类:共享数据库+共享表(靠字段隔离)或共享数据库+独立表(靠表名前缀/后缀),前者更主流也更可控。
用 django-tenants 实现共享数据库 + 独立 Schema(PostgreSQL 推荐)
这是目前最接近“标准解”的方案,依赖 PostgreSQL 的 schema 隔离能力,每个租户拥有独立的 schema,Django 模型无需改写,路由自动切换。
- 必须用 PostgreSQL ——
django-tenants的 schema 模式不支持 MySQL 或 SQLite - 安装后需在
settings.py中替换DATABASES配置为TENANT_APPS和SHARED_APPS两组,并设置TENANT_MODEL = "customers.Client" - 主域名访问走 public schema(放租户元数据),子域名如
acme.example.com自动路由到schema="acme",这个行为由中间件TenantMiddleware控制 - 注意迁移命令要区分:
python manage.py migrate作用于 public,python manage.py migrate_schemas --shared或--tenant才能分别操作共享表和租户表
用 tenant-schemas 后续分支或 django-tenants + 字段隔离(通用但需自律)
如果只能用 MySQL,或想最小化 DB 权限(避免 CREATE SCHEMA),就得退回到“单库单表+字段隔离”模式——所有租户数据混存在同一张表里,靠 tenant_id 字段过滤。
- 每个模型必须显式继承
TenantModel(来自tenant_schemas分支)或手动加tenant = models.ForeignKey("Tenant", on_delete=models.CASCADE) - 查询时不能直接写
User.objects.filter(...),必须用User.objects.filter(tenant=current_tenant, ...);漏掉tenant条件等于数据越界 - 推荐配合自定义 Manager,例如
TenantManager重写get_queryset(),自动注入tenant_id过滤,但要注意objects.all()、objects.raw()、values()等绕过 Manager 的调用仍可能泄露数据 - Admin 后台需重写
get_queryset()并限制has_change_permission,否则超级用户能看到全部租户数据
为什么不要用 DATABASE_ROUTERS + 多数据库做多租户?
看似灵活,实则埋雷最多:每个租户配一个数据库连接,会迅速耗尽 PostgreSQL 连接池,且 Django 的 db_for_read/db_for_write 路由逻辑无法可靠绑定请求上下文中的租户身份。
立即学习“Python免费学习笔记(深入)”;
- HTTP 请求生命周期中,租户识别通常发生在中间件(如解析子域名),但 database router 是模块级加载的,无法动态感知当前请求的
tenant_id - 事务跨租户时(比如计费系统要同时读写多个租户),
using="xxx"必须显式传参,极易遗漏,引发写错库、读错表 - 迁移管理爆炸式增长:100 个租户 = 100 套 migration 表,
django_migrations在每个库都要维护,makemigrations输出混乱,CI/CD 几乎不可控
真正难的不是选哪个库,而是把租户上下文从入口(中间件或视图)稳稳地透传到 ORM 查询、缓存键、异步任务甚至日志标签里。哪怕用了 django-tenants,如果 Celery 任务里忘了 schema_context,或者 Redis 缓存没带 tenant_id 做命名空间,一样会串数据。


















