由于数据量快速增长,导致接口性能问题,而分表是一个比较有效的解决方式。这里主要探讨以下几个问题
首先是从接口性能出发,用jmeter测试一遍接口,找出存在性能问题的接口,然后添加打点日志,找到影响性能的相关model。基本上是一些数据量大的、关联关系多、存在多个textfield字段查询的一些model。这里要说的是Django一些自带的接口,是存在比较严重的性能问题的,比如bulk_update批量更新的接口,如果更新的object中有多对多的关系,通过打debug日志可以看到Django做了很多连表操作,就会特别慢,甚至要好几秒。
我们系统最后是确定对五个大表进分表,按照一个标注任务(数据量几万到几十万不等)为单位进行分表。
纵向分表,就是竖着把一个很多字段的表分为N个,这N个表是1对1的关系,这个方案不适用我们系统。
横向分表,顾名思义就是横着把一个表分为N个,表的结构是一样的,是根据不同规则进行分表,比如id为单数一个表,双数一个表。我们采用的就这个方案,使用任务id来进行分表,一个任务一个表。
Django 提供了根据model创建表的方法 create_model,具体可以参考官方文档,下面有具体实现的例子。
有了根据model创建表的方法之后,那么我们要考虑的就是如何动态生成model。我调研了一下网上大佬们的做法,梳理了一下,大致有两种方法:
from django.db import models, connection
# 对model进行二次封装
def get_task_data_model(task_id=None, init=False):
table_name = 'task_data' # 这里是没有做分表的老数据,需要兼容一下
split_table_name = 'task_data_%r' % task_id
if task_id and (split_table_name in connection.introspection.table_names() or init):
table_name = split_table_name
class TaskData(models.Model):
status = models.IntegerField(default=0)
desc = models.TextField(null=True)
ctime = models.DateTimeField(auto_now_add=True)
mtime = models.DateTimeField(auto_now=True)
class Meta:
db_table = table_name
return TaskData
# 根据动态model来创建相应的表
with connection.schema_editor() as schema_editor:
logger.info("start create table [%s]" % table_name)
task_data_model = get_task_data_model(task_id, init=True)
schema_editor.create_model(task_data_model)
logger.info("create table [%s] successfully" % table_name)
# 根据动态model进行增删查改,Django orm的方法都可以使用
task_data_model = get_task_data_model(task_id)
task_data_model.objects.create() # 增
task_data_model.objects.filter() # 查
task_data_model.objects.filter().delete() # 删
task_data_model.objects.filter().update() # 改
为了说明这个问题,我举个例子,假设分表前我有两个model,User和Project,Project里有一个创建人creator的字段,外键关联了User表:
creator = models.ForeignKey(
User,
db_constraint=False,
null=True,
on_delete=models.DO_NOTHING
)
我们知道,外键实际存的是一个id,假如User进行了分表,每个表都是自增id,那么creator_id就会出现重复的情况,我们就不知道他是来自哪个分表的user了。ManyToManyField也是如此。解决这个问题有几种方式