Django ORM 不内置“分表”,但可以通过动态 Model 实现。
核心思路:让同一个逻辑 Model 映射到不同物理表。
1. ORM 是怎么确定表名的
Django 的查询链路大致是:
Model→Manager→QuerySet→ SQL- 表名由
Model.Meta.db_table决定
示例:
class User(models.Model):
user_id = models.IntegerField()
user_name = models.CharField(max_length=256)
password = models.CharField(max_length=256)
class Meta:
db_table = "user"
这意味着,当你写:
User.objects.filter(user_id=10).first()
实际上已经固定了访问的表名。
2. 方案一:穷举 Model(不推荐)
把同一逻辑表拆成多个 Model:
class User(models.Model):
user_id = models.IntegerField(primary_key=True)
user_name = models.CharField(max_length=256)
password = models.CharField(max_length=256)
class User0(User):
class Meta:
db_table = "user_0"
class User1(User):
class Meta:
db_table = "user_1"
class User2(User):
class Meta:
db_table = "user_2"
再根据 user_id 路由:
user_model_map = {0: User0, 1: User1, 2: User2}
TABLE_NUM = 3
def get_user_db_model(user_id):
return user_model_map[user_id % TABLE_NUM]
问题:分 1000 张表就要写 1000 个类,显然不现实。
3. 方案二:动态创建 Model(推荐)
核心是:运行时生成 Model + 缓存。
from django.db import models
TABLE_NUM = 3
class User(models.Model):
@classmethod
def get_user_db_model(cls, user_id=None):
suffix = user_id % TABLE_NUM
table_name = "user_%s" % suffix
if table_name in cls._user_model_dict:
return cls._user_model_dict[table_name]
class Meta:
db_table = table_name
attrs = {
"__module__": cls.__module__,
"Meta": Meta,
}
user_db_model = type(str("User_%s" % suffix), (cls,), attrs)
cls._user_model_dict[table_name] = user_db_model
return user_db_model
_user_model_dict = {}
user_id = models.IntegerField()
user_name = models.CharField(max_length=256)
password = models.CharField(max_length=256)
class Meta:
abstract = True
使用方式:
db_model = User.get_user_db_model(user_id)
user = db_model.objects.get(user_id=user_id)
这比复制粘贴强很多,但还是不够优雅。
4. 方案三:封装为通用基类
目标:让任意 Model 继承后即可分表。
from django.db import models
class Object:
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
def _model_new(cls, *args, **kwargs):
return cls(*args, **kwargs)
class ShardModel:
"""
ShardModel support table horizontal partition.
"""
_shard_db_models = {}
def __new__(cls, *args, **kwargs):
shard_key = kwargs.pop("shard_key", 0) % cls.Config.table_num
model_name = f"{cls.__name__}_{shard_key}"
model_class = cls._shard_db_models.get(model_name)
if model_class is not None:
return model_class
attrs = dict(cls.__dict__)
if "objects" in attrs:
attrs["objects"] = attrs["objects"].__class__()
meta = Object(**cls.Meta.__dict__)
meta.db_table = meta.db_table % shard_key
attrs["Meta"] = meta
attrs["new"] = classmethod(_model_new)
model_class = type(model_name, tuple([models.Model] + list(cls.__bases__[1:])), attrs)
cls._shard_db_models[model_name] = model_class
return model_class
class User(ShardModel):
user_id = models.IntegerField()
user_name = models.CharField(max_length=256)
password = models.CharField(max_length=256)
class Config:
table_num = 3
class Meta:
app_label = "default"
db_table = "user_%s"
调用:
user = User(shard_key=user_id).objects.get(user_id=user_id)
5. 注意事项
- 迁移时要确保所有分表都存在。
- 分表后,跨表统计会更复杂,建议先在业务侧聚合。
- 分表不是银弹,优先评估缓存、索引与读写分离。
小结
从“穷举 Model”到“动态生成 + 缓存”,再到“通用基类”,分表的可维护性会大幅提升。