Django 5.0 本身不自动做读写分离,必须靠 DATABASES 多库配置 + 自定义 DatabaseRouter 类 + DATABASE_ROUTERS 注册三者配合,缺一不可;手动用 .using("slave") 是兜底手段,不是集成方案。

直接说结论:Django 5.0 本身不自动做读写分离,必须靠 DATABASES 多库配置 + 自定义 DatabaseRouter 类 + DATABASE_ROUTERS 注册三者配合,缺一不可;手动用 .using("slave") 是兜底手段,不是集成方案。
如何在 settings.py 中正确定义主从数据库别名
别名不能随便起,尤其是不能和 Django 内置别名(如 default)冲突,但更关键的是:所有别名必须在 DATABASES 字典里显式声明,且每个都要有完整连接参数。常见错误是只配了 slave 却漏掉 CONN_MAX_AGE 或 OPTIONS,导致连接复用失效或初始化失败。
-
default必须是写库(主库),哪怕你实际用master别名,也要确保路由逻辑最终把它映射为写目标 - 从库别名建议统一用
slave1、slave2这类可扩展命名,避免用read或ro——这些词可能被中间件或监控工具误识别 - MySQL 场景下,主从端口通常不同(如主库
3306,从库3307或8306),HOST可以相同(同机多实例)也可以不同(跨机器),但NAME必须一致(逻辑库名相同,否则同步会出问题) - SQLite 仅用于本地测试,
db.sqlite3和db_slave.sqlite3无法真正同步,切勿在生产环境用 SQLite 做读写分离验证
为什么 db_for_read / db_for_write 路由方法必须返回字符串别名
这两个方法的返回值会被 Django ORM 直接传给底层连接池,如果返回 None、False 或未声明的别名(比如写了 "slaver" 但 DATABASES 里只有 "slave"),Django 会抛出 ConnectionDoesNotExist 错误,而不是降级到 default。这不是容错机制,是硬性约束。
-
db_for_read返回的别名,必须对应一个只读或至少没被设为WRITE_ONLY的库(Django 不校验权限,但 MySQL 用户权限必须限制为SELECT) - 一主多从时,不要在
db_for_read里直接写死return "slave1",要用random.choice(["slave1", "slave2"])或加权重的轮询逻辑,否则流量全打在一个从库上 - 如果你的模型有
Meta.db_table = "shard_user_01"这类分表逻辑,db_for_write里可以基于model._meta.model_name或hints.get("instance")做动态路由,但注意hints不总包含instance
迁移命令必须对每个库单独执行
python manage.py migrate 默认只跑 default,从库不会自动同步表结构。遗漏这步,路由过去读的时候会报 no such table ——这个错误和 SQL 查询无关,纯粹是 schema 缺失。
立即学习“Python免费学习笔记(深入)”;
- 先跑主库:
python manage.py migrate --database default - 再跑从库:
python manage.py migrate --database slave1(注意别名要和DATABASES里一致) - 如果用了
django-migration-linter或类似工具,需确认它是否支持多库迁移校验;默认不支持,容易漏检从库的 migration 冲突 -
makemigrations只生成文件,不碰数据库,所以只需执行一次
最容易被忽略的是 allow_migrate 方法——它不参与运行时读写路由,但决定 migration 是否允许落到某个库。如果没实现或返回 False,migrate --database slave1 会静默跳过,表结构永远建不上。哪怕你只用一主一从,也建议在 router 里显式写上:def allow_migrate(self, db, app_label, model_name=None, **hints): return db in ["default", "slave1"]。



















