日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

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

新聞中心

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

背景

10年的永安網(wǎng)站建設(shè)經(jīng)驗(yàn),針對(duì)設(shè)計(jì)、前端、開發(fā)、售后、文案、推廣等六對(duì)一服務(wù),響應(yīng)快,48小時(shí)及時(shí)工作處理。成都全網(wǎng)營銷的優(yōu)勢是能夠根據(jù)用戶設(shè)備顯示端的尺寸不同,自動(dòng)調(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)站推廣”以來,每個(gè)客戶項(xiàng)目都認(rèn)真落實(shí)執(zhí)行。

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

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

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

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

N+1 Selects問題

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

 
 
 
 
  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',) 

上面定義了州跟市,一個(gè)州有0或多個(gè)市,這個(gè)例子程序用來打印一個(gè)州跟市的內(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 

要完成這個(gè)功能的代碼如下:

 
 
 
 
  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就會(huì)看到有一個(gè)用于列出全部的州查詢,然后對(duì)應(yīng)每個(gè)州有一個(gè)查詢,用于列出這個(gè)州下面的市。如果只有3個(gè)州,這不是很多,但如果是50個(gè),“+1”部分還是一個(gè)查詢,為了得到全部對(duì)應(yīng)的市,“N"則變成了50。

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

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

 
 
 
 
  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)在處理每個(gè)州時(shí)都得查詢一下它所屬的國家。注意,這里只能不停的檢索同一個(gè)州,因?yàn)檫@些州都是同一個(gè)國家的。

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

#p#

select_related

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

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

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

 
 
 
 
  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. ... 

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

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

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

prefetch_related

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

相反地, prefetch_related 的功能是收集關(guān)聯(lián)對(duì)象的全部id值,一次性批量獲取到它們,然后透明的附到相應(yīng)的對(duì)象。這種方式最好的一個(gè)地方是能用在一對(duì)多關(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扔掉是個(gè)大進(jìn)步。3 * 20ms總是會(huì)比(2 * 50 + 1) * 20ms  小,甚至比用select_related時(shí)的 (50 + 1) * 20ms也小。

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

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

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

要跨多個(gè)級(jí)別時(shí)怎么辦?select_related 和prefetch_related都可以通過雙下劃線遍歷關(guān)系對(duì)象。用這個(gè)功能時(shí),中間對(duì)象也會(huì)包括在內(nèi)。這很有用,但在更復(fù)雜的對(duì)象模型中有點(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,挺可惜的。但有一個(gè)變通的方法,即可以直接用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


分享標(biāo)題:高性能Web開發(fā):減少數(shù)據(jù)庫往返
文章網(wǎng)址:http://m.5511xx.com/article/cddphhh.html