我已经做了最接近第 1 点的类似操作,但没有使用中间件来设置默认连接,而是使用了 Django 数据库路由器。如果每个请求需要,这允许应用程序逻辑使用多个数据库。为每个查询选择合适的数据库取决于应用程序逻辑,这是这种方法的一大缺点。
使用此设置,所有数据库都列在 中settings.DATABASES
,包括可能在客户之间共享的数据库。每个客户特定的模型都放置在具有特定应用标签的 Django 应用中。
例如。下面的类定义了一个存在于所有客户数据库中的模型。
class MyModel(Model):
....
class Meta:
app_label = 'customer_records'
managed = False
将数据库路由器放置在settings.DATABASE_ROUTERS
链中以通过 路由数据库请求app_label
,如下所示(不是完整示例):
class AppLabelRouter(object):
def get_customer_db(self, model):
# Route models belonging to 'myapp' to the 'shared_db' database, irrespective
# of customer.
if model._meta.app_label == 'myapp':
return 'shared_db'
if model._meta.app_label == 'customer_records':
customer_db = thread_local_data.current_customer_db()
if customer_db is not None:
return customer_db
raise Exception("No customer database selected")
return None
def db_for_read(self, model, **hints):
return self.get_customer_db(model, **hints)
def db_for_write(self, model, **hints):
return self.get_customer_db(model, **hints)
这个路由器的特殊部分是thread_local_data.current_customer_db()
调用。在使用路由器之前,调用者/应用程序必须已经在thread_local_data
. 为此,可以使用 Python 上下文管理器来推送/弹出当前客户数据库。
配置完所有这些后,应用程序代码看起来像这样,UseCustomerDatabase
上下文管理器将当前客户数据库名称推送/弹出到其中thread_local_data
,以便thread_local_data.current_customer_db()
在最终命中路由器时返回正确的数据库名称:
class MyView(DetailView):
def get_object(self):
db_name = determine_customer_db_to_use(self.request)
with UseCustomerDatabase(db_name):
return MyModel.object.get(pk=1)
这已经是一个相当复杂的设置了。它有效,但我将尝试总结我认为的优点和缺点:
优点
- 数据库选择灵活。它允许在单个查询中使用多个数据库,客户特定数据库和共享数据库都可以在请求中使用。
- 数据库选择是明确的(不确定这是优点还是缺点)。如果您尝试运行查询客户数据库但应用程序没有选择一个,则会发生异常,指示编程错误。
- 使用数据库路由器允许不同的数据库存在于不同的主机上,而不是依赖于
USE db;
猜测所有数据库都可以通过单个连接访问的语句。
缺点
- 设置起来很复杂,并且需要很多层才能使其正常运行。
- 线程本地数据的需要和使用是模糊的。
- 视图中充斥着数据库选择代码。这可以使用基于类的视图进行抽象,以根据请求参数以与中间件选择默认数据库相同的方式自动选择数据库。
- 选择数据库的上下文管理器必须以这样一种方式包装查询集,即在评估查询时上下文管理器仍然处于活动状态。
建议
如果您想要灵活的数据库访问,我建议使用 Django 的数据库路由器。使用中间件或视图 Mixin,它会根据请求参数自动设置用于连接的默认数据库。您可能不得不求助于线程本地数据来存储要使用的默认数据库,以便当路由器被击中时,它知道要路由到哪个数据库。这允许 Django 使用其现有的持久连接到数据库(如果需要,可以驻留在不同的主机上),并根据请求中设置的路由选择要使用的数据库。
这种方法还有一个优点,即如果需要,可以通过使用该QuerySet using()
函数选择默认数据库以外的数据库来覆盖查询的数据库。