Python Django实现MySQL百万、千万级的数据量下载:解决memoryerror、nginx time out

前文

  在用Django写项目的时候时常需要提供文件下载的功能,而Django也是贴心提供了几种方法:FileResponse、StreamingHttpResponse、HttpResponse,其中FileResponse和StreamingHttpResponse都是使用迭代器迭代生成数据的方法,所以适合传输文件比较大的情况;而HttpResponse则是直接取得数据返回给用户,所以容易造成memoryerror和nginx time out(一次性取得数据和返回的数据过多,导致nginx超时或者内存不足),关于这三者,DJango的官网也是写的非常清楚,连接如下:https://docs.djangoproject.com/en/1.11/ref/request-response/
  那正常我们使用的是FileResponse和StreamingHttpResponse,因为它们流式传输(迭代器)的特点,可以使得数据一条条的返回给客户端,文件随时中断和复传,并且保持文件的一致性。

FileResponse和StreamingHttpResponse

  FileResponse顾名思义,就是打开文件然后进行传输,并且可以指定一次能够传输的数据chunk。所以适用场景:从服务端返回大文件。缺点是无法实时获取数据库的内容并传输给客户端。举例如下:

def download(request):
	file=open('path/demo.py','rb')
    response =FileResponse(file)
    response['Content-Type']='application/octet-stream'
    response['Content-Disposition']='attachment;filename="demo.py"'
    return response

  从上可以发现,文件打开后作为参数传入FileResponse,随后指定传输头即可,但是很明显用这个来传输数据库就不太方便了,所以这边推介用StreamingHttpResponse的方式来传输。
  这里就用PyMysql来取得数据,然后指定为csv的格式返回,具体代码如下:

# 通过pymysql取得数据
import pymysql
field_types = {
        1: 'tinyint',
        2: 'smallint',
        3: 'int'}  #用于后面的字段名匹配,这里省略了大多数
conn = pymysql.connect(host='127.0.0.1',port=3306,database='demo',user='root',password='root')
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
cursor.execute(sql)
#获取所有数据
data = cursor.fetchall()
cols = {}
#获取所有字段
for i,row in enumerate(self.cursor.description):
	if row[0] in cols:
	    cols[str(i)+row[0]] = field_types.get(row[1], str(row[1]))  #这里的field_type是类型和数字的匹配
	cols[row[0]] = field_types.get(row[1], str(row[1]))
cursor.close()
conn.close()

#通过StreamingHttpResponse指定返回格式为csv
response = StreamingHttpResponse(get_result_fromat(data, cols))
response['Content-Type'] = 'application/octet-stream'
response['Content-Disposition'] = 'attachment;filename="{0}"'.format(out_file_name)
return response

#循环所有数据,然后加到字段上返回,注意的是要用迭代器来控制
def get_result_fromat(data, cols):
	tmp_str = ""
	# 返回文件的每一列列名
    for col in cols:
        tmp_str += '"%s",' % (col)
    yield tmp_str.strip(",") + "\n"
    for row in data:
        tmp_str = ""
        for col in cols:
            tmp_str += '"%s",' % (str(row[col]))
        yield tmp_str.strip(',') + "\n"

  整个代码如上,大致分为三部分:从mysql取数据,格式化成我们想要的格式:excel、csv、txt等等,这边指定的是csv,如果对其他格式也有兴趣的可以留言,最后就是用StreamingHttpResponse指定返回的格式返回。

实现百万级数据量下载

  上面的代码下载可以支持几万行甚至十几万行的数据,但是如果超过20万行以上的数据,那就比较困难了,我这边的剩余内存大概是1G的样子,当超过15万行数据(大概)的时候,就报memoryerror了,问题就是因为fetchall,虽然我们StreamingHttpResponse是一条条的返回,但是我们的数据时一次性批量的取得!
  如何解决?以下是我的解决方法和思路:

  • 用fetchone来代替fetchall,迭代生成fetchone
  • 发现还是memoryerror,因为execute是一次性执行,后来发现可以用流式游标来代替原来的普通游标,即SSDictCursor代替DictCursor

  于是整个代码需要修改的地方如下:

cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) ===>
cursor = conn.cursor(cursor=pymysql.cursors.SSDictCursor)
data = cursor.fetchall()   ===>
row = cursor.fetchone()
def get_result_fromat(data, cols):
	tmp_str = ""
	# 返回文件的每一列列名
    for col in cols:
        tmp_str += '"%s",' % (col)
    yield tmp_str.strip(",") + "\n"
    for row in data:
        tmp_str = ""
        for col in cols:
            tmp_str += '"%s",' % (str(row[col]))
        yield tmp_str.strip(',') + "\n"  
        
        =====>
        
def get_result_fromat(data, cols):
	tmp_str = ""
    for col in cols:
        tmp_str += '"%s",' % (col)
    yield tmp_str.strip(",") + "\n"
    while True:
        tmp_str = ""
        for col in cols:
            tmp_str += '"%s",' % (str(row[col]))
        yield tmp_str.strip(',') + "\n"
        row = db.cursor.fetchone()
        if row is None:
            break

  可以看到就是通过while True来实现不断地取数据下载,有效避免一次性从MySQL取出内存不足报错,又或者取得过久导致nginx超时!

总结

  关于下载就分享到这了,还是比较简单的,谢谢观看~

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
可以回答这个问题。以下是一个简单的用Python Django写的后端注册登录程序,使用MySQL储存数据的代码实现: 1. 安装DjangoMySQL驱动程序 ``` pip install django pip install mysqlclient ``` 2. 创建Django项目和应用 ``` django-admin startproject myproject cd myproject python manage.py startapp myapp ``` 3. 配置数据库 在myproject/settings.py文件中,将DATABASES设置为: ``` DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'mydatabase', 'USER': 'mydatabaseuser', 'PASSWORD': 'mypassword', 'HOST': 'localhost', 'PORT': '3306', } } ``` 其中,mydatabase是你的数据库名称,mydatabaseuser是你的数据库用户名,mypassword是你的数据库密码,localhost是你的数据库主机名,3306是你的数据库端口号。 4. 创建用户模型 在myapp/models.py文件中,创建一个用户模型: ``` from django.db import models class User(models.Model): username = models.CharField(max_length=50) password = models.CharField(max_length=50) ``` 5. 创建注册和登录视图 在myapp/views.py文件中,创建一个注册视图和一个登录视图: ``` from django.shortcuts import render, redirect from .models import User def register(request): if request.method == 'POST': username = request.POST['username'] password = request.POST['password'] user = User.objects.create(username=username, password=password) return redirect('login') else: return render(request, 'register.html') def login(request): if request.method == 'POST': username = request.POST['username'] password = request.POST['password'] user = User.objects.filter(username=username, password=password).first() if user: return redirect('home') else: return render(request, 'login.html', {'error': '用户名或密码错误'}) else: return render(request, 'login.html') ``` 6. 创建注册和登录模板 在myapp/templates目录下,创建一个register.html模板和一个login.html模板: register.html: ``` <!DOCTYPE html> <html> <head> <title>注册</title> </head> <body> <h1>注册</h1> <form method="post"> {% csrf_token %} <label>用户名:</label> <input type="text" name="username"><br> <label>密码:</label> <input type="password" name="password"><br> <input type="submit" value="注册"> </form> </body> </html> ``` login.html: ``` <!DOCTYPE html> <html> <head> <title>登录</title> </head> <body> <h1>登录</h1> {% if error %} <p>{{ error }}</p> {% endif %} <form method="post"> {% csrf_token %} <label>用户名:</label> <input type="text" name="username"><br> <label>密码:</label> <input type="password" name="password"><br> <input type="submit" value="登录"> </form> </body> </html> ``` 7. 创建主页视图和模板 在myapp/views.py文件中,创建一个主页视图: ``` from django.shortcuts import render def home(request): return render(request, 'home.html') ``` 在myapp/templates目录下,创建一个home.html模板: ``` <!DOCTYPE html> <html> <head> <title>主页</title> </head> <body> <h1>欢迎来到主页</h1> </body> </html> ``` 8. 配置URL路由 在myproject/urls.py文件中,将URL路由设置为: ``` from django.contrib import admin from django.urls import path from myapp.views import register, login, home urlpatterns = [ path('admin/', admin.site.urls), path('register/', register, name='register'), path('login/', login, name='login'), path('home/', home, name='home'), ] ``` 9. 运行Django项目 在命令行中,进入myproject目录,运行以下命令: ``` python manage.py runserver ``` 然后在浏览器中访问http://127.0.0.1:8000/register,就可以注册用户了。注册成功后,可以访问http://127.0.0.1:8000/login进行登录,登录成功后,就可以访问http://127.0.0.1:8000/home进入主页了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值