Django 2.0 + DRF处理POST请求中超大JSON的高效方案咨询
我之前接手过类似的超大JSON导入任务,数GB的文件、数百万条数据,一开始也是内存爆得一塌糊涂,后来踩了不少坑,总结出几个关键思路,应该能解决你的问题:
1. 绝对不要一次性加载整个JSON文件
这是内存爆炸的根源!别用json.load()把整个文件塞进内存,改用流式解析,只在内存中保留当前处理的单条数据。
推荐用ijson库(专门处理大JSON的流式解析工具),它可以逐对象读取JSON,不会加载整个文件。安装很简单:
pip install ijson
比如你的JSON是数组格式([{}, {}, ...]),可以这样解析:
import ijson with open('huge_data.json', 'rb') as f: # 'item'对应数组中的每个元素,根据你的JSON结构调整路径 for item in ijson.items(f, 'item'): # 处理单条item,比如转成模型实例 process_single_item(item)
如果你的JSON是每行一个对象(JSON Lines格式),更简单,直接逐行读取:
import json with open('huge_data.json', 'r') as f: for line in f: item = json.loads(line.strip()) process_single_item(item)
2. 批量操作+手动清理内存
Django的save()方法每次都会触发数据库交互,不仅慢还会累积内存。改用bulk_create()批量插入,同时处理完一批后手动清理内存:
from django.db import transaction import gc from myapp.models import YourModel batch_size = 1000 # 可根据服务器内存调整,建议1000-10000之间 batch = [] with open('huge_data.json', 'rb') as f: for item in ijson.items(f, 'item'): obj = YourModel( field1=item['key1'], field2=item['key2'], # 映射你的模型字段 ) batch.append(obj) if len(batch) >= batch_size: # 用事务包裹,保证批量插入的原子性 with transaction.atomic(): YourModel.objects.bulk_create(batch) # 强制清理内存:清空列表、删除引用、触发垃圾回收 batch.clear() del batch[:] gc.collect() # 处理最后一批剩余数据 if batch: with transaction.atomic(): YourModel.objects.bulk_create(batch)
这里的关键是:处理完一批就立刻清空batch,并调用gc.collect()强制回收内存——Django的ORM对象有时候不会自动释放,手动触发很有必要。
3. 用异步任务脱耦web进程
如果是通过DRF接口上传超大文件,绝对不要在web请求中同步处理!web进程内存有限,而且请求超时时间也不允许。应该把文件保存到临时目录,然后丢给异步任务处理(比如用Celery):
from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.parsers import FileUploadParser from celery import shared_task import os @shared_task def process_large_json_task(file_path): # 这里放上面的流式解析+批量插入代码 batch_size = 1000 batch = [] with open(file_path, 'rb') as f: for item in ijson.items(f, 'item'): obj = YourModel(...) batch.append(obj) if len(batch) >= batch_size: with transaction.atomic(): YourModel.objects.bulk_create(batch) batch.clear() gc.collect() if batch: with transaction.atomic(): YourModel.objects.bulk_create(batch) # 处理完删除临时文件 os.remove(file_path) class LargeJSONUploadView(APIView): parser_classes = (FileUploadParser,) def post(self, request, filename, format=None): file_obj = request.data['file'] # 保存到临时目录 temp_path = f'/tmp/large_upload_{filename}' with open(temp_path, 'wb+') as dest: for chunk in file_obj.chunks(): dest.write(chunk) # 启动异步任务 process_large_json_task.delay(temp_path) return Response({'status': 'processing'}, status=202)
这样web进程只负责接收文件,真正的处理在后台异步进行,不会占用web服务的内存,也不会超时。
4. 极端情况:绕过ORM用原生SQL
如果数据量实在大到ORM都扛不住(比如数亿条),可以直接用Django的游标执行原生SQL批量插入,跳过ORM对象的内存开销:
from django.db import connection with open('huge_data.json', 'rb') as f: batch = [] for item in ijson.items(f, 'item'): # 把数据转成SQL需要的元组 data_tuple = (item['key1'], item['key2'], ...) batch.append(data_tuple) if len(batch) >= 10000: with connection.cursor() as cursor: # 构造批量插入SQL placeholders = ', '.join(['%s'] * len(data_tuple)) sql = f"INSERT INTO your_table (field1, field2, ...) VALUES {placeholders}" cursor.executemany(sql, batch) batch.clear() gc.collect()
这种方式内存占用极低,但要注意SQL注入风险(确保数据是可信的),以及字段类型要和数据库匹配。
几个避坑小贴士
- 不要在循环中保留任何不必要的对象引用,比如不要把所有模型实例存在一个大列表里;
- 调整
batch_size:太大容易内存溢出,太小会增加数据库IO,建议根据服务器内存测试; - 如果用Docker部署,记得给容器分配足够的内存,但更重要的是优化代码而非加内存;
- 监控内存使用:可以用
psutil库在代码中打印内存占用,找到内存泄漏的点。
内容的提问来源于stack exchange,提问作者hannu40k

