如何有效的遍历django的QuerySet

如何有效的遍历django的QuerySet,第1张

最近做了一个小的需求,在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表进行扩充,或者建立一个字表,存放详细信息,而不是将所有信息存放入一个字段中,避免在查询时的二次解析。

不评价你的解决方案。你的模板比我还熟悉。 只是我感觉,你可能走了弯路。

模块只是用来处理一些简单的循环的。并不足以做复杂的算法。那是python最擅长的。

所以你在python里加工一下。形成似乎列表的结构。然后象“打印机”一样,直接在模板里顺序打出来就可以了。

所谓MVC,并不是说所有的视图都是让模板完成,所有的数据逻辑都是MODEL来完成。实际上现实工程里,比MVC的逻辑要复杂,更多层。 没有必要将这个通用化复杂化。 很多时候,ALL-IN-ONE。

所有的东西都有VIEWER里简单完成,这样的代码可读性,可维护性更好。这就达到软件工程的目标了。


欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/sjk/10097263.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-05
下一篇 2023-05-05

发表评论

登录后才能评论

评论列表(0条)

保存