必须在databases中定义'default'(主库)和'replica'(从库)等唯一别名,'default'不可删除且须支持读写;需自定义databaserouter实现db_for_read、db_for_write、allow_relation、allow_migrate四个方法,并在settings.py中注册database_routers,否则读写分离不生效。

配置 DATABASES 字典时必须区分 primary 和 replica
Django 本身不自动做读写分离,得靠 DATABASES 配置 + 路由类配合。核心是把主库('default')设为写库,再额外加一个或多个只读库(比如 'replica'),名称不能叫 'default',否则路由逻辑会失效。
常见错误是把 replica 也命名为 'default',或者漏掉 'TEST' 配置导致测试时连错库:
-
'default'必须支持读写,且'NAME'和'HOST'指向主库实例 -
'replica'的'USER'应该是只读账号,'OPTIONS'建议加{'read_only': True}(虽不强制,但能提醒后续代码别误写) - 测试数据库名要用
'TEST': {'NAME': 'test_replica_db'}显式指定,否则 Django 可能复用主库 test 名,造成测试污染
自定义 DatabaseRouter 必须实现四个方法
只重写 db_for_read 是不够的——Django 在迁移、事务、关联查询等场景下会调用 db_for_write、allow_relation、allow_migrate。少一个就可能报 django.core.exceptions.SuspendedOperation 或静默连错库。
典型路由逻辑如下(放在 router.py 中):
class PrimaryReplicaRouter:
def db_for_read(self, model, **hints):
return 'replica'
<pre class="brush:python;toolbar:false;">def db_for_write(self, model, **hints):
return 'default'
def allow_relation(self, obj1, obj2, **hints):
db_set = {'default', 'replica'}
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):
if db == 'replica':
return False
return True
注意:allow_migrate 返回 False 表示“禁止在 replica 上执行 migrate”,否则 python manage.py migrate --database=replica 会尝试建表,而 replica 往往没 DDL 权限。
QuerySet 执行读操作时不会自动走 replica
写了路由类 ≠ 自动分流。Django 默认所有 Model.objects.all()、get()、filter() 都走 'default',除非显式指定 .using('replica')。
容易踩的坑:
- 忘记在视图或服务层加
.using('replica'),导致读请求全压主库 - 在事务中混用
.using('replica')和.using('default'),触发DatabaseError: Transaction managed block must use same database - 使用
select_related()或prefetch_related()时,关联表若没统一指定库,Django 会 fallback 到'default',造成跨库 join 失败(MySQL 不支持)
安全做法:对确定只读的接口,统一封装一层:
def get_user_list():
return User.objects.using('replica').all()
连接池和超时参数需按主从角色差异化设置
主库连接要短平快,replica 可容忍稍高延迟。如果共用同一套 'CONN_MAX_AGE' 和 'OPTIONS',容易出现 replica 连接空闲过久被中间件断开,而主库又因长连接积压太多连接数。
推荐配置差异:
- 主库(
'default'):'CONN_MAX_AGE': 0(禁用持久连接),'TIMEOUT': 5 - 副本(
'replica'):'CONN_MAX_AGE': 60,'TIMEOUT': 10,并加'OPTIONS': {'max_allowed_packet': 64 * 1024 * 1024}(应对大结果集) - 所有库都应配
'ATOMIC_REQUESTS': False,避免自动事务把只读请求也拖进事务上下文
真实环境中,replica 网络延迟波动大,'TIMEOUT' 设太低会导致大量 OperationalError: (2013, "Lost connection to MySQL server during query"),但设太高又拖慢整体响应——得结合监控调整。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











