Django中指定.save(using='default')仍指向其他数据库的问题
我将Django settings.py配置如下:
DATABASES = { 'default': { # 省略若干配置 'NAME': 'myproject', 'HOST': 'localhost', }, 'other': { 'NAME': 'other', 'HOST': 'else.where', } }我想要从
other数据库获取数据并保存到default数据库,操作代码如下:things = Thing.objects.using('other').raw('SELECT code,created FROM other.otherapp_bigthing WHERE created>...') # 'code'是other库中的唯一字符串,也是Thing模型的主键 for t in things: t.save(using='default')执行后出现
ProgrammingError: (1146, "Table 'other.myapp_thing' doesn't exist")错误,该错误本身符合实际情况,但根据using参数的文档说明,我预期记录会保存到myproject.myapp_thing。为何明确指定使用default时,数据库名称仍取自other的配置?
这个问题的核心在于通过raw()查询返回的Model实例会绑定到你指定的源数据库别名,即使你在save()时指定了不同的using参数,Django仍会优先使用实例本身的_state.db属性来确定操作的数据库。
为什么会这样?
当你执行Thing.objects.using('other').raw(...)时,返回的RawQuerySet中的每个Thing实例,其内部的_state.db属性会被设置为'other'——这是Django用来跟踪实例所属数据库的标记。
当你调用t.save(using='default')时,由于实例已经有了主键值(code),Django默认会尝试执行UPDATE操作,而在生成UPDATE语句时,它会优先使用实例_state.db中存储的数据库别名(也就是'other'),而不是你传入的using参数。这就导致它去other数据库中查找Thing模型对应的myapp_thing表,而该表只存在于default库中,所以抛出了表不存在的错误。
解决方案
有两种简单的方式可以解决这个问题:
1. 创建新的Thing实例(推荐)
不要直接保存从raw()查询得到的实例,而是用查询到的数据创建一个全新的Thing对象,再保存到default数据库:
things = Thing.objects.using('other').raw('SELECT code,created FROM other.otherapp_bigthing WHERE created>...') for t in things: # 基于查询到的数据创建新实例 new_thing = Thing(code=t.code, created=t.created) new_thing.save(using='default')
这种方式最清晰,避免了实例绑定到源数据库的问题,新实例默认会使用default数据库(或者你明确指定using)。
2. 修改实例的_state.db并强制插入
如果你想复用原实例,可以修改它的_state.db属性,同时使用force_insert=True强制Django执行INSERT操作(而不是UPDATE):
things = Thing.objects.using('other').raw('SELECT code,created FROM other.otherapp_bigthing WHERE created>...') for t in things: # 将实例绑定到default数据库 t._state.db = 'default' # 强制插入,避免Django尝试UPDATE操作 t.save(force_insert=True)
force_insert=True会告诉Django忽略实例的现有主键状态,直接执行INSERT,确保数据写入default库的myapp_thing表。
内容的提问来源于stack exchange,提问作者arne.b

