在上面一篇文章里,介绍了安装Django的方法,这里说说连接数据库吧
这篇主要介绍踩的坑和解决办法
正常连接和初始化数据库的命令是
执行这个命令的时候,出现了如下报错
经过一段排查,是我配置数据库的时候,多嵌套了一层default,修改为如下即可
然后继续执行migrate时,会报如下错误
本机环境是mac电脑,按官方教程 *** 作的时候,发现安装mysqlclient的python包会依赖本机安装mysql或者mysql-client,但在装mysql和mysql-client的时候,发现一直报错。后面发现解决办法是,在settings.py文件里,加下如下代码,即改用pymsql连接即可。
或者在settings.py同目录的__init__.py里加如上代码也可以
然后再执行python manage.py migrate命令会发现表顺利创建。新增表,不影响原有库的其他表。
最近做了一个小的需求,在django模型中通过前台页面的表单的提交(post),后台对post的参数进行解析,通过models模型查询MySQL,将数据结构进行加工,返回到前台页面进行展示。由于对django中
QuerySet特性的不熟悉,所以测试过程中发现了很多问题。
开始的阶段没有遇到什么问题,我们举例,在models有一张员工表
employee,对应的表结构中,postion列表示员工职位,前台post过来的参数赋给position,加上入职时间、离职时间,查询 *** 作通过
models.filter(position=params)完成,获取的员工信息内容由QuerySet和当前展示页与每页展示的记录数进行简单的计
算,返回给前台页面进行渲染展示。编码如下:
1 def get_employees(position, start, end):
2 return employee.objects.filter(alert_time__lt=end,alert_time__gt=start).filter(position__in=position)
3
4
5 @login_required
6 def show(request):
7 if not validate(request):
8 return render_to_response('none.html',
9 context_instance=RequestContext(request, 'msg':'params error')
10 )
11
12 position = request.REQUEST.get('position')
13 time_range = request.REQUEST.get('time')
14 start, end = time_range[0], time_range[1]
15
16 num_per_page, page_num = get_num(request)
17 all_employees = get_employees(position, start, end)
18 # 根据当前页与每页展示的记录数,取到正确的记录
19 employees = employees_events[(page_num-1)*num_per_page:page_num*num_per_page]
20
21 return render_to_response('show_employees.html',
22 context_instance=RequestContext(
23 request,
24 'employees': employees,
25 'num_per_page': num_per_page,
26 'page_num':page_num,
27 'page_options' : [50, 100, 200]
28 )
29 )
运行之后可以正确的对所查询的员工信息进行展示,并且查询速度很快。
employee表中存放着不同职位的员工信息,不同类型的详细内容也不相同,假设employees有一列名为infomation,存储的是员工的详
细信息,infomation = {'age': 33, 'gender': 'male', 'nationality': 'German',
'degree': 'doctor', 'motto': 'just do
it'},现在的需求是要展示出分类更细的员工信息,前台页面除了post职位、入职离职时间外,还会对infomation中的内容进行筛选,这里以查
询中国籍的设计师为例,在之前的代码基础上,需要做一些修改。员工信息表employee存放于MySQL中,而MySQL为ORM数据库,它并未提供类
似mongodb一样更为强大的聚合函数,所以这里不能通过objects提供的方法进行filter,一次性将所需的数据获取出来,那么需要对type
进行过滤后的数据,进行二次遍历,通过information来确定当前记录是否需要返回展示,在展示过程中,需要根据num_per_page和
page_num计算出需要展示数据起始以及终止位置。
1 def get_employees(position, start, end):
2 return employee.objects.filter(alert_time__lt=end,alert_time__gt=start).filter(position__in=position)
3
4
5 def filter_with_nation(all_employees, nationality, num_per_page, page_num):
6 result = []
7
8 pos = (page_num-1)*num_per_page
9 cnt = 0
10 start = False
11 for employee in all_employees:
12 info = json.loads(employee.information)
13 if info.nationality != nationality:
14 continue
15
16 # 获取的数据可能并不是首页,所以需要先跳过前n-1页
17 if cnt == pos:
18 if start:
19 break
20 cnt = 0
21 pos = num_per_page
22 start = True
23
24 if start:
25 result.append(employee)
26
27 return employee
28
29
30 @login_required
31 def show(request):
32 if not validate(request):
33 return render_to_response('none.html',
34 context_instance=RequestContext(request, 'msg':'params error')
35 )
36
37 position = request.REQUEST.get('position')
38 time_range = request.REQUEST.get('time')
39 start, end = time_range[0], time_range[1]
40
41 num_per_page, page_num = get_num(request)
42 all_employees = get_employees(position, start, end)
43
44 nationality = request.REQUEST.get('nationality')
45
46 employees = filter_with_nation(all_employees, num_per_page, page_num)
47
48 return render_to_response('show_employees.html',
49 context_instance=RequestContext(
50 request,
51 'employees': employees,
52 'num_per_page': num_per_page,
53 'page_num':page_num,
54 'page_options' : [50, 100, 200]
55 )
56 )
当编码完成之后,在数据employee表数据很小的情况下测试并未发现问
题,而当数据量非常大,并且查询的数据很少时,代码运行非常耗时。我们设想,这是一家规模很大的跨国公司,同时人员的流动量也很大,所以employee
表的数据量很庞大,而这里一些来自于小国家的员工并不多,比如需要查询国籍为梵蒂冈的员工时,前台页面进入了无尽的等待状态。同时,监控进程的内存信息,
发现进程的内存一直在增长。毫无疑问,问题出现在filter_with_nation这个函数中,这里逐条遍历了employee中的数据,并且对每条
数据进行了解析,这并不是高效的做法。
在网上查阅了相关资料,了解到:
1 Django的queryset是惰性的,使用filter语句进行查询,实际上并没有运行任何的要真正从数据库获得数据
2 只要你查询的时候才真正的 *** 作数据库。会导致执行查询的 *** 作有:对QuerySet进行遍历queryset,切片,序列化,对 QuerySet 应用 list()、len()方法,还有if语句
3 当第一次进入循环并且对QuerySet进行遍历时,Django从数据库中获取数据,在它返回任何可遍历的数据之前,会在内存中为每一条数据创建实例,而这有可能会导致内存溢出。
上面的原来很好的解释了代码所造成的现象。那么如何进行优化是个问题,网上有
说到当QuerySet非常巨大时,为避免将它们一次装入内存,可以使用迭代器iterator()来处理,但对上面的代码进行修改,遍历时使用
employee.iterator(),而结果和之前一样,内存持续增长,前台页面等待,对此的解释是:using iterator()
will save you some memory by not storing the result of the cache
internally (though not necessarily on PostgreSQL!)but will still
retrieve the whole objects from the database。
这里我们知道不能一次性对QuerySet中所有的记录进行遍历,那么只能对
QuerySet进行切片,每次取一个chunk_size的大小,遍历这部分数据,然后进行累加,当达到需要的数目时,返回满足的对象列表,这里修改下
filter_with_nation函数:
1 def filter_with_nation(all_employees, nationality, num_per_page, page_num):
2 result = []
3
4 pos = (page_num-1)*num_per_page
5 cnt = 0
6 start_pos = 0
7 start = False
8 while True:
9 employees = all_employees[start_pos:start_pos+num_per_page]
10 start_pos += num_per_page
11
12 for employee in employees:
13 info = json.loads(employee.infomation)
14 if info.nationality != nationality:
15 continue
16
17 if cnt == pos:
18 if start:
19 break
20 cnt = 0
21 pos = num_per_page
22 start = True
23
24 if start:
25 result.append(opt)
26
27 cnt += 1
28
29 if cnt == num_per_page or not events:
30 break
31
32 return result
运行上述代码时,查询的速度更快,内存也没有明显的增长,得到效果不错的优
化。这篇文章初衷在于记录自己对django中queryset的理解和使用,而对于文中的例子,其实正常业务中,如果需要记录员工详细的信息,最好对
employee表进行扩充,或者建立一个字表,存放详细信息,而不是将所有信息存放入一个字段中,避免在查询时的二次解析。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)