Python中如何实现Django的分表操作?

现在有一个问题,数据库每天会创建一张新的表,表名格式大概为:

'prefix_{}'.format(datetime.datetime.now().strftime("%Y%m%d"))

而现在在开发一个 Django 后端程序,仅仅需要用 django-admin 链接数据库进行一些很基础的信息展示即可。不过现在卡在了怎么才能动态的使用 model 更换数据库表名,拿到当前日期的数据呢?


Python中如何实现Django的分表操作?
7 回复

既然只是为了展示一些基础数据为什么不直接写 SQL 呢?


在Django里直接搞分表(水平分表)确实有点麻烦,因为ORM默认一个模型对应一张表。不过,有几种路子可以走:

1. 手动路由(最直接,但最糙) 就是自己写逻辑,根据某个字段(比如用户ID)决定用哪张表。在save()和查询的时候手动拼表名。这方法把ORM的优势废了一大半,容易出错,不推荐。

2. 使用db_router + 动态模型 这是比较常见的“标准”做法。核心是利用Django的数据库路由(db_router)和运行时动态修改模型的Meta.db_table属性。

  • 第一步:定义你的分表规则。 比如,我们按用户ID的尾数对10取模,分到10张表里。
  • 第二步:创建一个数据库路由类。settings.py里设置DATABASE_ROUTERS
  • 第三步:动态创建模型类。 在需要的时候,根据分表规则,复制基础模型类并修改其表名。

下面是一个完整可运行的示例:

假设我们有一个UserLog模型,需要按user_id分10张表:user_log_0user_log_9

(1) 项目结构

myproject/
    myapp/
        __init__.py
        models.py
        routers.py  # 存放我们的路由逻辑
    myproject/
        settings.py

(2) myapp/models.py - 定义基础抽象模型

from django.db import models

class BaseUserLog(models.Model):
    """基础模型,不创建数据库表。"""
    user_id = models.IntegerField()
    action = models.CharField(max_length=100)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        abstract = True  # 关键!这是一个抽象基类,不会单独生成表。

# 注意:这里不要定义普通的`class UserLog(models.Model)`。

(3) myapp/routers.py - 核心:路由和动态模型工厂

from django.db import models
from myapp.models import BaseUserLog

class UserLogRouter:
    """
    将UserLog模型的操作路由到对应的分表。
    """
    def db_for_read(self, model, **hints):
        return self._get_db_for_model(model)

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

    def _get_db_for_model(self, model):
        # 只处理我们动态生成的UserLog模型
        if model.__name__ == 'UserLog' and hasattr(model, '_table_suffix'):
            return 'default'  # 假设所有分表都在'default'数据库
        return None

# 动态模型创建函数
def get_user_log_model(table_suffix):
    """
    根据后缀(如0,1,2...9)返回对应的动态模型类。
    使用缓存避免重复创建类。
    """
    table_name = f'user_log_{table_suffix}'

    # 简单的缓存机制
    if not hasattr(get_user_log_model, '_model_cache'):
        get_user_log_model._model_cache = {}
    if table_name in get_user_log_model._model_cache:
        return get_user_log_model._model_cache[table_name]

    # 动态创建Meta类
    class Meta:
        db_table = table_name
        app_label = 'myapp'

    # 动态创建模型类
    attrs = {
        '__module__': 'myapp.routers', # 重要:指定模块,让Django能正确追踪
        'Meta': Meta,
    }

    model = type('UserLog', (BaseUserLog,), attrs)
    
    # 注册到Django的apps中(关键步骤,否则迁移和查询会出问题)
    from django.apps import apps
    if not apps.ready:
        # 如果apps未就绪,先简单缓存,通常在运行时调用时apps已就绪。
        get_user_log_model._model_cache[table_name] = model
    else:
        # 手动注册模型
        apps.register_model('myapp', model)
        get_user_log_model._model_cache[table_name] = model

    return model

# 工具函数:根据user_id获取对应的模型
def get_user_log_model_by_user_id(user_id):
    table_suffix = user_id % 10  # 分10张表
    return get_user_log_model(table_suffix)

(4) myproject/settings.py - 配置路由

DATABASE_ROUTERS = ['myapp.routers.UserLogRouter']

(5) 如何使用? 在你的视图或任何地方,像下面这样用:

from myapp.routers import get_user_log_model_by_user_id

# 写入数据
user_id = 12345
DynamicUserLog = get_user_log_model_by_user_id(user_id)
log_entry = DynamicUserLog.objects.create(user_id=user_id, action='login')
log_entry.save()

# 读取数据:同样需要先获取对应的模型类
DynamicUserLog = get_user_log_model_by_user_id(12345)
logs = DynamicUserLog.objects.filter(user_id=12345)
for log in logs:
    print(log.action, log.created_at)

# 注意:你不能直接用`UserLog.objects`全局查询所有分表。
# 需要聚合查询时会很麻烦,得查10次然后自己合并。

3. 使用第三方库 如果项目复杂,上面这套自己维护起来挺累。可以考虑用现成的库,它们封装了这些脏活:

总结一下: Django分表就得绕过ORM的默认约定,要么自己手动管表名(麻烦),要么用db_router动态创模型(主流做法),要么上第三方库(省心但可能有学习成本)。根据数据量和团队水平选吧。

一句话建议: 中小项目用动态模型方案可控性更好,大项目或团队直接考虑成熟的三方库。

懒……并且想试试 Django 的这部分功能,外加不知道以后是否会增加需求,综合以上三点没有选 SQL

试试更改 self.Meta.db_table 呢

我指的是你模型类的 self

什么数据库啊,可以从数据库层面的 partition 解决问题,使用主表表名,然后自动路由到正确的子表上去。

https://www.postgresql.org/docs/current/ddl-partitioning.html

回到顶部