django多租户最稳妥方案是django-tenants+postgresql独立schema,需前置配置installed_apps顺序、区分shared_apps/tenant_apps、用migrate_schemas分步迁移,并通过中间件绑定租户上下文贯穿请求全链路。

Django 本身不原生支持多租户,直接用 django.contrib.auth 或默认 ORM 模型做租户隔离,大概率会在数据隔离、路由分发、中间件上下文这几个环节出问题——尤其是当租户共享数据库时,漏掉一个 tenant_id 过滤或错配了 schema 切换逻辑,就可能查到别人的数据。
用 django-tenants 实现共享数据库 + 独立 Schema
这是目前最稳妥的方案,每个租户拥有独立的 PostgreSQL schema,Django 的模型自动绑定到当前 schema,天然避免跨租户查询。但必须用 PostgreSQL(MySQL 不支持)。
实操要点:
- 安装后需在
INSTALLED_APPS中把django_tenants放在最前面,否则其AppConfig无法劫持模型注册流程 - 所有租户共用的模型(如
Tenant、Domain)必须放在SHARED_APPS;租户私有模型(如Invoice、Project)只能在TENANT_APPS中声明 - 迁移命令要区分:公共表用
python manage.py migrate_schemas --shared,租户表用python manage.py migrate_schemas --tenant - 创建租户时,
schema_name只能是小写字母+数字+下划线,不能以数字开头,否则 PostgreSQL 会报invalid schema name
手动实现共享数据库 + 行级租户标识(Tenant ID)
适合已有系统改造,或必须用 MySQL 的场景。核心是让每张业务表都带 tenant_id 字段,并确保每次查询都自动加上该条件。
关键控制点:
- 所有模型继承一个基类,比如
TenantModel,其中定义tenant_id = models.PositiveBigIntegerField(),并重写get_queryset()方法注入.filter(tenant_id=xxx) - 不能依赖视图层手动加过滤——容易遗漏。要用 Django 的
QuerySet自定义管理器(objects = TenantAwareManager()),并在get_queryset()中读取当前请求的租户上下文(通常从中间件写入threading.local或asgiref.local.Local) - 外键关系必须显式指定
on_delete=models.CASCADE,否则 Django 默认的CASCADE行为在多租户下可能误删其他租户数据 -
bulk_create、update、delete这类批量操作极易绕过租户过滤,务必在封装方法里强制校验tenant_id
路由和中间件如何识别当前租户
租户识别失败,后面所有隔离逻辑都白搭。常见方式有三种:子域名(acme.example.com)、路径前缀(example.com/acme/)、请求头(X-Tenant-ID)。子域名最主流,但开发调试麻烦。
中间件实操建议:
- 子域名方式:解析
request.get_host(),提取第一段作为schema_name或tenant_slug,再查Domain表获取对应租户对象,存入request.tenant - 避免在中间件里做数据库查询——高并发下会拖慢所有请求。应缓存已查过的租户(例如用
django.core.cache.caches['default'],key 为域名) - 如果用了
django-tenants,必须在中间件中调用tenant_model.activate_tenant(request.tenant),否则 ORM 不会切换 schema - API 场景下,若用 JWT 认证,可在 token payload 里塞
tenant_id,中间件优先从 token 读, fallback 到域名
真正难的不是选哪种方案,而是租户上下文如何贯穿整个请求生命周期——从中间件进来到信号触发、异步任务、管理命令执行,稍有断点,数据就可能混在一起。尤其要注意 Celery 任务和 Django Q 队列,默认不携带 request 上下文,必须显式传参或通过 current_tenant 全局变量同步状态。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











