Django数据库路由器是通过实现DatabaseRouter类的四个方法来实现读写分离的机制,解决高并发下主从数据不一致问题:db_for_read返回从库(排除auth等内置表),db_for_write强制主库,allow_relation允许同组库关联,allow_migrate限制迁移仅在default执行。

什么是 Django 数据库路由器,它解决什么问题
数据库读写分离在高并发场景下是刚需,Django 本身不自动区分读/写操作,但通过 DatabaseRouter 类可以干预每个查询发往哪个数据库实例。关键不是“配多个数据库”,而是让 SELECT 尽量走从库,INSERT/UPDATE/DELETE 强制走主库——否则数据不一致风险极高。
常见错误是只在 DATABASES 配了多个库,却没写路由器,结果所有请求仍默认打到 default;或者路由器里漏判了 using 参数,导致 .using('slave') 调用被覆盖。
如何编写一个最小可用的读写分离路由器
必须实现四个方法:db_for_read、db_for_write、allow_relation、allow_migrate。其中前两个是核心,后两个影响关联查询和迁移行为,不能留空或直接返回 None。
-
db_for_read:返回从库别名(如'slave'),但需排除auth、contenttypes等 Django 内置表——它们通常不复制,查从库会报Table doesn't exist -
db_for_write:一律返回'default'(主库),避免写操作误入从库 -
allow_relation:跨库外键或select_related时必须显式允许,否则 ORM 拒绝执行,典型返回True当两边都是default或slave -
allow_migrate:迁移只允许在default上执行,否则从库 schema 落后,后续读取字段会出错
示例片段:
立即学习“Python免费学习笔记(深入)”;
class ReadWriteRouter:
def db_for_read(self, model, **hints):
if model._meta.app_label in ('auth', 'contenttypes', 'admin', 'sessions'):
return 'default'
return 'slave'
def db_for_write(self, model, **hints):
return 'default'
def allow_relation(self, obj1, obj2, **hints):
db_set = {'default', 'slave'}
if obj1._state.db in db_set and obj2._state.db in db_set:
return True
return None
def allow_migrate(self, db, app_label, model_name=None, **hints):
return db == 'default'
settings.py 中配置多数据库与路由的陷阱
数据库配置顺序和键名直接影响路由器行为。Django 不校验 slave 是否真实存在,只看你返回的字符串是否在 DATABASES 字典里——拼错就静默 fallback 到 default,极难排查。
-
DATABASES必须包含'default'和至少一个从库(如'slave'),且从库配置中HOST、PORT、NAME要真实可连,建议加TEST配置避免单元测试连错库 -
DATABASE_ROUTERS是列表,不是字符串,写成['myapp.router.ReadWriteRouter']而非'myapp.router.ReadWriteRouter',否则启动报TypeError: 'str' object is not iterable - 从库连接池要单独配置,比如用
django-db-geventpool替换PASSWORD后的CONN_MAX_AGE,否则长连接堆积在从库上,主库反而空闲
手动指定数据库时如何绕过路由器
某些场景需要强制走特定库,比如后台导出报表必须查主库保证实时性,这时用 .using('default') 可以跳过 db_for_read 的判断。但要注意:一旦用了 .using(),该 QuerySet 所有后续链式调用(包括 select_related)都锁定该库,不会重新进路由器。
-
User.objects.using('default').filter(...)→ 强制主库读 -
User.objects.using('slave').filter(...)→ 强制从库读,但若路由器未放行该模型(如上面的auth判断),会抛ValueError: Cannot use 'slave' as database alias - 事务内禁止跨库操作,
with transaction.atomic(using='default'):块里调用.using('slave')会触发TransactionManagementError
最易忽略的是缓存层——如果用了 cache_page 或手动 cache.set(),而缓存键没包含数据库别名,主从切换后可能读到过期数据。这点没有银弹,得在业务逻辑里显式控制。



















