使用多个数据库
大多数其他文档都假设使用单一数据库,本文主要讨论如何在 Django 中使用多个数据库。 使用多个数据库,要增加一些步骤。
定义你的数据库
使用多数据库的第一步是通过 DATABASES 设置要使用的数据库服务。这个 设置用于映射数据库别名和特定的联结设置字典,这是 Django 定义数据库一贯的手法。 字典内部的设置参见 DATABASES 文档。
数据库可以使用任何别名,但是 default 有特殊意义。当没有选择其他数据库时, Django 总是使用别名为 default 的数据库。因此,如果你没有定义一个名为 default 的数据库时,你应当小心了,在使用数据库前要指定你想用的数据库。
以下是一个定义两个数据库的 settings.py 代码片断。定义了一个缺省的 PostgreSQL 数据库和一个名为 users 的 MySQL 数据库:
如果你尝试访问 DATABASES 设置中没有定义的数据库, Django 会抛出一个 django.db.utils.ConnectionDoesNotExist异常。
同步你的数据库
syncdb 管理命令一次只操作一个数据库。缺省情况下,它操作 default 数据库。但是加上 --database 参数,你可以让 syncdb 同步不同的 数据库。所以要同步我们例子中的所有数据库的所有模型可以使用如下命令:
如果你不是同步所有的程序到同一个数据库中,你可定义一个 数据库路由 来为指定的模型实施特定的控制 策略。
如果你要精细地控制同步,那么还有一种方式是修改 sqlall 的输出,手工在 数据库中执行命令,命令如下:
使用其他管理命令
其他操作数据库的 django-admin.py 命令与 syncdb 类似,他们一次只 操作一个数据库,使用 --database 来控制使用哪个数据库。
自动数据库路由
使用多数据库最简单的方法是设置一个数据库路由方案。缺省的路由方案确保对象 “紧贴”其原本的数据库(例如:一个对象从哪个数据库取得,就保存回哪个数据库)。 缺省的路由方案还确保如果一个数据库没有指定,所有的查询都会作用于 缺省 数据 库。
你不必为启动缺省路由方案作任何事,因为它是“开箱即用”的。但是,如果你要执行 一些更有趣的数据库分配行为的话,你可以定义并安装你自己的数据库路由。
数据库路由
一个数据库路由是一个类,这个类最多有四个方法:
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 。这是一个纯粹的验证操作,用于外键和多对多操作中,两个对象 的关系是否被允许。
allow_syncdb( db, model)决定 model 是否可以和 db 为别名的数据库同步。如果可以返回 True , 如果不可以返回 False ,或者没有意见时返回 None 。这个方法用于决定一个给定 数据库的模型是否可用。
一个路由不必提供 所有 这些方法,可以省略其中一个或多个。如果其中一个方法被 省略了,那么 Django 会在执行相关检查时跳过相应路由。
提示参数
数据库路由接收的“提示”参数可用于决定哪个数据库应当接收一个给定的请求。
目前,唯一可以提供的提示参数是 实例 ,即一个与读写操作相关的对象的实例。 可以是一个已保存的对象的实例,也可以是一个多对多关系中添加的实例。在某些情况下, 也可能没有对象的实例可以提供。路由会检查提示实例是否存在,并相应地决定是否改变 路由行为。
使用路由
数据库路由使用 DATABASE_ROUTERS 设置来安装。这个设置定义一个类名称 列表,每个类定义一个用于主路由 (django.db.router) 的路由。
主路由用于 Django 分配数据库操作。当一个查询想要知道使用哪个数据库时,会提供 一个模型和一个提示(如果有的话),并调用主路由。 Django 就会按次序尝试每个路由, 直到找到合适的路由建议。如果找不到路由建议就会尝试实例提示的当前的 _state.db 。如果没有提供路由提示,或者实例没有当前数据库状态,那么主路由会 分配 缺省 数据库。
一个例子
仅用于示例目的!
这个例子仅用于展示路由如何改变数据库的使用。本例有意忽略了一些复杂的东西以 便于更好的展示路由是如何工作的。
如果任何一个 myapp 中的模型包含与 另一个 数据库中模型的关系时,本例 是无效的。参见 跨数据库关系一节中介绍 的 Django 引用完整性问题。
本例的主/从配置也是有缺陷的:它没有处理复制延时(比如因为把写操作传递给从 数据库耗费时间而产生的查询不一致),也没有考虑与数据库使用策略的交互作用。
那么,这个例子有什么用呢?本例仅用于演示一个 myapp 存在于 other 数据库, 所有其他模型之间是主/从关系,且存在于 master 、 slave1 和 slave2 数据库。本例使用了两个路由:
然后在你的设置文件增加如下内容(把 path.to. 替换为你定义路由的模型的路径 ):
这个设置中,路由的顺序是很重要的,因为查询时是按这个设置中的顺序依次查询的。上 例中, MyAppRouter 先于MasterSlaveRouter ,因此, myapp 中的模型就 优先于其他模型。如果 DATABASE_ROUTERS 设置中两个路由的顺序变换了, 那么 MasterSlaveRouter.allow_syncdb() 会优先执行。因为 MasterSlaveRouter 是 包罗万象的,这样就会导致所有模型可以使用所有数据库。
设置好之后让我们来运行一些代码:
手动选择数据库
Django 也提供一个可以让你通过代码完全控制数据库使用的 API 。手动定义数据库分配 优先于路由。
为一个 查询集 手动选择一个数据库
你可以在 查询集 “链”中的任何点为 查询集 选择数据库。我们通过在 查询集 上调用 using() 来得到使用指定数据库的另一个 查询集 。
using() 使用一个参数:你想要运行查询的数据库的别名。例如:
为 save() 选择一个数据库
在使用 Model.save() 时加上 using 关键字可以指定保存到哪个数据库。
例如,要把一个对象保存到 legacy_users 数据库应该这样做:
如果你不定义 using ,那么 save() 方法会根据路由分配把数据保存到缺省 数据库中。
把一个对象从一个数据库移动到另一个数据库
当你已经在一个数据库中保存了一个对象后,你可能会使用 save(using=...) 把这个 对象移动到另一个数据库中。但是,如果你没有使用恰当的方法,那么可能会出现意想不 到的后果。
假设有如下的例子:
在第一名中,一个新的 Person 对象被保存到 first 数据库中。这时, p 还没有一个主键,因此 Django 执行了一个INSERT SQL 语句。这样就会创建一个 主键,并将这个主键分配给 p 。
在第二句中,因为 p 已经有了一个主键,所以 Django 在保存对象时会尝试在新的 数据库中使用这个主键。如果 second数据库中没有使用这个主键,那就不会有问题, 该对象会复制到新数据库。
然而,如果 p 的主键在 second 数据库中已经使用过了,那么 second 使用 这个主键的已存在的对象将会被 p 覆盖。
有两种方法可以避免上述情况的发生。第一,你可以清除实例的主键。如果一个对象没有 主主键,那么 Django 会把它看作一个新对象,在保存到 second 数据库中时就不会 带来数据的损失:
第二种方法是在 save() 方法中使用 force_insert 选项来保证 Django 执行 一个 INSERT SQL:
这样可以保证名为 Fred 的人员在两个数据库中使用相同的主键。如果在保存到 second 数据库时主键已被占用,会抛出一个错误。
选择一个要删除数据的数据库
缺省情况下,一个现存对象从哪个数据库得到,删除这个对象也会在这个数据库中进行:
通过向 Model.delete() 方法传递 using 关键字参数可以定义在哪个数据库中删除 数据。 using 的用法与 save() 方法中使用这个参数类似。
例如,假设我们要把一个用户从 legacy_users 数据库移动到 new_users 数据库 可以使用如下命令:
多数据库情况下使用管理器
在管理器上使用 db_manager() ,可以让管理器访问一个非缺省数据库。
例如,假设你有一个操作数据库的自定义管理器 User.objects.create_user() 。 因为 create_user() 是一个管理器方法,不是一个 查询集 ,所以你不能用 User.objects.using('new_users').create_user() 。( create_user() 方法 只能用于 User.objects 管理器,而不能用于,管理器衍生出的 查询集 。) 解决方法是使用 db_manager() ,就象下面这样:
db_manager() 返回的是绑定到你指定的数据库的管理器的一个副本。
多数据库情况下使用 get_query_set()
如果你在管理器中重载了 get_query_set() ,请确保在其父类中也调用了相同的方法 (使用 super() )或者正确处理管理器中的 _db 属性(一个包含要使用的数据库 名称的字符串)。
例如,如果你要从 get_query_set 方法返回一个自定义 查询集 类,那么你可以 这样做:
在 Django 管理接口中使用多数据库
Django 的管理接口没有明显支持多数据库。如果想要支持的话你必须写自定义 ModelAdmin 。
如果要支持多数据库,那么 ModelAdmin 对象有五个方法要自定义:
以上代码实现了一个多数据库策略:所有给定类型的对象全部储存到一个指定的数据库( 如所有 User 对象都在 other 数据库中)。如果你的多数据库应用更复杂,你就 需要自定义更复杂的 ModelAdmin 。
内联也可以按上面类似的方式处理。内联处理需要三个自定义方法:
一旦你写好了管理模型的定义,就可被任何 Admin 实例注册使用:
这个例子设置了两个管理站点。第一个管理站点,显示作者和出版商对象,出版商对象有 一个内联表格显示该出版商所出版的书。第二个管理站点只显示出版商,没有内联对象。
多数据库情况下使用原始指针
如果使用多个数据库,那么可以使用 django.db.connections 来获得特定数据库的 连接(和指针)。django.db.connections 是一个类似字典的对象,允许你通过别名 来获得一个特定的连接:
多数据库的限制
跨数据库关系
Django 当前不支持外键或多对多关系跨数据库的情况。如果你已经通过使用路由把模型 分配到不同的数据库中,那么任何外键或多对多关系涉及的模型必须放在同一个数据库中。
这里涉及到一个引用完整性问题。为了维护两个对象之间的关系, Django 需要知道相 关联对象的主键是否合法。如果主键存放在另一个数据库,就不可能轻易的验证主键的 合法性了。
如果你正在使用 Postgres 、 Oracle 或 带有InnoDB 的 MySQL ,那么在数据库级别就 已经限制跨数据库关系。
但是,如是你正在使用的 SQLite 或只使用 MyISAM 表的 MySQL ,就没有这种限制,因此 可以“假装”使用跨数据库外键,但是这种方式不被 Django 官方支持。