在Django中匹配不同数据库的关系数据

4 投票
3 回答
809 浏览
提问于 2025-04-18 12:28

在开发一个用于索引系统文档的网站时,我遇到了一个棘手的问题,主要是关于在Django中如何处理不同数据库之间的数据“匹配”或关系。

这是我本地数据库的一个简化模型:

from django.db import models

class Document(models.Model):
    name = models.CharField(max_length=200)
    system_id = models.IntegerField()
    ...

想象一下,系统的详细信息存储在一个远程数据库中。

from django.db import models               

class System(models.Model):     
    name = models.CharField(max_length=200)           
    system_id = models.IntegerField()      
    ...

我的想法是,当我在网站上创建一个新的文档条目时,相关系统的ID会被存储在本地数据库中。在展示数据时,我需要用这个存储的ID去远程数据库中获取系统名称和其他详细信息。

我查阅了一些关于跨数据库的外键的资料,但这似乎非常复杂,我不确定自己是否真的需要建立关系。相反,我想象在文档模型/类中有一个函数,可以通过导入自定义路由或函数来获取匹配的数据。

我该如何解决这个问题呢?


需要注意的是,我无法对远程数据库进行任何更改,它是只读的。我也不确定是否应该为系统创建一个模型。两个数据库都使用PostgreSQL,不过我觉得在这个场景中,使用哪个数据库并不是特别重要。

3 个回答

1

我建议使用一个叫 get_system() 的方法。这样:

class Document:
    def get_system(self):
       return System.objects.using('remote').get(system_id=self.system_id)

这是最简单的解决方案。还有一种可能的解决办法是使用 PostgreSQL 的外部数据包装器功能。通过使用 FDW,你可以把处理多个数据库的复杂性隐藏起来,让它在数据库内部完成——这样你就可以使用需要文档和系统之间关系的查询了。

最后,如果你的使用场景允许的话,定期把系统数据复制到本地数据库也是一个不错的解决方案。

2

你说得对,在Django的ORM中,跨数据库的外键确实是个问题,数据库层面上也是如此。

其实你已经有了基本的答案:"我想象在Document模型/类里面有一个函数,可以用来获取匹配的数据"

我会这样做:

class RemoteObject(object):
    def __init__(self, remote_model, remote_db, field_name):
        # assumes remote db is defined in Django settings and has an
        # associated Django model definition:
        self.remote_model = remote_model
        self.remote_db = remote_db
        # name of id field on model (real db field):
        self.field_name = field_name
        # we will cache the retrieved remote model on the instance
        # the same way that Django does with foreign key fields:
        self.cache_name = '_{}_cache'.format(field_name)

    def __get__(self, instance, cls):
        try:
            rel_obj = getattr(instance, self.cache_name)
        except AttributeError:
            system_id = getattr(instance, self.field_name)
            remote_qs = self.remote_model.objects.using(self.remote_db)
            try:
                rel_obj = remote_qs.get(id=system_id)
            except self.remote_model.DoesNotExist:
                rel_obj = None
            setattr(instance, self.cache_name, rel_obj)
        if rel_obj is None:
            raise self.related.model.DoesNotExist
        else:
            return rel_obj

    def __set__(self, instance, value):
        setattr(instance, self.field_name, value.id)
        setattr(instance, self.cache_name, value)


class Document(models.Model:
    name = models.CharField(max_length=200)
    system_id = models.IntegerField()
    system = RemoteObject(System, 'system_db_name', 'system_id')

你可能会注意到,上面的RemoteObject类实现了Python的描述符协议,想了解更多可以看这里:
https://docs.python.org/2/howto/descriptor.html

使用示例:

>>> doc = Document.objects.get(pk=1)
>>> print doc.system_id
3
>>> print doc.system.id
3
>>> print doc.system.name
'my system'
>>> other_system = System.objects.using('system_db_name').get(pk=5)
>>> doc.system = other_system
>>> print doc.system_id
5

进一步来说,你可以写一个自定义的数据库路由器:
https://docs.djangoproject.com/en/dev/topics/db/multi-db/#using-routers

这样你就可以在代码中省去using('system_db_name')的调用,通过路由将所有对System模型的读取指向正确的数据库。

4

在Django的文档中,关于多数据库(手动选择数据库)的部分

# This will run on the 'default' database.
Author.objects.all()

# So will this.
Author.objects.using('default').all()

# This will run on the 'other' database.
Author.objects.using('other').all()

The 'default' and 'other' are aliases for you databases.
In your case it would could be 'default' and 'remote'.

当然,你可以把 .all() 替换成你想要的任何内容。

Example: System.objects.using('remote').get(id=123456)

撰写回答