Django - 获取/保存大对象耗时较长

0 投票
1 回答
522 浏览
提问于 2025-04-18 15:12

我正在尝试从一个模型中获取几百万个项目,并对它们进行解析。不过,不知道为什么,保存数据的过程花费了很多时间。

这是我目前拥有的模型:

class mapt(models.Model):
    s = models.IntegerField(primary_key=True)
    name = models.CharField(max_length=2000)

    def __unicode__(self):
        return str(self.s)

class datt(models.Model):
    s = models.IntegerField(primary_key=True)
    setid = models.IntegerField()
    var = models.IntegerField()
    val = models.IntegerField()

    def __unicode(self):
        return str(self.s)

class sett(models.Model):
    setid = models.IntegerField(primary_key=True)
    block = models.IntegerField()
    username = models.IntegerField()
    ts = models.IntegerField()

    def __unicode__(self):
        return str(self.setid)

class data_parsed(models.Model):
    setid = models.IntegerField(max_length=2000, primary_key=True)
    block = models.CharField(max_length=2000)
    username = models.CharField(max_length=2000)
    data = models.CharField(max_length=200000)
    time = models.IntegerField()

    def __unicode__(self):
        return str(self.setid)

datt模型中的s参数实际上应该作为mapt模型的s参数的外键。此外,sett模型中的setid字段应该作为setid的setid的外键。

最后,data_parsed中的setid是sett模型的外键。

目前算法是这样写的:

def database_rebuild(start_data_parsed):
    listSetID = []
    #Part 1
    for items in sett.objects.filter(setid__gte=start_data_parsed):
        listSetID.append(items.setid)
    uniqueSetID = listSetID 

    #Part 2
    for items in uniqueSetID:
        try:
            SetID = items
            settObject = sett.objects.get(setid=SetID)

            UserName = mapt.objects.get(pk=settObject.username).name
            TS = pk=settObject.ts
            BlockName = mapt.objects.get(pk=settObject.block).name

            DataPairs_1 = []
            DataPairs_2 = []
            DataPairs_1_Data = []
            DataPairs_2_Data = []

            for data in datt.objects.filter(setid__exact=SetID):
                DataPairs_1.append(data.var)
                DataPairs_2.append(data.val)

            for Data in DataPairs_1:
                DataPairs_1_Data.append(mapt.objects.get(pk=Data).name)

            for Data in DataPairs_2:
                DataPairs_2_Data.append(mapt.objects.get(pk=Data).name)

            assert (len(DataPairs_1) == len(DataPairs_2)), "Length not equal"

            #Part 3
            Serialize = []
            for idx, val in enumerate(DataPairs_1_Data):
                Serialize.append(str(DataPairs_1_Data[idx]) + ":PARSEABLE:" + str(DataPairs_2_Data[idx]) + ":PARSEABLENEXT:")


            Serialize_Text = ""
            for Data in Serialize:
                Serialize_Text += Data


            Data = Serialize_Text
            p = data_parsed(SetID,  BlockName, UserName, Data, TS)
            p.save()
        except AssertionError, e:
            print "Error:" + str(e.args)
            print "Possibly DataPairs does not have equal length"
        except Exception as e:
            print "Error:" + str(sys.exc_info()[0])
            print "Stack:" + str(e.args)

基本上,它的工作流程是:

  1. 找到所有大于某个数字的sett对象

  2. 获取UserName、TS和BlockName,然后获取datt字段中与mapt的s字段对应的var和val字段的所有字段。Var和Val基本上是NAME_OF_FIELD:VALUE类型的关系。

  3. 将所有的var和val参数序列化,以便我可以从var和val中获取所有参数,这些参数在data_parsed中以行的形式分布在mapt表中。

目前的解决方案可以完成我想要的所有功能,但是在一台Intel Core i5-4300U CPU @ 1.90Ghz的电脑上,它每天只能在celery定时任务中解析大约15000行数据。我在sett表中大约有3355566行数据,解析完这些数据大约需要23天。

有没有办法加快这个过程呢?

============================更新============================

新模型:

class mapt(models.Model):
    s = models.IntegerField(primary_key=True)
    name = models.CharField(max_length=2000)

    def __unicode__(self):
        return str(self.s)

class sett(models.Model):
    setid = models.IntegerField(primary_key=True)
    block = models.ForeignKey(mapt, related_name='sett_block')
    username = models.ForeignKey(mapt, related_name='sett_username')
    ts = models.IntegerField()

    def __unicode__(self):
        return str(self.setid)

# class sett(models.Model):
    # setid = models.IntegerField(primary_key=True)
    # block = models.IntegerField()
    # username = models.IntegerField()
    # ts = models.IntegerField()

    # def __unicode__(self):
        # return str(self.setid)

class datt(models.Model):
    s = models.IntegerField(primary_key=True)
    setid = models.ForeignKey(sett, related_name='datt_setid')
    var = models.ForeignKey(mapt, related_name='datt_var')
    val = models.ForeignKey(mapt, related_name='datt_val')

    def __unicode(self):
        return str(self.s)

# class datt(models.Model):
    # s = models.IntegerField(primary_key=True)
    # setid = models.IntegerField()
    # var = models.IntegerField()
    # val = models.IntegerField()

    # def __unicode(self):
        # return str(self.s)

class data_parsed(models.Model):
    setid = models.ForeignKey(sett, related_name='data_parsed_setid', primary_key=True)
    block = models.CharField(max_length=2000)
    username = models.CharField(max_length=2000)
    data = models.CharField(max_length=2000000)
    time = models.IntegerField()

    def __unicode__(self):
        return str(self.setid)

新的解析:

def database_rebuild(start_data_parsed, end_data_parsed):
    for items in sett.objects.filter(setid__gte=start_data_parsed, setid__lte=end_data_parsed):
        try:
            UserName = mapt.objects.get(pk=items.username_id).name
            TS = pk=items.ts
            BlockName = mapt.objects.get(pk=items.block_id).name

            DataPairs_1 = []
            DataPairs_2 = []
            DataPairs_1_Data = []
            DataPairs_2_Data = []

            for data in datt.objects.filter(setid_id__exact=items.setid):
                DataPairs_1.append(data.var_id)
                DataPairs_2.append(data.val_id)

            for Data in DataPairs_1:
                DataPairs_1_Data.append(mapt.objects.get(pk=Data).name)

            for Data in DataPairs_2:
                DataPairs_2_Data.append(mapt.objects.get(pk=Data).name)

            assert (len(DataPairs_1) == len(DataPairs_2)), "Length not equal"

            Serialize = []
            for idx, val in enumerate(DataPairs_1_Data):
                Serialize.append(str(DataPairs_1_Data[idx]) + ":PARSEABLE:" + str(DataPairs_2_Data[idx]))

            Data = ":PARSEABLENEXT:".join(Serialize)
            p = data_parsed(items.setid, BlockName, UserName, Data, TS)
            p.save()
        except AssertionError, e:
            print "Error:" + str(e.args)
            print "Possibly DataPairs does not have equal length"
        except Exception as e:
            print "Error:" + str(sys.exc_info()[0])
            print "Stack:" + str(e.args)

1 个回答

1

用重复添加的方式来定义列表是非常慢的。你可以使用列表推导式,或者直接用 list() 构造函数。

在 Python 中,不要用 for 循环和 += 来连接字符串列表,应该使用 join() 方法。

不过,这里主要的问题不是这个。你有很多 objects.get() 的调用,每次都会和数据库进行一次交互。如果你的 mapt 表里没有几百万行数据的话,建议你直接创建一个字典,把 mapt 的主键映射到 mapt 对象上。

如果你把外键定义成外键,Django 的 ORM 可以帮你用大约五个查询就完成很多操作。也就是说,你可以用 some_instance.some_fk 来代替 SomeModel.objects.get(id=some_instance.some_fk_id)(这样每个实例第一次访问时才会查询数据库)。如果 some_instance 是这样初始化的 some_instance = SomeOtherModel.objects.select_related('some_fk').get(id=id_of_some_instance),那么你甚至可以省去外键查询。

也许在不改变数据库的情况下修改模型是可行的。

撰写回答