Python中如何实现Django的分表操作?
现在有一个问题,数据库每天会创建一张新的表,表名格式大概为:
'prefix_{}'.format(datetime.datetime.now().strftime("%Y%m%d"))
而现在在开发一个 Django 后端程序,仅仅需要用 django-admin 链接数据库进行一些很基础的信息展示即可。不过现在卡在了怎么才能动态的使用 model 更换数据库表名,拿到当前日期的数据呢?
Python中如何实现Django的分表操作?
既然只是为了展示一些基础数据为什么不直接写 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_0 到 user_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-sharding: 功能比较全。django-db-partition: 专门做分区的。
总结一下:
Django分表就得绕过ORM的默认约定,要么自己手动管表名(麻烦),要么用db_router动态创模型(主流做法),要么上第三方库(省心但可能有学习成本)。根据数据量和团队水平选吧。
一句话建议: 中小项目用动态模型方案可控性更好,大项目或团队直接考虑成熟的三方库。
懒……并且想试试 Django 的这部分功能,外加不知道以后是否会增加需求,综合以上三点没有选 SQL
试试更改 self.Meta.db_table 呢
我指的是你模型类的 self
什么数据库啊,可以从数据库层面的 partition 解决问题,使用主表表名,然后自动路由到正确的子表上去。
https://www.postgresql.org/docs/current/ddl-partitioning.html

