Django多数据库配置与路由实现详解

栗子
栗子
发布时间:2026-10-13 | 4人浏览过
原创

本文详解Django项目中配置和使用多个数据库的方法。涵盖在settings中定义DATABASES、通过app_label指定模型归属,以及编写Database Routers类实现读写分离和迁移控制。包含AuthRouter与PrimaryReplicaRouter完整示例,讲解migrate命令的--database参数使用,以及手动指定using、处理主键冲突和对象转移的具体代码实现。

Django多数据库配置概述

部分项目场景下需要同时操作多个数据库,实现这一目标的方法相对直接。

Settings中配置DATABASES

第一步是在项目的settings配置文件中定义DATABASES字典。

假设项目需要同时连接两个不同的数据库实例。


DATABASES = {
  'default': {
    'NAME': 'app_data',
    'ENGINE': 'django.db.backends.postgresql',
    'USER': 'postgres_user',
    'PASSWORD': 's3krit'
  },
  'users': {
    'NAME': 'user_data',
    'ENGINE': 'django.db.backends.mysql',
    'USER': 'mysql_user',
    'PASSWORD': 'priv4te'
  }
}

通过上述配置,系统便识别出两个数据库连接,分别命名为default和user。这里的数据库别名可以根据实际需求自由命名。

其中,default别名具有特殊意义:如果某个Model在路由配置中未指定特定数据库,系统将默认使用名为default的数据库进行读写操作。

此外,也可以将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'
  }
}

一旦default被设为空,意味着系统不再拥有默认数据库。此时,必须为所有Model(包括第三方库中引入的Model)明确指定数据库路由策略。

第二步,为需要指定数据库归属的Model设置app_label属性。


class MyUser(models.Model):
  ...
   class Meta:
    app_label = 'users'

Database Routers路由规则编写

第三步,编写Database Routers类来控制数据库选择逻辑。

Database Router的核心作用是决定每个Model使用哪个数据库,主要需实现以下四个方法:

db_for_read(model, **hints)

该方法用于指定model执行读取操作时使用的数据库。

db_for_write(model, **hints)

该方法用于指定model执行写入操作时使用的数据库。

allow_relation(obj1, obj2, **hints)

该方法用于判断obj1与obj2之间是否允许建立关联,主要应用于ForeignKey和ManyToManyField关系操作。

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

该方法用于确定名为db的数据库是否允许执行migrate迁移操作。

完整路由配置示例

以下是一个完整的配置示例。

数据库连接配置如下:


DATABASES = {
  'default': {},
  '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',
  },
}

若希望实现如下路由效果:

当app_label为auth的Model进行读写操作时,均指向auth_db数据库;其余Model的写入操作指向primary数据库,而读取操作则随机分配至replica1或replica2数据库。

针对auth应用的配置:


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_name=None, **hints):
    """
    Make sure the auth app only appears in the 'auth_db'
    database.
    """
    if app_label == 'auth':
      return db == 'auth_db'
    return None

通过上述配置,app_label为auth的Model的所有读写操作均在auth_db中完成,允许相关对象建立关联,且migrate迁移操作仅在auth_db数据库中执行。

针对其他应用的配置:


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_name=None, **hints):
    """
    All non-auth models end up in this pool.
    """
    return True

通过上述配置,读取操作将随机在replica1和replica2之间选择,而写入操作统一使用primary数据库。

最后,在settings.py文件中配置DATABASE_ROUTERS列表。


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

完成上述配置后,多数据库路由即可生效。

数据库迁移与管理命令

执行migrate迁移命令时:


$ ./manage.py migrate
$ ./manage.py migrate --database=users

migrate命令默认针对default数据库执行。若需对其他数据库执行迁移,可添加--database选项,后接目标数据库的别名。

同样地,dbshell、dumpdata和loaddata等管理命令也支持--database选项。

手动指定数据库与对象转移

开发者也可以手动指定数据库路由。

例如执行查询操作:


>>> # 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()

例如执行保存操作:


>>> my_object.save(using='legacy_users')

例如执行移动或转移对象操作:


>>> p = Person(name='Fred')
>>> p.save(using='first') # (statement 1)
>>> p.save(using='second') # (statement 2)

上述代码逻辑可能存在潜在问题:当对象p在first数据库中首次保存时,系统会自动生成主键。若随后尝试将p保存至second数据库,由于p已拥有主键,若该主键未被其他记录使用则无影响;但若该主键已被占用,则可能导致数据覆盖。

针对此问题,有两种解决方案:

方案一:在保存前清除主键。


>>> 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参数强制插入。


>>> p = Person(name='Fred')
>>> p.save(using='first')
>>> p.save(using='second', force_insert=True)

关于删除操作:

原则是从哪个数据库获取的对象,就从该数据库执行删除操作。


>>> u = User.objects.using('legacy_users').get(username='fred')
>>> u.delete() # will delete from the `legacy_users` database

若需将对象从legacy_users数据库迁移至new_users数据库:


>>> user_obj.save(using='new_users')
>>> user_obj.delete(using='legacy_users')

以上内容涵盖了Django多数据库配置的核心要点,希望对读者有所帮助,欢迎继续支持本站获取更多技术资讯。

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表