Django4 中文入门教程 自动数据库路由

2024-02-25 开发教程 Django4 中文入门教程 匿名 8

使用多数据库最简单的方式就是设置数据库路由方案。默认路由方案确保对象对原始数据库保持粘性(比如,从 ​foo ​数据库检索到的对象将被保持到同一个数据库)。默认路由方案确保当数据库没有指定时,所有查询回退到 ​default ​数据库。

你无需执行任何操作来激活默认路由——在每个 Django 项目上是开箱即用的。然而,如果想实现更多有趣的数据库分配行为,可以定义和安装自己的数据库路由。

数据库路由

数据库路由是一个类,它提供四种方法:

db_for_read(model, **hints)

建议用于读取​model​类型对象的数据库。
如果数据库操作可以提供有助于选择数据库的任何附加信息,它将在 ​hints ​中提供。
如果没有建议,则返回 ​None ​。

db_for_write(model, **hints)

建议用于写入​model​类型对象的数据库。
如果数据库操作可以提供有助于选择数据库的任何附加信息,它将在 ​hints ​中提供。
如果没有建议,则返回 ​None ​。

allow_relation(obj1, obj2, **hints)

如果允许 ​obj1 ​和 ​obj2 ​之间的关系,返回 ​True ​。如果阻止关系,返回 ​False ​,或如果路由没意见,则返回 ​None​。这纯粹是一种验证操作,由外键和多对多操作决定是否应该允许关系。
如果没有路由有意见(比如所有路由返回 ​None​),则只允许同一个数据库内的关系。

allow_migrate(db, app_label, model_name=None, **hints)

决定是否允许迁移操作在别名为 ​db ​的数据库上运行。如果操作运行,那么返回 ​True ​,如果没有运行则返回 ​False ​,或路由没有意见则返回 ​None ​。
app_label ​参数是要迁移的应用程序的标签。
model_name ​由大部分迁移操作设置来要迁移的模型的 ​model._meta.model_name​ (模型 ​__name__​ 的小写版本) 的值。 对于 RunPython 和 RunSQL 操作的值是 ​None ​,除非它们提示要提供它。
hints通过某些操作来向路由传达附加信息。
当设置 ​model_name ​,​hints​ 通常包含 ​model​下的模型类。注意它可能是 ​historical model​ ,因此没有任何自定义属性,方法或管理器。你应该只能依赖 ​_meta​ 。
这个方法也可以用于确定给定数据库上模型的可用性。
makemigrations ​会在模型变动时创建迁移,但如果 ​allow_migrate()​ 返回 ​False​,任何针对 ​model_name​ 的迁移操作会在运行 ​migrate的时候跳过。对于已经迁移过的模型,改变 ​allow_migrate()​ 的行为,可能会破坏主键,格外表或丢失的表。当 ​makemigrations ​核实迁移历史,它跳过不允许迁移的 app 的数据库。

路由不是必须提供所有这些方法——它也许省略它们中的一个或多个。如果某个方法被省略,Django会在执行相关检查时候,跳过这个路由。

使用路由

数据库路由 ​DATABASE_ROUTERS ​配置安装。这个配置定义类名列表,每个类名指定了主路由​django.db.router​应使用的路由。
Django 的数据库操作使用主路由来分配数据库使用。每当查询需要知道正在使用哪个数据库时,它会调用主路由,提供一个模型和提示(如果可用的话),然后 Django 会依次尝试每个路由直到找到数据库。如果没有找到,它试着访问提示实例的当前 ​instance._state.db​ 。如果没有提供提示实例,或者 ​instance._state.db​ 为 ​None ​,主路由将分配默认数据库。

例如我们有一些数据库:一个 ​auth ​应用,和其他应用使用带有两个只读副本的主/副设置。以下是指定这些数据库的设置:

DATABASES = {
'default': {},
'auth_db': {
'NAME': 'auth_db_name',
'ENGINE': 'django.db.backends.mysql',
'USER': 'mysql_user',
'PASSWORD': 'swordfish',
},
'primary': {
'NAME': 'primary_name',
'ENGINE': 'django.db.backends.mysql',
'USER': 'mysql_user',
'PASSWORD': 'spam',
},
'replica1': {
'NAME': 'replica1_name',
'ENGINE': 'django.db.backends.mysql',
'USER': 'mysql_user',
'PASSWORD': 'eggs',
},
'replica2': {
'NAME': 'replica2_name',
'ENGINE': 'django.db.backends.mysql',
'USER': 'mysql_user',
'PASSWORD': 'bacon',
},
}

现在需要处理路由。首先需要一个将 ​auth ​和 ​contenttypes app​ 的查询发送到 ​auth_db ​的路由(​auth ​模型已经关联了 ​ContentType​,因此它们必须保存在同一个数据库里):

class AuthRouter:
"""
A router to control all database operations on models in the
auth and contenttypes applications.
"""
route_app_labels = {'auth', 'contenttypes'}
def db_for_read(self, model, **hints):
"""
Attempts to read auth and contenttypes models go to auth_db.
"""
if model._meta.app_label in self.route_app_labels:
return 'auth_db'
return None
def db_for_write(self, model, **hints):
"""
Attempts to write auth and contenttypes models go to auth_db.
"""
if model._meta.app_label in self.route_app_labels:
return 'auth_db'
return None
def allow_relation(self, obj1, obj2, **hints):
"""
Allow relations if a model in the auth or contenttypes apps is
involved.
"""
if (
obj1._meta.app_label in self.route_app_labels or
obj2._meta.app_label in self.route_app_labels
):
return True
return None
def allow_migrate(self, db, app_label, model_name=None, **hints):
"""
Make sure the auth and contenttypes apps only appear in the
'auth_db' database.
"""
if app_label in self.route_app_labels:
return db == 'auth_db'
return None

我们也需要一个发送所有其他应用到主/副配置的路由,并且随机选择一个副本来读取:

import random
class PrimaryReplicaRouter:
def db_for_read(self, model, **hints):
"""
Reads go to a randomly-chosen replica.
"""
return random.choice(['replica1', 'replica2'])
def db_for_write(self, model, **hints):
"""
Writes always go to primary.
"""
return 'primary'
def allow_relation(self, obj1, obj2, **hints):
"""
Relations between objects are allowed if both objects are
in the primary/replica pool.
"""
db_set = {'primary', 'replica1', 'replica2'}
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):
"""
All non-auth models end up in this pool.
"""
return True

最后,在配置文件中,我们添加下面的代码(用定义路由器的模块的实际 Python 路径替换 ​path.to.​ ):

DATABASE_ROUTERS = ['path.to.AuthRouter', 'path.to.PrimaryReplicaRouter']

处理路由的顺序非常重要。路由将按照 ​DATABASE_ROUTERS ​里设置的顺序查询。在这个例子里, ​AuthRouter ​将在 ​PrimaryReplicaRouter ​前处理,因此,在做出其他决定之前,先处理与 ​auth ​相关的模型。如果 ​DATABASE_ROUTERS ​设置在其他顺序里列出两个路由,​PrimaryReplicaRouter.allow_migrate()​ 将首先处理。​PrimaryReplicaRouter​ 实现的特性意味着所有模型可用于所有数据库。
安装好这个设置,并按照 同步数据库 的要求迁移所有的数据库,让我们运行一些 Django 代码:

>>> # This retrieval will be performed on the 'auth_db' database
>>> fred = User.objects.get(username='fred')
>>> fred.first_name = 'Frederick'
>>> # This save will also be directed to 'auth_db'
>>> fred.save()
>>> # These retrieval will be randomly allocated to a replica database
>>> dna = Person.objects.get(name='Douglas Adams')
>>> # A new object has no database allocation when created
>>> mh = Book(title='Mostly Harmless')
>>> # This assignment will consult the router, and set mh onto
>>> # the same database as the author object
>>> mh.author = dna
>>> # This save will force the 'mh' instance onto the primary database...
>>> mh.save()
>>> # ... but if we re-retrieve the object, it will come back on a replica
>>> mh = Book.objects.get(title='Mostly Harmless')

这个例子定义了一个路由来处理与来自 ​auth ​应用的模型交互,其他路由处理与所以其他应用的交互。如果 ​default ​为空,并且不想定义一个全能数据库来处理所有未指定的应用,那么路由必须在迁移之前处理 ​INSTALLED_APPS ​的所有应用名。