必须写databaserouter类并注册到database_routers,否则所有查询永远只走default;'default'必须存在且配置完整,db_for_read和db_for_write返回值须精确匹配databases中键名,否则抛connectiondoesnotexist。

必须写 DatabaseRouter 类并注册到 DATABASE_ROUTERS,否则所有查询永远只走 default。
settings.py 里 DATABASES 配置的硬性规则
Django 不识别“主库”“从库”语义,只认你写的别名字符串。常见错误是配了 'slave' 却漏掉 'default' ——'default' 必须存在,哪怕你从不直接用它,迁移和管理命令默认都依赖它。
- 每个别名(如
'default'、'replica1'、'replica2')都要有完整连接参数:ENGINE、NAME、HOST、PORT、USER、PASSWORD,空字符串或未声明字段(比如'read_only': True)会被 Django 完全忽略 - MySQL 主从的
NAME必须一致,否则同步失效;PORT通常不同(主库 3306,从库 3307 或 8306) - SQLite 不能用于验证读写分离——
db.sqlite3和db_slave.sqlite3无法真正同步,仅限本地开发临时测试
db_for_read 和 db_for_write 返回值必须精准匹配 DATABASES 键名
这两个方法返回的字符串,会直接传给连接池。返回 None 就 fallback 到 default;返回不存在的键(比如写了 'slaver' 但 DATABASES 里只有 'replica1'),就会抛 ConnectionDoesNotExist 错误,而不是降级。
-
db_for_write几乎总是返回'default'(或你命名的主库别名),不能轮询或随机 -
db_for_read一主多从时,别写死return 'replica1',要用random.choice(['replica1', 'replica2'])或加权重的轮询逻辑,否则流量全压一台 - 事务中读操作要谨慎:如果当前在
transaction.atomic()块里,db_for_read最好返回None或当前using值,避免从库读导致一致性问题
allow_migrate 必须显式禁止从库执行迁移
迁移命令 python manage.py migrate 默认只跑 default,但当你注册了路由后,Django 会对每个数据库调用 allow_migrate。若没控制,它可能尝试在只读从库上执行 DDL,直接报错 OperationalError: cannot execute INSERT in a read-only transaction。
- 正确写法是:当
db != 'default'时返回False,表示“这个迁移不该在这库跑” - 不要无条件
return True,也不要return None(这会让 Django 继续用默认逻辑,但多库环境下不可靠) - 如果某些 app 的表只存在于特定库(比如分析报表只在
'analytics'),可在allow_migrate里按app_label分流,但需确保对应库已提前建好 schema
路由类没生效?先查这三个地方
最常被忽略的是注册环节:Django 启动时不校验 DATABASE_ROUTERS 路径是否真实可 import,路径错、类名拼错、模块不存在,都会静默失败——所有查询照常走 default,毫无提示。
-
DATABASE_ROUTERS = ['myapp.routers.PrimaryReplicaRouter']必须是字符串路径,不是类实例,且引号不能漏 - 路由类必须放在能被 import 的位置(比如
myapp/routers.py),不能放在views.py或临时脚本里 -
.using('replica1')优先级高于路由,调试时手动指定会绕过db_for_read,容易掩盖配置问题
真正麻烦的从来不是配多个库,而是让每个 QuerySet 在该走哪的时候,不靠人眼盯、不靠注释提醒,就自动走对——这取决于路由类返回的字符串和 DATABASES 字典键名之间那零点几毫米的精确匹配。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











