django-tenants仅支持postgresql,因其依赖create schema语句和search_path动态切换实现租户隔离;mysql无schema概念,sqlite不支持search_path,故无法运行。

直接用 django-tenants 实现多租户,不是不行,但默认的共享数据库+独立 Schema 方案在 PostgreSQL 上能跑通,在 MySQL 或 SQLite 上根本不可用——它压根不支持。
为什么 django-tenants 只认 PostgreSQL?
该库依赖 PostgreSQL 的 CREATE SCHEMA 语句隔离租户数据,所有迁移、表创建、查询都通过动态切换 search_path 实现。MySQL 没有 schema 隔离概念(database ≠ schema),SQLite 更是连 SET search_path 都不支持。
常见错误现象:
- 运行
python manage.py migrate_schemas报错:OperationalError: (1064, "You have an error in your SQL syntax...") - 启动时提示:
DatabaseWrapper does not support schema creation - 租户切换后查到其他租户的数据(隔离失效)
如果你非要用 MySQL,得换方案:要么手动前缀分表(tenant_xxx_user),要么用独立数据库(每个租户一个 DB),但后者会让 django-tenants 的中间件和路由逻辑大量失效,得自己重写 TenantMiddleware 和 get_tenant_model() 行为。
PostgreSQL 下启用 django-tenants 的关键三步
跳过文档里模糊的“配置 DATABASES”,重点只做三件事:
- 在
settings.py中把SHARED_APPS和TENANT_APPS分清楚:公共表(如auth.User、contenttypes)进SHARED_APPS;租户专属模型(如products.Product)进TENANT_APPS - 确保
PGDATABASE是空库(或只有publicschema),首次运行python manage.py migrate_schemas --shared才会建好publicschema - 租户域名必须显式注册:在
tenant_model.objects.create(domain_url="acme.localhost", schema_name="acme")后,还要把acme.localhost加进ALLOWED_HOSTS,否则中间件直接 400
示例片段:
# settings.py TENANT_MODEL = "customers.Client" TENANT_DOMAIN_MODEL = "customers.Domain" <p>SHARED_APPS = [ 'django.contrib.contenttypes', 'django.contrib.auth', 'django.contrib.sessions', 'customers', # Tenant model 定义在这里,但属于 shared ] TENANT_APPS = [ 'django.contrib.contenttypes', 'django.contrib.auth', 'sales', 'invoicing', ]</p>
sync_tenant 和 migrate_schemas 到底怎么用?
这两个命令常被混用,但作用完全不同:
-
python manage.py migrate_schemas --shared:只对publicschema 运行迁移(比如改Client表结构) -
python manage.py migrate_schemas --tenant:对所有已存在租户的 schema(如acme、beta)运行TENANT_APPS的迁移 -
python manage.py sync_tenant:已废弃,Django 3.2+ 不再兼容;别用,文档没及时更新
新增租户后,必须先 save(),再手动调用 tenant.auto_create_schema = True 并 tenant.save(),否则 migrate_schemas --tenant 不会识别它。
租户请求路由失败的典型原因
访问 acme.localhost 却进了 public schema,大概率是以下某个环节断了:
-
DOMAIN_MODEL的domain_url字段值带http://(必须纯域名,如acme.localhost) - HTTP Host 头被反向代理(Nginx / Docker)修改过,Django 拿到的是
localhost:8000,不是请求的真实域名 -
TenantDomainRouter没在MIDDLEWARE里,或位置错了(必须在SessionMiddleware之后、CommonMiddleware之前)
调试时加一行日志到 get_tenant() 方法里,打印 request.get_host() 和查出的 domain 对象,比猜快得多。
最易被忽略的一点:所有需要跨租户操作的管理命令(比如导出全量订单),不能直接用 Model.objects.all() —— 它只会查当前 schema。必须显式用 connection.set_schema_to_public() 切出去,或者用原始 SQL 绑定 public schema。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











