必须在settings.py中定义'default'(主库)和'replica'(从库)两个不同别名,显式配置engine、name、host等参数;'default'不可删除,主从host和账号权限须区分;自动读写分离需实现databaserouter的db_for_read、db_for_write、allow_migrate三方法并注册到database_routers。

必须手动配置 DATABASES 字典和 DatabaseRouter,Django 不会自动识别主从语义,也不支持靠字段名(如 READ_ONLY)触发分流。
如何在 settings.py 中正确定义主库与从库?
Django 要求 'default' 别名必须存在且不可删除,哪怕你从不直接用它——迁移、createsuperuser 等管理命令默认都走这个别名。主从库必须用不同别名,例如 'default'(主)和 'replica'(从),不能都叫 'default',否则后定义的会覆盖前一个。
- 每个库的
ENGINE、NAME、HOST、PORT、USER、PASSWORD都要显式写出,空字符串或未解析的环境变量会导致连接失败 -
OPTIONS里加{'read_only': True}是无效的:Django 完全忽略未声明的配置项,它只认自己文档里列出的 key - 主从库的
NAME通常相同(因为是复制关系),但HOST和权限账号必须区分:主库用读写账号,从库用只读账号(如ro_user)
为什么写了 .using('replica') 还是查到主库?
手动调 .using() 只影响当前 QuerySet 的主表查询,对关联操作完全无效。这是最常被忽略的坑。
-
Book.objects.using('replica').select_related('author')中,author查询仍发往default - 事务块内(
transaction.atomic())所有操作强制绑定到同一个库,即使写了.using('replica')也会被忽略 -
save(using='replica')是危险操作:它绕过路由逻辑,可能把写请求发到只读从库,导致 MySQL 报错ERROR 1290 (HY000): The MySQL server is running with the --read-only option
如何实现可靠、可维护的自动读写分离?
靠到处写 .using() 不可持续,必须用 DatabaseRouter。核心是三个方法,缺一不可:
-
db_for_read(model, **hints):返回从库别名(如'replica'),可按模型名做细粒度控制(比如报表类模型走'analytics') -
db_for_write(model, **hints):几乎总是返回'default',确保写操作不误入从库 -
allow_migrate(db, app_label, model_name=None, **hints):必须只对db == 'default'返回True,否则执行python manage.py migrate --database replica会成功但无意义,而migrate不带--database时又可能报错
路由类需在 settings.py 中注册:DATABASE_ROUTERS = ['myapp.db_router.ReplicaRouter'];注意路径要能被 Django 导入,文件名不能是 router.py 这种太泛的名字,容易和其它模块冲突。
验证读请求是否真走从库?别信日志,要看连接来源
Django 日志里显示的数据库名是别名(如 default),不是真实 HOST,所以不能靠它判断。真正有效的方法只有两个:
- 在从库执行
SHOW PROCESSLIST,观察是否有来自应用服务器的新连接(看Host列) - 在路由的
db_for_read方法里加print(f"READ → {db}"),配合一次请求触发并看终端输出——但上线前必须删掉,不能留 print
Admin 页面默认全部走 default,如果想让它也读从库,必须重写对应 ModelAdmin 的 get_queryset(self, request),并在里面显式调 .using('replica')。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











