这篇主题描述Django 对多个数据库的支持。大部分Django 文档假设你只和一个数据库打交道。若是你想与多个数据库打交道,你将须要一些额外的步骤。html
在Django中使用多个数据库的第一步是告诉Django 你将要使用的数据库服务器。这经过使用DATABASES
设置完成。该设置映射数据库别名到一个数据库链接设置的字典,这是整个Django 中引用一个数据库的方式。字典中的设置在 DATABASES
文档中有完整描述。python
你能够为数据库选择任何别名。然而,default
这个别名具备特殊的含义。当没有选择其它数据库时,Django 使用具备default
别名的数据库。mysql
下面是settings.py
的一个示例片断,它定义两个数据库 —— 一个默认的PostgreSQL 数据库和一个叫作users
的MySQL 数据库:sql
DATABASES = { 'default': { 'NAME': 'app_data', 'ENGINE': 'django.db.backends.postgresql_psycopg2', 'USER': 'postgres_user', 'PASSWORD': 's3krit' }, 'users': { 'NAME': 'user_data', 'ENGINE': 'django.db.backends.mysql', 'USER': 'mysql_user', 'PASSWORD': 'priv4te' } }
若是default
数据库在你的项目中不合适,你须要当心地永远指定是想使用的数据库。Django 要求default
数据库必须定义,可是其参数字典能够保留为空若是不使用它。若要这样作,你必须为你的全部的应用的模型创建DATABASE_ROUTERS
,包括正在使用的contrib
中的应用和第三方应用,以使得不会有查询被路由到默认的数据库。下面是settings.py
的一个示例片断,它定义两个非默认的数据库,其中default
有意保留为空:数据库
DATABASES = { 'default': {}, 'users': { 'NAME': 'user_data', 'ENGINE': 'django.db.backends.mysql', 'USER': 'mysql_user', 'PASSWORD': 'superS3cret' }, 'customers': { 'NAME': 'customer_data', 'ENGINE': 'django.db.backends.mysql', 'USER': 'mysql_cust', 'PASSWORD': 'veryPriv@ate' } }
若是你试图访问在DATABASES
设置中没有定义的数据库,Django 将抛出一个django.db.utils.ConnectionDoesNotExist
异常。django
migrate
管理命令一次操做一个数据库。默认状况下,它在default
数据库上操做,可是经过提供一个--database
参数,你能够告诉migrate
同步一个不一样的数据库。所以,为了同步全部模型到咱们示例中的全部数据库,你将须要调用:服务器
$ ./manage.py migrate $ ./manage.py migrate --database=users
若是你不想每一个应用都被同步到同一台数据库上,你能够定义一个数据库路由,它实现一个策略来控制特定模型的访问性。session
其它django-admin
命令与数据库交互的方式与migrate
相同 —— 它们都一次只操做一个数据库,并使用--database
来控制使用的数据库。架构
使用多数据库最简单的方法是创建一个数据库路由模式。默认的路由模式确保对象’粘滞‘在它们原始的数据库上(例如,从foo
数据库中获取的对象将保存在同一个数据库中)。默认的路由模式还确保若是没有指明数据库,全部的查询都回归到default
数据库中。app
你不须要作任何事情来激活默认的路由模式 —— 它在每一个Django项目上’直接‘提供。然而,若是你想实现更有趣的数据库分配行为,你能够定义并安装你本身的数据库路由。
数据库路由是一个类,它提供4个方法:
db_for_read(model, **hints)
建议model类型的对象的读操做应该使用的数据库。
若是一个数据库操做可以提供其它额外的信息能够帮助选择一个数据库,它将在hints
字典中提供。合法的hints
的详细信息在下文给出。
若是没有建议,则返回None
。
db_for_write(model, **hints)
建议Model 类型的对象的写操做应该使用的数据库。
若是一个数据库操做可以提供其它额外的信息能够帮助选择一个数据库,它将在hints
字典中提供。 合法的hints
的详细信息在下文给出。
若是没有建议,则返回None。
allow_relation(obj1, obj2, **hints)
若是obj1
和obj2
之间应该容许关联则返回True
,若是应该防止关联则返回False
,若是路由没法判断则返回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
,除非这两个操做使用hint 提供它。
hints
用于某些操做来传递额外的信息给路由。
当设置了model_name
时,hints
一般经过键'model
'包含该模型的类。注意,它多是一个历史模型,所以不会有自定的属性、方法或管理器。你应该只依赖_meta
。
这个方法还能够用来决定一个给定数据库上某个模型的可用性。
注意,若是这个方法返回False
,迁移将默默地不会在模型上作任何操做。这可能致使你应用某些操做以后出现损坏的外键、表多余或者缺失。
Changed in Django 1.8: The signature of allow_migrate has changed significantly from previous versions. See the deprecation notes for more details.
路由没必要提供全部这些方法 —— 它能够省略一个或多个。若是某个方法缺失,在作相应的检查时Django 将忽略该路由。
Hint
由数据库路由接收,用于决定哪一个数据库应该接收一个给定的请求。
目前,惟一一个提供的hint 是instance,它是一个对象实例,与正在进行的读或者写操做关联。This might be the instance that is being saved, or it might be an instance that is being added in a many-to-many relation. In some cases, no instance hint will be provided at all. The router checks for the existence of an instance hint, and determine if that hint should be used to alter routing behavior.
数据库路由使用DATABASE_ROUTERS
设置安装。这个设置定义一个类名的列表,其中每一个类表示一个路由,它们将被主路由(django.db.router
)使用。
Django 的数据库操做使用主路由来分配数据库的使用。每当一个查询须要知道使用哪个数据库时,它将调用主路由,并提供一个模型和一个Hint
(可选)。Django 而后依次测试每一个路由直至找到一个数据库的建议。若是找不到建议,它将尝试Hint
实例的当前_state.db
。若是没有提供Hint 实例,或者该实例当前没有数据库状态,主路由将分配default
数据库。
只是为了示例!
这个例子的目的是演示如何使用路由这个基本结构来改变数据库的使用。它有意忽略一些复杂的问题,目的是为了演示如何使用路由。
若是
myapp
中的任何一个模型包含与其它 数据库以外的模型的关联,这个例子将不能工做。跨数据的关联引入引用完整性问题,Django目前还没法处理。
Primary/replica
(在某些数据库中叫作master/slave
)配置也是有缺陷的 —— 它不提供任何处理Replication lag 的解决办法(例如,由于写入同步到replica 须要必定的时间,这会引入查询的不一致)。It also doesn’t consider the interaction of transactions with the database utilization strategy.
那么 —— 在实际应用中这觉得着什么?让咱们看一下另一个配置的例子。这个配置将有几个数据库:一个用于auth
应用,全部其它应用使用一个具备两个读replica
的 primary/replica
。下面是表示这些数据库的设置:
DATABASES = { 'auth_db': { 'NAME': 'auth_db', 'ENGINE': 'django.db.backends.mysql', 'USER': 'mysql_user', 'PASSWORD': 'swordfish', }, 'primary': { 'NAME': 'primary', 'ENGINE': 'django.db.backends.mysql', 'USER': 'mysql_user', 'PASSWORD': 'spam', }, 'replica1': { 'NAME': 'replica1', 'ENGINE': 'django.db.backends.mysql', 'USER': 'mysql_user', 'PASSWORD': 'eggs', }, 'replica2': { 'NAME': 'replica2', 'ENGINE': 'django.db.backends.mysql', 'USER': 'mysql_user', 'PASSWORD': 'bacon', }, }
如今咱们将须要处理路由。首先,咱们须要一个路由,它知道发送auth
应用的查询到auth_db
:
class AuthRouter(object): """ A router to control all database operations on models in the auth application. """ def db_for_read(self, model, **hints): """ Attempts to read auth models go to auth_db. """ if model._meta.app_label == 'auth': return 'auth_db' return None def db_for_write(self, model, **hints): """ Attempts to write auth models go to auth_db. """ if model._meta.app_label == 'auth': return 'auth_db' return None def allow_relation(self, obj1, obj2, **hints): """ Allow relations if a model in the auth app is involved. """ if obj1._meta.app_label == 'auth' or \ obj2._meta.app_label == 'auth': return True return None def allow_migrate(self, db, app_label, model=None, **hints): """ Make sure the auth app only appears in the 'auth_db' database. """ if app_label == 'auth': return db == 'auth_db' return None
咱们还须要一个路由,它发送全部其它应用的查询到primary/replica
配置,并随机选择一个replica
来读取:
import random class PrimaryReplicaRouter(object): 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_list = ('primary', 'replica1', 'replica2') if obj1._state.db in db_list and obj2._state.db in db_list: return True return None def allow_migrate(self, db, app_label, model=None, **hints): """ All non-auth models end up in this pool. """ return True
最后,在设置文件中,咱们添加以下内容(替换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')
Django 还提供一个API,容许你在你的代码中彻底控制数据库的使用。人工指定的数据库的优先级高于路由分配的数据库。
你能够在QuerySet
“链”的任意节点上为QuerySet
选择数据库 。只须要在QuerySet
上调用using()
就可让QuerySet
使用一个指定的数据库。
using()
接收单个参数:你的查询想要运行的数据库的别名。例如:
>>> # 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()
对Model.save()
使用using
关键字来指定数据应该保存在哪一个数据库。
例如,若要保存一个对象到legacy_users 数据库,你应该使用:
>>> my_object.save(using='legacy_users')
若是你不指定using
,save()
方法将保存到路由分配的默认数据库中。
若是你已经保存一个实例到一个数据库中,你可能很想使用save(using=...)
来迁移该实例到一个新的数据库中。然而,若是你不使用正确的步骤,这可能致使意外的结果。
考虑下面的例子:
>>> p = Person(name='Fred') >>> p.save(using='first') # (statement 1) >>> p.save(using='second') # (statement 2)
在statement 1中,一个新的Person
对象被保存到 first
数据库中。此时p
没有主键,因此Django 发出一个SQL INSERT
语句。这会建立一个主键,且Django 将此主键赋值给p
。
当保存在statement 2中发生时,p
已经具备一个主键,Django 将尝试在新的数据库上使用该主键。若是该主键值在second
数据库中没有使用,那么你不会遇到问题 —— 该对象将被复制到新的数据库中。
然而,若是p
的主键在second
数据库上已经在使用second
数据库中的已经存在的对象将在p
保存时被覆盖。
你能够用两种方法避免这种状况。首先,你能够清除实例的主键。若是一个对象没有主键,Django 将把它当作一个新的对象,这将避免second
数据库上数据的丢失:
>>> p = Person(name='Fred') >>> p.save(using='first') >>> p.pk = None # Clear the primary key. >>> p.save(using='second') # Write a completely new object.
第二种方法是使用force_insert
选项来save()
以确保Django 使用一个INSERT SQL
:
>>> p = Person(name='Fred') >>> p.save(using='first') >>> p.save(using='second', force_insert=True)
这将确保名称为Fred
的Person
在两个数据库上具备相同的主键。在你试图保存到second
数据库,若是主键已经在使用,将会引抛出发一个错误。
默认状况下,删除一个已存在对象的调用将在与获取对象时使用的相同数据库上执行:
>>> u = User.objects.using('legacy_users').get(username='fred') >>> u.delete() # will delete from the `legacy_users` database
要指定删除一个模型时使用的数据库,能够对Model.delete()
方法使用using
关键字参数。这个参数的工做方式与save()
的using
关键字参数同样。
例如,你正在从legacy_users
数据库到new_users
数据库迁移一个User
,你可使用这些命令:
>>> user_obj.save(using='new_users') >>> user_obj.delete(using='legacy_users')
在管理器上使用db_manager()
方法来让管理器访问非默认的数据库。
例如,你有一个自定义的管理器方法,它访问数据库时候用 ——User.objects.create_user()
。由于create_user()
是一个管理器方法,不是一个QuerySet
方法,你不可使用User.objects.using('new_users').create_user()
。(create_user()
方法只能在User.objects
上使用,而不能在从管理器获得的QuerySet
上使用)。解决办法是使用db_manager()
,像这样:
User.objects.db_manager('new_users').create_user(...)
db_manager()
返回一个绑定在你指定的数据上的一个管理器。
若是你正在覆盖你的管理器上的get_queryset()
,请确保在其父类上调用方法(使用super()
)或者正确处理管理器上的_db
属性(一个包含将要使用的数据库名称的字符串)。
例如,若是你想从get_queryset
方法返回一个自定义的 QuerySet
类,你能够这样作:
class MyManager(models.Manager): def get_queryset(self): qs = CustomQuerySet(self.model) if self._db is not None: qs = qs.using(self._db) return qs
Django 的管理站点没有对多数据库的任何显式的支持。若是你给数据库上某个模型提供的管理站点不想经过你的路由链指定,你将须要编写自定义的ModelAdmin
类用来将管理站点导向一个特殊的数据库。
ModelAdmin
对象具备5个方法,它们须要定制以支持多数据库:
class MultiDBModelAdmin(admin.ModelAdmin): # A handy constant for the name of the alternate database. using = 'other' def save_model(self, request, obj, form, change): # Tell Django to save objects to the 'other' database. obj.save(using=self.using) def delete_model(self, request, obj): # Tell Django to delete objects from the 'other' database obj.delete(using=self.using) def get_queryset(self, request): # Tell Django to look for objects on the 'other' database. return super(MultiDBModelAdmin, self).get_queryset(request).using(self.using) def formfield_for_foreignkey(self, db_field, request=None, **kwargs): # Tell Django to populate ForeignKey widgets using a query # on the 'other' database. return super(MultiDBModelAdmin, self).formfield_for_foreignkey(db_field, request=request, using=self.using, **kwargs) def formfield_for_manytomany(self, db_field, request=None, **kwargs): # Tell Django to populate ManyToMany widgets using a query # on the 'other' database. return super(MultiDBModelAdmin, self).formfield_for_manytomany(db_field, request=request, using=self.using, **kwargs)
这里提供的实现实现了一个多数据库策略,其中一个给定类型的全部对象都将保存在一个特定的数据库上(例如,全部的User
保存在other
数据库中)。若是你的多数据库的用法更加复杂,你的ModelAdmin
将须要反映相应的策略。
Inlines
能够用类似的方式处理。它们须要3个自定义的方法:
class MultiDBTabularInline(admin.TabularInline): using = 'other' def get_queryset(self, request): # Tell Django to look for inline objects on the 'other' database. return super(MultiDBTabularInline, self).get_queryset(request).using(self.using) def formfield_for_foreignkey(self, db_field, request=None, **kwargs): # Tell Django to populate ForeignKey widgets using a query # on the 'other' database. return super(MultiDBTabularInline, self).formfield_for_foreignkey(db_field, request=request, using=self.using, **kwargs) def formfield_for_manytomany(self, db_field, request=None, **kwargs): # Tell Django to populate ManyToMany widgets using a query # on the 'other' database. return super(MultiDBTabularInline, self).formfield_for_manytomany(db_field, request=request, using=self.using, **kwargs)
一旦你写好你的模型管理站点的定义,它们就可使用任何Admin
实例来注册:
from django.contrib import admin # Specialize the multi-db admin objects for use with specific models. class BookInline(MultiDBTabularInline): model = Book class PublisherAdmin(MultiDBModelAdmin): inlines = [BookInline] admin.site.register(Author, MultiDBModelAdmin) admin.site.register(Publisher, PublisherAdmin) othersite = admin.AdminSite('othersite') othersite.register(Publisher, MultiDBModelAdmin)
这个例子创建两个管理站点。在第一个站点上,Author
和 Publisher
对象被暴露出来;Publisher
对象具备一个表格的内联,显示该出版社出版的书籍。第二个站点只暴露Publishers
,而没有内联。
若是你正在使用多个数据库,你可使用django.db.connections
来获取特定数据库的链接(和游标):django.db.connections
是一个类字典对象,它容许你使用别名来获取一个特定的链接:
from django.db import connections cursor = connections['my_db_alias'].cursor()
Django 目前不提供跨多个数据库的外键或多对多关系的支持。若是你使用一个路由来路由分离到不一样的数据库上,这些模型定义的任何外键和多对多关联必须在单个数据库的内部。
这是由于引用完整性的缘由。为了保持两个对象之间的关联,Django 须要知道关联对象的主键是合法的。若是主键存储在另一个数据库上,判断一个主键的合法性不是很容易。
若是你正在使用Postgres、Oracle或者MySQ 的InnoDB,这是数据库完整性级别的强制要求 —— 数据库级别的主键约束防止建立不能验证合法性的关联。
然而,若是你正在使用SQLite 或MySQL的MyISAM 表,则没有强制性的引用完整性;结果是你能够‘伪造’跨数据库的外键。可是Django 官方不支持这种配置。
有几个Contrib 应用包含模型,其中一些应用相互依赖。由于跨数据库的关联是不可能的,这对你如何在数据库之间划分这些模型带来一些限制:
contenttypes.ContentType
、sessions.Session
和sites.Site
能够存储在分开存储在不一样的数据库中,只要给出合适的路由
auth
模型 —— User
、Group
和Permission
—— 关联在一块儿并与ContentType
关联,因此它们必须与ContentType
存储在相同的数据库中。
admin
依赖auth
,因此它们的模型必须与auth
在同一个数据库中。
flatpages
和redirects
依赖sites
,因此它们必须与sites
在同一个数据库中。
另外,一些对象在migrate在数据库中建立一张表后自动建立:
一个默认的Site
,
为每一个模型建立一个ContentType
(包括没有存储在同一个数据库中的模型),
为每一个模型建立3个Permission
(包括不是存储在同一个数据库中的模型)。
对于常见的多数据库架构,将这些对象放在多个数据库中没有什么用处。常见的数据库架构包括primary/replica
和链接到外部的数据库。所以,建议写一个数据库路由,它只容许同步这3个模型到一个数据中。对于不须要将表放在多个数据库中的Contrib 应用和第三方应用,可使用一样的方法。
警告
若是你将
Content Types
同步到多个数据库中,注意它们的主键在数据库之间可能不一致。这可能致使数据损坏或数据丢失。
译者:Django 文档协做翻译小组,原文:Multiple databases。
本文以 CC BY-NC-SA 3.0 协议发布,转载请保留做者署名和文章出处。
Django 文档协做翻译小组人手紧缺,有兴趣的朋友能够加入咱们,彻底公益性质。交流群:467338606。