分库分表前须确认三件事:django版本≥4.2、主键含路由信息(如user_id)、已弃用syncdb类命令;中间件动态切库需安全重绑connections;using()优先级最高,路由器次之,中间件仅预热连接;分表逻辑应收口至manager层,但需防范并发表名错乱。

分库分表前必须确认的三件事
直接上中间件动态切库,大概率会踩坑。先确认:Django 版本是否 ≥ 4.2(django.db.connections 的 ensure_defaults 行为有变化);业务主键是否天然带路由信息(比如 user_id、tenant_id);是否已弃用 syncdb 类命令——分库后 manage.py migrate 默认只作用于 default 库,必须显式指定 --database=xxx。
用中间件拦截请求并切换 django.db.connections
核心不是“替换数据库配置”,而是运行时修改连接别名指向的实际连接对象。Django 的 connections 是线程局部的字典,中间件里可安全重绑:
from django.db import connections
from django.utils.deprecation import MiddlewareMixin
<p>class DatabaseRouterMiddleware(MiddlewareMixin):
def process_request(self, request):</p><h1>假设从 JWT 或 header 提取 tenant_id</h1><pre class="brush:python;toolbar:false;"> tenant_id = request.META.get("HTTP_X_TENANT_ID")
if not tenant_id:
return
db_alias = f"tenant_{tenant_id}"
# 确保该 alias 已在 settings.DATABASES 中定义
if db_alias not in connections:
# 动态注册连接(仅限测试/简单场景,生产建议预定义)
from django.db.utils import ConnectionHandler
connections.databases[db_alias] = {
"ENGINE": "django.db.backends.postgresql",
"NAME": f"myapp_tenant_{tenant_id}",
"USER": "...",
"PASSWORD": "...",
"HOST": "...",
"PORT": "5432",
}
# 强制刷新该 alias 的连接实例(避免复用旧连接)
if db_alias in connections:
connections[db_alias].close()
del connections[db_alias]
⚠️ 注意:connections[db_alias] 是懒加载的,首次访问才初始化;中间件里不能只改 connections.databases,必须触发重建或显式 del + 让后续 ORM 自动重建。
using() 和 db_for_read/write 路由器哪个更可控
中间件只负责“连接准备”,具体查哪张表还得靠路由逻辑。二者不互斥,但优先级不同:
-
using()最高:显式调用MyModel.objects.using("tenant_123").all()会绕过所有路由器,直连指定库 - 路由器次之:在
settings.DATABASE_ROUTERS中定义的类,靠db_for_read()/db_for_write()返回别名 - 中间件最低:它只是让某个别名“可用”,不决定 ORM 用不用它
推荐组合:中间件预热连接 + 路由器根据 request.tenant_id(需在中间件中挂载到 request)返回对应别名。避免在每个 QuerySet 上硬写 using,易漏且难维护。
分表逻辑必须收口到 Manager 层
同一库内分表(如 order_202401、order_202402)无法靠 Django 内置机制解决,必须自定义 Manager:
class OrderManager(models.Manager):
def get_queryset(self):
# 根据当前时间或 order_no 推导表名后缀
table_suffix = timezone.now().strftime("%Y%m")
self.model._meta.db_table = f"order_{table_suffix}"
return super().get_queryset()
<p>class Order(models.Model):
objects = OrderManager()</p><h1>注意:此法要求所有分表结构完全一致,且 migration 需手动适配</h1>
⚠️ 关键限制:_meta.db_table 是类属性,修改后会影响**所有实例**。如果并发请求跨月(如 1.31 和 2.1 同时进来),可能引发表名错乱。更稳妥做法是重写 get_queryset() 中的 query 对象的 model 属性,或使用第三方库如 django-sharding。
分库分表真正难的不是切库动作本身,而是事务边界、跨库 JOIN、全局唯一 ID、以及迁移脚本如何适配多库——这些没法靠中间件一劳永逸,得按业务收敛到具体模型和查询路径里。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











