一种django-mysql分表方案

一种django-mysql分表方案

Overview

那么如何使用django+mysql的分表方法来进行大量数据的读写处理呢?答案是肯定的。但是本人在搜索国内外的部分资料后发现,在使用django进行分表时没有比较优秀的方案。国内文章虽然结果比较多,但是有效的文章很少大部分内容抄袭冗余严重。极大的影响着学习者的热情并且浪费使用者的时间。

于是我在django现有的方法中,发展一套分表的解决方案。注意这只是解决方案的一种。供大家学习使用,下面将涉及一些源码和源码的注释。还有一些伪代码和思路。如果你希望得到一个可以直接使用的代码包,那么我将遗憾的说,可能你要失望了。因为我比较讨厌伸手党。但是我保证如果你认真的读完这篇文章,你便可以解决你手头上的问题。

还有我真的有将现在这里一些重复工作变成开源包的想法,但是事实上我对自己的设计能力不够自信。所以如果你是资深的python开发工程师,我道真希望你提出建议。并且期待和你的交流。

Note

注意如何你要使用一下技术是可以使用的:

  • django框架
  • 安装时间分表
  • 使用mysql数据库
  • 使用pandas做数据处理
  • 使用抽象的model和数据库的表进行对应
  • 不同的model使用不同的分表规则(不同是时间间隔)
  • 不同的model使用不同数据库

注意涉及一下的特性是不能够实现的:

  • 使用数据个数的分表规则
  • 使用model级别的复杂查询
  • 在model层描述数据表之间的关系,如一对多,多对多等
  • 在生成的表中每个字段存在字段注释

大体思路

确定表名规则

首先我们要明白的一点是:我们为什么要分表,因为数据量大。于是我们首要的问题就是分表的规则的问题。我这里暂时只讨论使用时间分表的问题。(这里暗示了我们要处理实时数据保存的任务)

我们现在要做的是:无论我给出一个什么时间,我应该计算处理这个时间应该对应什么表

我们第一个任务便是,建立一个可以根据时间计算表名的方法。这个我相信对应任何一个有经验的python开发来说不会很难。于是我这里不放代码了。(正好这里也是一个考验,如果你写不出来这样的一个函数的话,我或许会建议你先不要读下面的内容。)下面我会把这个函数的要求给你的:

要求:实现根据时间计算表名的函数。要是传入一个时间范围的数据然后根据时间间隔和传入的表前缀生成完整的表名的列表。比如我们按照3个小时做一个分表。然后我们发现16点到20点的数据分成了两个表(注意这里我是打比方不一定是两张)那么我们输入开始时间和结束时间,然后输入一个表前缀最后输出涉及的所有数据的表名。

像这样 (star_time,end_time,table_prefix, interval) => [ ‘table_prefix_2018_09_11_3_12’, ‘table_prefix_2018_09_11_3_13’] 这里输出两个表名因为这里的数据与两表有关

确定处理主体

好了我们已经明白了任何一个时间的数据要进那个表了。这个是我们进行分表的基础。接下来我们要明白我们给我们的数据和model一个映射,让model中已经定义到数据模型,和我们分开表里的数据模型一一对应才行。

能够做到吗?网上大部分资料告诉我们数据库中的表是和model中的tb_name对应的。也就是说正常情况下django是一个表和一个model对应的。现在想要让一个model对应所有的表是一件困难的事。我想到的方法是让时间段来控制表的对应关系。也就是让代码层来接管class中的tb_name属性。于是我们要用到的就是python中有关源类的知识。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class NewDynamicModel(object):
_instance = dict()
def __new__(cls, base_cls, tb_name):
"""
创建类根据表名
:param base_cls: 类名(这个类要models基类)
:param tb_name: 表名
:return: base_cls类的实例
"""
new_cls_name = "%s_To_%s" % (base_cls.__name__, '_'.join(map(lambda x: x.capitalize(), tb_name.split('_'))))
if new_cls_name not in cls._instance:
new_meta_cls = base_cls.Meta
new_meta_cls.db_table = tb_name
model_cls = type(str(new_cls_name), (base_cls,),
{'__tablename__': tb_name, 'Meta': new_meta_cls, '__module__': cls.__module__})
cls._instance[new_cls_name] = model_cls
return cls._instance[new_cls_name]

这段代码并不是我独创的,而是在百度上找到的,这里很明显new_meta_cls.db_table = tb_name这句代码中控制了我们具体的model中的tb_table从而控制新生成的class对应那张表的问题。那么我们怎么知道表名呢?那就是我们上文中提到的那个获取表名的方法啦。

表不存在的问题

上面的方法写好了,但是还是有一个问题。上面方法适用于分表已经完成情况下,表存在后这些新生成的class是可以找到正常的表,但是倘若原来的表不存在的情况下,这些代码就要报错的。更糟糕的是,我们面对的场景下这种像新表中插入数据的任务是经常发生的。

于是我们清楚我们的目标是:在已经知道model的情况下,使用代码生成表

我敢打赌如果你使用百度查询这些关键字情况下一定查询不到什么有用的信息。这里我直接给出一个比较好的答案:那就是使用SchemaEditor 使用schemaEditor可以很好的使用model的class来生成的对应的表。并且你还可通过某种方法指定生成在那个数据库里。

1
2
3
from django.db import connection
with connection.schema_editor() as schema_editor:
schema_editor.create_model(MyModel)

看到这段官方文档的示例代码你或许就明白了。这里我把原来的删除换成了新建表。聪明的你也肯定明白这里只要把connection换成使用connections就可以使用不同数据库作为你这个model新建表的位置了。

当然美中不足的是,这里的新建表方法如果存在了这个表是要报错的。所以我建议你先写一个判断表名是否存在的方法。

使用pandas处理数据

我们现在可以得到models对应的数据了。比如我要链接两个表的数据(垂直表的数据),或者水平表的两个字段链接查询,都要写出很复杂的sql。而且更加令人担心的是,django并没有给这些复杂恼人的查询提供良好的分装。在django和model的世界里,我想用分表进行一个分组后的limit都是一件困难的事,更别提其他复杂的所谓的骚操作了。那么怎么办呢?使用是pandas处理数据吧。这个很好想到。

于是我们的核心问题变成了: 如何建立models到,dataframe的映射

恕我直言,这个可以说是目前为止倒数第二简单的内容。因为我们已经有了涉及要查询的表的全部的class了。也就是说我们其实已经可以得到所需要的数据构成的list了(如果你还反应不过来,这里可以得到的是QuerySet对象)那么我们现在要做的就是以后list[object]转成dataframe对象的问题。

这个简单,我将不在描述。注意这里有一点我认为有趣的地方。那就是我们要给生成的数据多一列。那就是生成的时间后缀suffix这是为什么呢?

思考一下如果我们把两个表里的数据合并成一个表,但是里面的主键都是从0开始的。那么会发生什么呢?对的主键碰撞,也就是说我们通过一会个表后缀来解决数据这里id找不到数据问题。除此之外还有什么好处呢?我们可以使用原来的dataframe直接存到新的model的对应关系中(我们可以简单处理,甚至只需改一个字段),注意如果原来的表存和新表只有表名不一样分表的规则是相同的。也就是说我们可以直接使用表名加上这个后缀来生成新表。并且依赖于pandas强大的分组功能,我们可以对不同的suffix数据进行分组并行处理。

后续工作

好的,现在整个大的架子已经搭起来了。我们现在思路也确定了。先把models对应的数据即使是分表的也读到一个dataframe中,然后在进行dataframe进行处理。处理到和新的models对应的程度。(我这里强烈的暗示了,数据表中的字段和datafram的列名一致)于是乎我们面临下面几个核心问题:

  • 如果将dataframe的数据批量插入到model对应的表中
  • 如果将dataframe的数据批量更新到model对应的表中

删除当然很简单,所以就没有列出来。

现在晚了,我也累了,所以如果你有耐心,可以等我下一次更新完成这些内容,也可以自己完成共享出来,我将很乐意将你的文章链接贴在这里。

我将一直的迷惑与无知,我是黄油香蕉君,再见。

给作者买杯咖啡吧。喵~