如何有效的遍歷django的QuerySet

  最近作了一個小的需求,在django模型中經過前臺頁面的表單的提交(post),後臺對post的參數進行解析,經過models模型查詢MySQL,將數據結構進行加工,返回到前臺頁面進行展現。因爲對django中QuerySet特性的不熟悉,因此測試過程當中發現了不少問題。html

  開始的階段沒有遇到什麼問題,咱們舉例,在models有一張員工表employee,對應的表結構中,postion列表示員工職位,前臺post過來的參數賦給position,加上入職時間、離職時間,查詢操做經過models.filter(position=params)完成,獲取的員工信息內容由QuerySet和當前展現頁與每頁展現的記錄數進行簡單的計算,返回給前臺頁面進行渲染展現。編碼以下:mongodb

 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中的數據,而且對每條數據進行了解析,這並非高效的作法。django

  在網上查閱了相關資料,瞭解到:json

1 Django的queryset是惰性的,使用filter語句進行查詢,實際上並無運行任何的要真正從數據庫得到數據數據結構

2 只要你查詢的時候才真正的操做數據庫。會致使執行查詢的操做有:對QuerySet進行遍歷queryset,切片,序列化,對 QuerySet 應用 list()、len()方法,還有if語句app

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。post

  這裏咱們知道不能一次性對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表進行擴充,或者創建一個字表,存放詳細信息,而不是將全部信息存放入一個字段中,避免在查詢時的二次解析。


 

  參考:

  http://www.oschina.net/translate/django-querysets

  http://stackoverflow.com/questions/4222176/why-is-iterating-through-a-large-django-queryset-consuming-massive-amounts-of-me

相關文章
相關標籤/搜索