You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Django 3.0与MariaDB的独立数据库多租户实现方案咨询

解决方案:Django 3.0 + MariaDB 独立数据库多租户实现

我完全理解你的需求——因为数据驻留法规要求必须采用独立数据库,而django-tenant-schemas仅支持PostgreSQL的schema模式,确实没法满足你的场景。好在Django原生的数据库路由系统可以帮我们实现目标,下面是具体的实现思路和可落地的步骤:

一、核心方案:自定义数据库路由(Database Router)

Django允许通过自定义路由类控制每个模型的数据库读写逻辑,这是实现独立数据库多租户的基础。

1. 配置基础数据库与租户模板

首先在settings.py中配置存储租户元数据的主数据库,同时定义租户数据库的模板(后续动态添加租户数据库时复用):

# settings.py
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'tenant_metadata',
        'USER': 'db_user',
        'PASSWORD': 'db_pass',
        'HOST': 'localhost',
        'PORT': '3306',
        'OPTIONS': {
            'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
            'charset': 'utf8mb4',
        },
    }
}

# 租户数据库配置模板,创建新租户时直接复用
TENANT_DB_TEMPLATE = {
    'ENGINE': 'django.db.backends.mysql',
    'USER': 'db_user',
    'PASSWORD': 'db_pass',
    'HOST': 'localhost', # 若需跨地区部署,可根据租户region动态调整
    'PORT': '3306',
    'OPTIONS': {
        'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
        'charset': 'utf8mb4',
    },
}

主数据库tenant_metadata专门存储租户的核心信息:租户ID、数据库名称、所属地区(用于匹配数据驻留要求)等。

2. 编写租户识别中间件

需要一个中间件来识别当前请求对应的租户,比如通过请求头、子域名或URL参数获取标识,再将租户信息绑定到request对象上:

# middleware.py
from django.http import HttpResponseForbidden
from .models import Tenant
from django.conf import settings
from django.db import connections

class TenantMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        # 示例:通过请求头X-Tenant-ID识别租户,可根据业务改为子域名/URL参数
        tenant_id = request.headers.get('X-Tenant-ID')
        if not tenant_id:
            return HttpResponseForbidden("Missing tenant identifier")
        
        try:
            request.tenant = Tenant.objects.get(id=tenant_id)
        except Tenant.DoesNotExist:
            return HttpResponseForbidden("Invalid tenant")
        
        # 动态将租户数据库添加到DATABASES(避免重复添加)
        if request.tenant.db_name not in settings.DATABASES:
            settings.DATABASES[request.tenant.db_name] = {
                **settings.TENANT_DB_TEMPLATE,
                'NAME': request.tenant.db_name
            }
            # 清除已缓存的连接,强制重新加载
            connections._connections.clear()
        
        response = self.get_response(request)
        return response

记得将这个中间件添加到settings.MIDDLEWARE列表中。

3. 实现数据库路由类

编写路由类,根据当前请求的租户信息,将模型的读写操作路由到对应租户数据库:

# routers.py
class TenantDatabaseRouter:
    def db_for_read(self, model, **hints):
        # 租户元数据模型固定在主数据库
        if model._meta.app_label == 'tenant_management':
            return 'default'
        # 其他模型根据当前请求的租户选择数据库
        request = hints.get('request') or getattr(self, '_current_request', None)
        if request and hasattr(request, 'tenant'):
            return request.tenant.db_name
        return 'default'

    def db_for_write(self, model, **hints):
        return self.db_for_read(model, **hints)

    def allow_relation(self, obj1, obj2, **hints):
        # 仅允许同一租户内的模型建立关联
        return obj1._state.db == obj2._state.db

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        # 租户元数据仅在主数据库迁移
        if app_label == 'tenant_management':
            return db == 'default'
        # 业务模型仅在租户数据库迁移(需手动指定数据库运行migrate)
        return db != 'default'

最后在settings.py中配置这个路由:

# settings.py
DATABASE_ROUTERS = ['your_project.routers.TenantDatabaseRouter']

二、租户数据库的自动化管理

1. 创建租户时自动初始化数据库

当新增租户时,自动创建对应的MariaDB数据库并执行迁移:

# models.py
from django.db import models
from django.db import connections
from django.core.management import call_command

class Tenant(models.Model):
    id = models.CharField(max_length=50, primary_key=True)
    name = models.CharField(max_length=100)
    db_name = models.CharField(max_length=100, unique=True)
    region = models.CharField(max_length=50) # 标记租户所属地区,匹配数据驻留要求

    def save(self, *args, **kwargs):
        is_new = self._state.adding
        super().save(*args, **kwargs)
        if is_new:
            # 创建租户数据库
            with connections['default'].cursor() as cursor:
                cursor.execute(f"CREATE DATABASE {self.db_name} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;")
            # 执行迁移到租户数据库
            call_command('migrate', database=self.db_name)

注意:需要确保数据库用户拥有创建数据库的权限。

2. 迁移的批量处理

  • 主数据库仅迁移租户元数据相关的App
  • 若需批量更新所有租户数据库的结构,可以编写自定义管理命令,遍历所有租户并执行migrate命令

三、关键优化与注意事项

  • 连接池管理:MariaDB连接数有限,建议使用django-db-connection-pool这类工具复用连接,避免租户过多导致连接耗尽
  • 缓存隔离:使用Django缓存时,需为每个租户的缓存键添加租户ID前缀,防止数据混淆
  • Admin适配:默认Django Admin使用default数据库,若要管理租户业务数据,需自定义Admin类,根据当前租户切换数据库
  • 测试隔离:编写测试用例时,需为每个测试创建临时租户数据库,测试完成后销毁,避免数据污染
  • 备份策略:因采用独立数据库,需为每个租户单独配置备份,符合数据驻留法规要求

四、第三方包辅助方案

如果不想完全从零搭建,可以基于django-multitenant扩展(需修改部分代码适配多数据库模式),或使用django-db-router简化路由逻辑,但核心思路仍离不开自定义数据库路由。

内容的提问来源于stack exchange,提问作者Manish

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 22:27:50