django数据库路由是通过databaserouter类提供的钩子机制实现读写分离,需手动在db_for_read和db_for_write中定义规则,而非自动生效;事务内读操作强制走db_for_write以保证一致性,select_for_update一律走写库,allow_migrate必须控制迁移仅作用于主库。

什么是Django数据库路由,它和读写分离有什么关系?
Django的DatabaseRouter不是自动帮你做读写分离的魔法开关,它只是提供了一套钩子,让你能决定每个查询该发给哪个数据库。真正的读写分离逻辑——比如“SELECT走从库,INSERT/UPDATE走主库”——得你自己在db_for_read和db_for_write里写清楚。
常见错误是以为只要配了多个数据库、写了路由类就万事大吉。实际上,如果db_for_read没返回从库别名,或者没处理事务中的读操作,SELECT还是全打到主库上。
-
db_for_read只在非事务内、且查询不含select_for_update()时生效;事务中默认走db_for_write返回的库 -
allow_relation必须显式允许主从间跨库关联(比如auth.User在主库,但日志模型在从库),否则ForeignKey反查会报DatabaseError - 不重写
allow_migrate会导致makemigrations和migrate只作用于default库,从库表结构永远不同步
如何写一个最小可用的读写分离路由类?
下面这个路由类覆盖了90%基础场景:主库default负责写和事务内读,从库slave承担普通读请求,并允许主从间外键关联。
class ReadWriteRouter:
def db_for_read(self, model, **hints):
return 'slave'
<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', 'slave'}
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 == 'slave':
return False # 从库不跑迁移
return None
关键点:
调用 Cutout.Pro 视觉处理 API 进行背景移除、人像抠图和照片增强,支持文件上传与图片 URL 输入。
-
db_for_read直接返回'slave',但要注意:如果用.using('default')显式指定库,路由会被绕过 -
allow_migrate返回False给slave,确保migrate只在default执行;返回None表示“不反对”,让Django按默认规则处理 - 没有硬编码模型名,靠
app_label或hints做细粒度控制会增加复杂度,初期没必要
为什么事务内读操作不会走从库?
Django强制事务内所有操作(包括SELECT)都使用db_for_write返回的数据库,这是为了保证事务一致性。比如你在一个transaction.atomic()里先SELECT再UPDATE,如果读走了从库,可能读到旧数据。
如果你确实需要事务内读从库(极少见),只能手动指定:MyModel.objects.using('slave').filter(...)。但这会破坏事务隔离,需自行承担风险。
- 事务装饰器
@transaction.atomic、上下文管理器with transaction.atomic():都会触发该行为 -
select_for_update()无论是否在事务中,一律走db_for_write,因为它本质是写操作 - ORM的
count()、exists()等也是读操作,同样受路由控制
配置和启用路由时最容易漏掉的三件事
光写好路由类还不够,Django需要明确知道去哪里找它,以及数据库配置是否匹配。
- 必须在
settings.py中把路由类路径加进DATABASE_ROUTERS:DATABASE_ROUTERS = ['myapp.routers.ReadWriteRouter'];漏掉这个,路由完全不生效 -
settings.DATABASES里要正确定义'slave',且HOST/PORT指向真实从库;测试时用sqlite模拟从库会导致OperationalError,因为SQLite不支持多连接并发读 - 如果用了第三方包(如
django-compressor),它们的模型可能触发路由;可在db_for_read里加if app_label == 'compressor': return 'default'临时规避
最麻烦的其实是验证——QuerySet本身不暴露用了哪个库,得靠connection.alias或数据库代理层的日志确认实际流向。别依赖print(queryset.query),它只显示SQL,不显示目标库。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










