Django多租户数据库路由的核心机制是通过自定义DatabaseRouter类拦截db_for_read、db_for_write等方法,根据请求上下文(如子域名)动态返回settings.DATABASES中已注册的数据库别名,而非为每个租户建独立数据库。

什么是Django多租户数据库路由的核心机制
Django本身不内置多租户支持,但通过DatabaseRouter类可拦截db_for_read、db_for_write等方法,动态决定模型操作发往哪个数据库。关键不是“为每个租户建一个DB”,而是让路由逻辑能根据当前请求上下文(比如子域名、HTTP头或中间件注入的租户标识)返回对应settings.DATABASES中已注册的数据库别名。
如何在请求生命周期中安全传递租户标识
不能依赖全局变量或线程局部存储(threading.local)在异步视图或WSGI多线程环境下稳定工作;推荐用Django中间件+request.tenant属性绑定,并配合django.utils.functional.SimpleLazyObject延迟解析。常见错误是把租户ID直接塞进request.META或session,导致路由层拿不到或时序错乱。
- 在中间件中解析子域名(如
tenant1.example.com→tenant1),查出对应数据库配置名(如db_tenant1),存入request.tenant_db_alias - 确保该中间件在
SessionMiddleware之后、CommonMiddleware之前执行 - 路由类中避免直接访问
request——Django路由不接收请求对象;改用threading.local+ 中间件写入 + 路由类读取,但必须加try/except兜底未设置场景
DatabaseRouter实现里最容易踩的四个坑
自定义DatabaseRouter时,allow_migrate方法若返回False会导致迁移失败,而None才是“不干预”的正确值;另外,Django 4.2+ 对allow_relation更严格,跨数据库外键会直接报错。
-
db_for_read和db_for_write必须返回字符串(数据库别名),不能返回None,否则抛ValueError: Unknown database -
allow_migrate对app_label == 'auth'这类共享应用,应固定返回True或指定主库(如'default'),否则用户表无法同步 - 不要在路由方法里做数据库查询——会引发递归调用死循环
- 测试时用
python manage.py dbshell --database=xxx手动连目标库确认表结构,比单纯看迁移日志更可靠
为什么不能只靠DATABASES配置切换,还得改模型Meta选项
Django默认所有模型都注册到default库,即使路由返回其他别名,若模型没显式声明using参数或没设Meta.db_table前缀,仍可能因缓存或反射逻辑误操作主库。尤其当多个租户共用同一套模型定义时,表名隔离靠的是数据库层面分离,而非Django自动加租户前缀。
立即学习“Python免费学习笔记(深入)”;
- 在
settings.py中为每个租户预定义数据库配置,例如'db_tenant_a': { ... },别名必须全小写且不含特殊字符 - 所有租户专属模型需在
Meta中设db_table = 'tenant_a_user'等硬编码名,避免Django按app名自动生成 - 共享模型(如
User、Group)应在allow_migrate中强制指向'default',并在路由中跳过其db_for_read逻辑
真正难的不是写几行路由代码,而是保证中间件、路由、模型定义、迁移命令、测试环境四者节奏一致——少一个环节,租户数据就可能混库或丢写。上线前务必用python manage.py showmigrations --database=db_tenant_x逐个核对状态。



















