加快 Django 表单以将大型(500k obs)CSV 文件上传到 MySQL 数据库

Posted

技术标签:

【中文标题】加快 Django 表单以将大型(500k obs)CSV 文件上传到 MySQL 数据库【英文标题】:SPEED UP Django Form to Upload large (500k obs) CSV file to MySQL DB 【发布时间】:2015-03-10 20:34:58 【问题描述】:

Django 表大约有 430,000 obs 和 230mb 文件;\ 并且来自以下详细信息概述的平面 CSV 文件\ 模型.PY。我考虑过为 CSV 阅读器使用块,但我认为处理器\ 我有填充 mysql 表的函数是我的挂断;需要20小时+\ 我怎样才能加快速度??

class MastTable(models.Model):
    evidence = models.ForeignKey(Evidence, blank=False)
    var2 = models.CharField(max_length=10, blank=True, null=True)
    var3 = models.CharField(max_length=10, blank=True, null=True)
    var4 = models.CharField(max_length=10, blank=True, null=True)
    var5 = models.CharField(max_length=10, blank=True, null=True)
    var6 = models.DateTimeField(blank=True, null=True)
    var7 = models.DateTimeField(blank=True, null=True)
    var8 = models.DateTimeField(blank=True, null=True)
    var9 = models.DateTimeField(blank=True, null=True)
    var10 = models.DateTimeField(blank=True, null=True)
    var11 = models.DateTimeField(blank=True, null=True)
    var12 = models.DateTimeField(blank=True, null=True)
    var13 = models.DateTimeField(blank=True, null=True)
    var14 = models.CharField(max_length=500, blank=True, null=True)
    var15 = models.CharField(max_length=500, blank=True, null=True)
    var16 = models.CharField(max_length=50, blank=True, null=True)
    var17 = models.CharField(max_length=500, blank=True, null=True)
    var18 = models.CharField(max_length=500, blank=True, null=True)
    var19 = models.CharField(max_length=500, blank=True, null=True)
    var20 = models.CharField(max_length=500, blank=True, null=True)
    var21 = models.CharField(max_length=500, blank=True, null=True)
    var22 = models.CharField(max_length=500, blank=True, null=True)
    var23 = models.DateTimeField(blank=True, null=True)
    var24 = models.DateTimeField(blank=True, null=True)
    var25 = models.DateTimeField(blank=True, null=True)
    var26 = models.DateTimeField(blank=True, null=True)

此帮助函数将为 CSV 创建一个读取器对象\ 并在 MySQL 上传之前解码文件中的任何时髦编解码器

def unicode_csv_reader(utf8_data, dialect=csv.excel, **kwargs):
    csv_reader = csv.reader(utf8_data, dialect=dialect, **kwargs)
    for row in csv_reader:
        yield [unicode(cell, 'ISO-8859-1') for cell in row]

UTILS.PY 文件中的函数将访问一个数据库表(名为“extract_properties”),该表\ 包含文件头以标识要转到哪个处理器功能\ 处理器函数如下所示

def processor_table(extract_properties):  #Process the table into MySQL
    evidence_obj, created = Evidence.objects.get_or_create(case=case_obj, 
    evidence_number=extract_properties['evidence_number']) #This retrieves the Primary Key
    reader = unicode_csv_reader(extract_properties['uploaded_file'],dialect='pipes') #CSVfunction  
    for idx, row in enumerate(reader):
        if idx <= (extract_properties['header_row_num'])+3: #Header is not always 1st row of file
            pass
        else:
            try:
                obj, created = MastTable.objects.create( #I was originally using 'get_or_create'
                    evidence=evidence_obj,
                    var2=row[0],
                    var3=row[1],
                    var4=row[2],
                    var5=row[3],
                    var6=date_convert(row[4],row[5]), #funct using 'dateutil.parser.parse'
                    var7=date_convert(row[6],row[7]),
                    var8=date_convert(row[8],row[9]),
                    var9=date_convert(row[10],row[11]),
                    var10=date_convert(row[12],row[13]),
                    var11=date_convert(row[14],row[15]),
                    var12=date_convert(row[16],row[17]),
                    var13=date_convert(row[18],row[19]),
                    var14=row[20],
                    var15=row[21],
                    var16=row[22],
                    var17=row[23],
                    var18=row[24],
                    var19=row[25],
                    var20=row[26],
                    var21=row[27],
                    var22=row[28],
                    var23=date_convert(row[29],row[30]),
                    var24=date_convert(row[31],row[32]),
                    var25=date_convert(row[33],row[34]),
                    var26=date_convert(row[35],row[36]),)
            except Exception as e:  #This logs any exceptions to a custom DB table
                print "Error",e
                print "row",row
                print "idx:",idx
                SystemExceptionLog.objects.get_or_create(indexrow=idx, errormsg=e.args[0],     
                timestamp=datetime.datetime.now(),   
                uploadedfile=extract_properties['uploaded_file'])
                continue
    return True 

最后是下面的 VIEWS.PY 表单来接受文件并调用上面的处理器来填充数据库 检查有效的表单数据并将任何文件传递给文件处理程序(如果有效)

def upload_file(request):
        if request.method == 'POST':
        form = UploadFileForm(request.POST, request.FILES)
        if form.is_valid():
            for _file in request.FILES.getlist('file'): 
                extract_properties = get_file_properties(_file) 
                if extract_properties:
                    for property in extract_properties: #File is found and processor kicked off 
                        print "starting parser"
                        try:
                            property['evidence_number'] = request.POST.get('evidence_number')
                            result = process_extract(property)
                            if result is None:
                                print 'Unable to get determine extract properties!'
                        except Exception as e:
                            print "!!!!!!!"
                            print "Error, could not upload", e
                            pass
                 else:
                    print 'Unable to identify file uploaded!' 
            return HttpResponseRedirect('')
        print form
    else:
        form = UploadFileForm()
    return render_to_response('nettop/upload_file.html',  # The web frontend Page for Upload
                              'form': form,
                              context_instance=RequestContext(request))

【问题讨论】:

【参考方案1】:

此 csv 文件是否包含无效行?我的意思是你真的需要这条线吗?

except Exception as e:  #This logs any exceptions to a custom DB table

如果没有抛出此类错误,那么您应该使用bulk_create() 而不仅仅是create()

我还建议在单个事务中执行导入。这是一个巨大的速度助推器:

from django.db import transaction

with transaction.atomic():
    processor_table(extract_properties)

【讨论】:

异常报告是因为数据源可以是动态的。之前,我在使用时髦的编解码器时遇到了一些问题。我尝试过使用 bulk_create。我有点不确定,但对“transaction.atomic()”很感兴趣。这是否类似于只使用 MySQL 接口并完全跳过 Django ORM?(对我来说是一个考虑过的替代方案) 跳过 Django ORM 并使用事务不是替代方案 - 您可以(并且,恕我直言,应该)两者都做。首先尝试transaction.atomic(),它很简单,可以将您的应用程序的速度提高一个数量级。之后切换到纯 SQL 以获得额外的速度提升。【参考方案2】:

Django 中最基本和最有效的优化是减少对数据库的查询次数。这对于 100 个查询是正确的,对于 500.000 个查询肯定是正确的。

您应该构造一个未保存模型实例的列表,而不是使用MastTable.objects.create(),并使用MastTable.objects.bulk_create(list_of_models) 在尽可能少的数据库往返中创建它们。这应该会大大加快速度。

如果您使用的是 MySQL,您可以增加 max_allowed_packet 设置以允许更大的批处理。它的默认值 1MB 非常低。 PostGRESQL 没有硬编码限制。如果您仍然遇到性能问题,可以切换到raw SQL statements。创建 500.000 个 python 对象可能有点开销。在我最近的一项测试中,使用connection.cursor 执行完全相同的查询要快约 20%。

最好将文件的实际处理留给后台进程,例如使用Celery,或使用StreamingHttpResponse 在此过程中提供反馈。

【讨论】:

以上是关于加快 Django 表单以将大型(500k obs)CSV 文件上传到 MySQL 数据库的主要内容,如果未能解决你的问题,请参考以下文章

django 管理表单上的大型多对多关系

在 django 中检索表单字段属性

Django - 如果国家/地区字段等于美国,则发送 ajax 请求以将状态选择器设置为美国各州的元组

如何将用户对象传递给 Django 中的表单

Powershell 问题 - 寻找最快的方法来遍历 500k 个对象以在另一个 500k 对象数组中寻找匹配项

Django中动态表单的任何现有解决方案?