主从数据库配置必须用多个databases条目并配合databaserouter实现读写分离,仅修改default无效;需严格区分读写路由、限制迁移仅在default执行、健康检查须验证复制延迟而非仅连通性。

主从数据库连接配置必须用多个 DATABASES 条目,不能靠修改 default 别名实现切换
Django 的 DATABASES 配置是静态字典,运行时改 DATABASES['default'] 不会生效——ORM 初始化后就缓存了连接参数。真实切换必须依赖路由(DatabaseRouter)+ 多个命名 DB 条目,比如:'default'、'replica'、'standby'。否则写操作发到只读从库会直接报 OperationalError: cannot execute INSERT in a read-only transaction。
实操建议:
- 在
settings.py中明确定义至少两个 DB:一个'default'(主库),一个或多个'replica'(从库) - 所有 DB 都要配
ATOMIC_REQUESTS=False,避免事务跨库失败 - 从库的
OPTIONS建议加{'options': '-c default_transaction_read_only=on'}(PostgreSQL)或'init_command': 'SET SESSION TRANSACTION READ ONLY'(MySQL),让数据库层也强制只读
DatabaseRouter 必须区分读/写意图,且不能仅靠 using 参数判断
很多人误以为只要在 db_for_read 里返回 'replica' 就万事大吉,但 Django 在执行 .filter()、.get() 等查询时,如果模型用了 select_related() 或 prefetch_related(),可能触发隐式写操作(如外键缓存更新),导致从库报错。真正可靠的路由逻辑得结合当前请求上下文和查询类型。
实操建议:
- 重写
db_for_read和db_for_write,前者优先返回'replica',后者必须返回'default' - 在
allow_migrate中严格限制迁移只跑在'default',否则从库表结构不一致会引发数据错乱 - 对强一致性场景(如用户刚提交订单立刻查详情),用
Model.objects.using('default').get(...)显式指定主库,别依赖路由
健康检查和故障转移不能靠 ping,得模拟真实查询
只用 conn.cursor().execute('SELECT 1') 检查连接存活远远不够。主库可能网络通但事务卡死,从库可能复制延迟超 30 秒却仍能响应 SELECT 1。这时候切过去,用户看到的就是过期数据。
实操建议:
- 为每个从库单独加健康检查端点,执行类似
SELECT pg_last_wal_receive_lsn(), pg_last_wal_replay_lsn()(PostgreSQL)或SHOW SLAVE STATUS(MySQL),比对延迟值 - 把延迟阈值(如 5 秒)和连接成功率(连续 3 次失败才下线)写进路由逻辑,而不是全局开关
- 避免在
db_for_read里实时做健康检查——太慢,应由后台任务(如 Celery)定时探测并更新内存状态,路由只查缓存
事务内强制走主库,但 atomic 块里混用 using 会静默失败
Django 的 @transaction.atomic 默认绑定到 default 数据库。如果在事务块里调用 SomeModel.objects.using('replica').all(),Django 不报错,但实际仍用主库连接执行——因为事务上下文锁死了连接。更危险的是,如果事务里先读从库再写主库,可能因隔离级别导致幻读。
实操建议:
- 任何涉及写操作的视图或服务函数,开头就加
with transaction.atomic(using='default'):,明确绑定 - 禁止在同一个
atomic块里调用不同using的 QuerySet 方法 - 读多写少的服务,考虑用
select_for_update()替代普通get(),它天然要求主库,避免路由误判
主从切换真正的复杂点不在代码怎么写,而在「什么时候不该切」——比如大促期间宁愿扛住主库压力,也不冒数据延迟风险切到从库。健康检查的指标阈值、路由的 fallback 策略、以及 ORM 层对事务和关联查询的隐式行为,这三者叠加起来,才是高可用落地最难啃的部分。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











