自动数据库路由
使用多数据库最简单的方式就是设置数据库路由方案。默认路由方案确保对象对原始数据库保持粘性(比如,从 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
的所有应用名。