You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何修复Django导出Excel函数的内存占用过高问题?

解决Django导出Excel内存占用过高问题

原代码核心问题分析

  • 全量数据加载到内存:使用tablib的Dataset存储所有数据后一次性导出,导致1GB内存占用。
  • 冗余密码生成:循环10次生成密码但每次重置变量,最终仅保留最后一次结果,完全无效。
  • 路径拼接不规范:直接字符串拼接路径易出错,且重复代码过多。
  • 全量数据库查询:一次性加载所有Product数据到内存,未做分批处理。
  • 手动内存回收冗余:手动删除变量并调用gc.collect()属于多余操作,Python垃圾回收机制会自动处理。

优化方案

  1. 流式写入Excel:用xlsxwriter逐行写入数据,避免全量数据占用内存。
  2. 优化密码生成:仅生成一次随机密码,移除无效循环。
  3. 规范路径处理:用os.path.join拼接路径,提升代码稳定性和可读性。
  4. 分批查询数据:使用iterator()分批获取数据库数据,减少内存占用。
  5. 简化文件操作:用glob批量删除旧文件,替代重复的listdir操作。
  6. 移除手动内存回收:删除不必要的del和gc.collect()调用。

优化后的完整代码

@sync_to_async
@login_required
def export_excel(request):
    import os
    import random
    import datetime
    import zipfile
    import xlsxwriter
    from django.conf import settings
    from glob import glob

    # 生成随机密码(仅生成一次)
    chars = 'abcdefghijklnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
    password = ''.join(random.choice(chars) for _ in range(10))

    # 规范生成路径
    user_id = str(request.user.usrid.listusers_id)
    base_path = os.path.join(settings.MEDIA_ROOT, 'pricenew', user_id)
    paths = {
        'zip': os.path.join(base_path, 'zip'),
        'rar': os.path.join(base_path, 'rar'),
        'xlsx': os.path.join(base_path, 'xlsx'),
    }

    # 创建目录(已存在则忽略)
    for path in paths.values():
        os.makedirs(path, exist_ok=True)

    # 检查缓存文件是否有效
    xlsx_files = glob(os.path.join(paths['xlsx'], '*'))
    zip_files = glob(os.path.join(paths['zip'], '*'))
    pricesecret = Price.objects.get(id=1)
    lock_file = os.path.join(paths['rar'], f"{pricesecret.url}.lock")

    if (os.path.exists(lock_file) 
        and xlsx_files 
        and os.stat(xlsx_files[0]).st_size != 0 
        and zip_files 
        and os.stat(zip_files[0]).st_size != 0):
        
        zip_filename = os.path.basename(zip_files[0])
        url = f"https://localhost/media/pricenew/{user_id}/zip/{zip_filename}"
        return JsonResponse({'pricesecret': zip_filename, 'url': url})
    else:
        # 删除所有旧文件
        for path in paths.values():
            for file_path in glob(os.path.join(path, '*')):
                os.remove(file_path)

        # 创建锁文件
        with open(lock_file, "wb"):
            pass

        # 生成Excel文件路径
        xlsx_file_path = os.path.join(paths['xlsx'], f'price_{password}.xlsx')

        # 流式写入Excel
        workbook = xlsxwriter.Workbook(xlsx_file_path)
        worksheet = workbook.add_worksheet("Прайс")

        # 写入表头
        headers = (
            'Код', 'Артикул', 'Наименование', 'Производитель', 'Ед', 'Цена,руб', 
            'Остаток', 'Заявка', 'Норма отпуска', 'Новинка!'
        )
        for col_num, header in enumerate(headers):
            worksheet.write(0, col_num, header)

        # 分批获取产品数据(iterator避免全量加载)
        products = Product.objects.filter(
            stock__gt=0, published=True
        ).select_related('brand', 'product_sklad').prefetch_related(
            'brand__clt1__client_id__discount_list_k1', 'category__parent'
        ).exclude(product_sklad__gte=1).iterator()

        # 获取禁用品牌列表
        banned_brands = list(Salenban.objects.filter(
            client_id=request.user.usrid.listusers_id, published=1
        ).values_list('mf_un', flat=True))

        # 逐行写入数据
        row_num = 1
        for book in products:
            zayvka = ""
            artikul = book.artikul if book.artikul != "None" else ""
            brand = book.brand if book.brand != "None" else ""
            new_product = book.new_product if book.new_product != 0 else ""
            stock = ">50" if book.stock > 50 else book.stock

            row_data = (
                book.id_p, artikul, book.name, brand, book.shtuk_e, 
                round(book.user_max2(), 2), stock, zayvka, book.min_kol, new_product
            )

            for col_num, value in enumerate(row_data):
                worksheet.write(row_num, col_num, value)
            row_num += 1

        workbook.close()

        # 压缩Excel文件
        zip_file_path = os.path.join(paths['zip'], f'price_{password}.zip')
        with zipfile.ZipFile(zip_file_path, "w", zipfile.ZIP_DEFLATED, compresslevel=9) as zf:
            zf.write(xlsx_file_path, os.path.basename(xlsx_file_path))

        # 返回下载链接
        url = f"https://localhost/media/pricenew/{user_id}/zip/price_{password}.zip"
        return JsonResponse({'pricesecret': f"{password}.zip", 'url': url})

关键优化点说明

  • 流式Excel写入:xlsxwriter直接操作文件句柄,逐行写入数据,内存仅占用单条记录的大小。
  • 数据库迭代查询:iterator()方法让Django不缓存所有查询结果,逐批从数据库获取数据。
  • 路径规范:通过os.path.join和settings.MEDIA_ROOT管理路径,避免硬编码错误。
  • 简化文件操作:glob批量匹配文件,删除旧文件的逻辑更简洁高效。
  • 冗余代码移除:删除无效循环、手动内存回收等不必要代码,提升性能和可读性。

内容的提问来源于stack exchange,提问作者slavyan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 15:27:02