日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
高性能Web開發(fā):減少數(shù)據(jù)庫往返

背景

10年的永安網(wǎng)站建設(shè)經(jīng)驗(yàn),針對設(shè)計(jì)、前端、開發(fā)、售后、文案、推廣等六對一服務(wù),響應(yīng)快,48小時及時工作處理。成都全網(wǎng)營銷的優(yōu)勢是能夠根據(jù)用戶設(shè)備顯示端的尺寸不同,自動調(diào)整永安建站的顯示方式,使網(wǎng)站能夠適用不同顯示終端,在瀏覽器中調(diào)整網(wǎng)站的寬度,無論在任何一種瀏覽器上瀏覽網(wǎng)站,都能展現(xiàn)優(yōu)雅布局與設(shè)計(jì),從而大程度地提升瀏覽體驗(yàn)。成都創(chuàng)新互聯(lián)公司從事“永安網(wǎng)站設(shè)計(jì)”,“永安網(wǎng)站推廣”以來,每個客戶項(xiàng)目都認(rèn)真落實(shí)執(zhí)行。

Web程序的后端主要有兩個東西:渲染(生成HTML,或數(shù)據(jù)序列化)和IO(數(shù)據(jù)庫操作,或內(nèi)部服務(wù)調(diào)用)。今天要講的是后面那個,關(guān)注一下如何減少數(shù)據(jù)庫往返這個問題。最快的查詢是不存在的,沒有最快,只有更快!

開始講之前我得提一下Schema的重要性,但不會在這花太多時間。單獨(dú)一個因素不會影響程序的整體響應(yīng)速度,有調(diào)數(shù)據(jù)的能力,比有一個好的數(shù)據(jù)(庫)Schema要強(qiáng)得多。這些東西以后會細(xì)講,但Schema問題常會限制你的選擇,所以現(xiàn)在提一下。

我也會提一下緩存。在理想情況下,我要討論的東西能有效減少返回不能緩存或緩存丟失的數(shù)據(jù)的時間,但跟通過優(yōu)化查詢減少數(shù)據(jù)庫往返次數(shù)一樣,避免將全部東西扔進(jìn)緩存里是個極大的進(jìn)步。

最后得提一下的是,文中我用的是Python(Django),但原理在其他語言或ORM框架里也適用。我以前搞過Java(Hibernate),不太順手,后來搞Perl(DBIX::Class)、Ruby(Rails)以及其他幾種東西去了。

N+1 Selects問題

關(guān)于數(shù)據(jù)庫往返最常見又讓人吃驚的問題是n+1 selects問題。這個問題最簡單的形式包括一個有子對象的實(shí)體,和一對多的關(guān)系。下面是一個小例子。

 
 
 
 
  1. from django.db import models  
  2.  
  3.  
  4. class State(models.Model):  
  5.     name = models.CharField(max_length=64)  
  6.     country = models.ForeignKey(Country, related_name='states')  
  7.  
  8.     class Meta:  
  9.         ordering = ('name',)  
  10.  
  11.  
  12. class City(models.Model):  
  13.     name = models.CharField(max_length=64)  
  14.     state = models.ForeignKey(State, related_name='cities')  
  15.  
  16.     class Meta:  
  17.         ordering = ('name',) 

上面定義了州跟市,一個州有0或多個市,這個例子程序用來打印一個州跟市的內(nèi)聯(lián)列表。

 
 
 
 
  1. Alaska  
  2.     Anchorage  
  3.     Fairbanks  
  4.     Willow  
  5. California  
  6.     Berkeley  
  7.     Monterey  
  8.     Palo Alto  
  9.     San Diego  
  10.     San Francisco  
  11.     Santa Cruz  
  12. Kentucky  
  13.     Albany  
  14.     Monticello  
  15.     Lexington  
  16.     Louisville  
  17.     Somerset  
  18.     Stamping Ground 

要完成這個功能的代碼如下:

 
 
 
 
  1. from django.shortcuts import render_to_response  
  2. from django.template.context import RequestContext  
  3. from locations.models import State  
  4.  
  5. def list_locations(request):  
  6.     data = {'states': State.objects.all()}  
  7.     return render_to_response('list_locations.html', data,  
  8.                               RequestContext(request)) 
 
 
 
 
  1. ...  
    •  
    • {% for state in states %}  
    • {{ state.name }}  
    •     
         
      •         {% for city in state.cities.all %}  
      •         
      • {{ city.name }}
      •  
      •         {% endfor %}  
      •     
       
    •  
    • {% endfor %}  
     
  2. ... 

如果將上面的代碼跑起來,生成相應(yīng)的HTML,通過django-debug-toolbar就會看到有一個用于列出全部的州查詢,然后對應(yīng)每個州有一個查詢,用于列出這個州下面的市。如果只有3個州,這不是很多,但如果是50個,“+1”部分還是一個查詢,為了得到全部對應(yīng)的市,“N"則變成了50。

2N+1 (不,這不算個事)

在開始搞這個N+1問題之前,我要給每個州加一個屬性,就是它所屬的國家。這就引入另一個一對多關(guān)系。每個州只能屬于一個國家。

 
 
 
 
  1. Alaska (United States)  
  2. ... 
 
 
 
 
  1. ...  
  2.  
  3. class Country(models.Model):  
  4.     name = models.CharField(max_length=64)  
  5.  
  6. class State(models.Model):  
  7.     name = models.CharField(max_length=64)  
  8.     country = models.ForeignKey(Country, related_name='states')  
  9.  
  10. ... 
 
 
 
 
  1. ...  
  2. {{ state.name }} ({{ state.country.name }})  
  3. ... 

在django-debug-toolbar的SQL窗口里,能看到現(xiàn)在處理每個州時都得查詢一下它所屬的國家。注意,這里只能不停的檢索同一個州,因?yàn)檫@些州都是同一個國家的。

現(xiàn)在就有兩個有趣的問題了,這是每個Django ORM方案都要面對的問題。

#p#

select_related

 
 
 
 
  1. states = State.objects.select_related('country').all() 

select_related通過在查詢主要對象(這里是州state)和其他對象(這里是國家country)之間的SQL做手腳起作用。這樣就可以省去為每個州都查一次國家。假如一次數(shù)據(jù)庫往返(網(wǎng)絡(luò)中轉(zhuǎn)->運(yùn)行->返回)用時20ms,加起來的話共有N*20ms。如果N足夠大,這樣做挺費(fèi)時的。

下面是新的檢索州的查詢:

 
 
 
 
  1. SELECT ... FROM "locations_state" 
  2.     INNER JOIN "locations_country" ON  
  3.         ("locations_state"."country_id" = "locations_country"."id")  
  4.     ORDER BY "locations_state"."name" ASC  
  5. ... 

用上面這個查詢?nèi)〈f的,能省去用來找國家的二級查詢。然而,這種解決有一個潛在的缺點(diǎn),即反復(fù)的返回同一個國家對象,從而不得不一次又一次的將這一行傳給ORM代碼,生成大量重復(fù)的對象。等下我們還會再說說這個。

在繼續(xù)往下之前得說一下,在Django ORM中,如果關(guān)系中的一方有多個對象,select_related是沒用的。它能用來為一個州抓取對應(yīng)的國家,但如果調(diào)用時添上“市”,它什么都不干。其他ORM框架(如Hibernate)沒有這種限制,但要用類似功能時得特別小心,這類框架會在join的時候?yàn)槎墝ο笾貜?fù)生成一級對象,然后很快就會失控,ORM滯在那里不停的處理大量的數(shù)據(jù)或結(jié)果行。

綜上所述,select_related的最好是在取單獨(dú)一個對象、同時又想抓取到關(guān)聯(lián)的(一個)對象時用。這樣只有一次數(shù)據(jù)庫往返,不會引入大量重復(fù)數(shù)據(jù),這在Django ORM只有一對一關(guān)系時都適用。

prefetch_related

 
 
 
 
  1. states = State.objects.prefetch_related('country', 'cities').all() 

相反地, prefetch_related 的功能是收集關(guān)聯(lián)對象的全部id值,一次性批量獲取到它們,然后透明的附到相應(yīng)的對象。這種方式最好的一個地方是能用在一對多關(guān)系中,比如本例中的州跟市。

下面是這種方式生成的SQL:

 
 
 
 
  1. SELECT ... FROM "locations_state" ORDER BY "locations_state"."name" ASC  
  2. SELECT ... FROM "locations_country" WHERE "locations_country"."id" IN (1)  
  3. SELECT ... FROM "locations_city" 
  4.     WHERE "locations_city"."state_id" IN (1, 2, 3)  
  5.     ORDER BY "locations_city"."name" ASC 

這樣2N+1就變成3了。把N扔掉是個大進(jìn)步。3 * 20ms總是會比(2 * 50 + 1) * 20ms  小,甚至比用select_related時的 (50 + 1) * 20ms也小。

上面這個例子對國家跟市都采用了prefetch。前面說過這里的州都屬同一國家,用select_related獲得州記錄時,這意味著要取到并處理這一國家記錄N次。相反,用prefetch_related只要取一次。而這樣會引入一次額外的數(shù)據(jù)庫往返,有沒有可能綜合兩種方式,你得在你的機(jī)器及數(shù)據(jù)上試試。然而,在本例中同時用select_related 和 prefetch_related可以將時間降到2 * 20ms,這可能會比分3次查詢要快,但也有很多潛在因素要考慮。

 
 
 
 
  1. states = State.objects.select_related('country') \  
  2.     .prefetch_related('cities').all() 

 能支持多深的關(guān)系?

要跨多個級別時怎么辦?select_related 和prefetch_related都可以通過雙下劃線遍歷關(guān)系對象。用這個功能時,中間對象也會包括在內(nèi)。這很有用,但在更復(fù)雜的對象模型中有點(diǎn)難用。

 
 
 
 
  1. # only works when there's a single object at each step  
  2. city = City.objects.select_related('state__country').all()[0]  
  3. # 1 query, no further db queries  
  4. print('{0} - {1} - {2}'.format(city.name, city.state.name,  
  5.                                city.state.country.name)  
  6.  
  7. # works for both single and multiple object relationships  
  8. countries = Country.objects.prefetch_related('states__cities')  
  9. # 3 queries, no further db queries  
  10. for country in countries:  
  11.     for state in country.states:  
  12.         for city in state.cities:  
  13.             print('{0} - {1} - {2}'.format(city.name, city.state.name,  
  14.                                            city.state.country.name) 

prefetch_related用在原生查詢

最后一點(diǎn)。上周的 efficiently querying for nearby things 一文中,為了實(shí)現(xiàn)查找最近的經(jīng)度/緯度點(diǎn),我寫了一條復(fù)雜的SQL。其實(shí)最好的方法是寫一條原生的sql查詢 。而原生查詢不支持prefetch_related,挺可惜的。但有一個變通的方法,即可以直接用Django實(shí)現(xiàn)prefetch_related功能的prefetch_related_objects。

 
 
 
 
  1. from django.db.models.query import prefetch_related_objects  
  2.  
  3. # prefetch_related_objects requires a list, it won't work on a QuerySet so  
  4. # we need to convert with list()  
  5. cities = list(City.objects.raw(''))  
  6. prefetch_related_objects(cities, ('state__country',))  
  7. # 3 queries, no further db queries  
  8. for city in cities:  
  9.     print('{0} - {1} - {2}'.format(city.name, city.state.name,  
  10.                                    city.state.country.name) 

這多牛呀!

英文原文:High Performance Web: Reducing Database Round Trips

譯文鏈接:http://www.oschina.net/translate/high-performance-web-reducing-database-round-trips


網(wǎng)頁名稱:高性能Web開發(fā):減少數(shù)據(jù)庫往返
標(biāo)題網(wǎng)址:http://www.dlmjj.cn/article/cddphhh.html