成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

高性能Web開發(fā):減少數(shù)據(jù)庫(kù)往返

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

背景

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

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

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

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

N+1 Selects問(wèn)題

關(guān)于數(shù)據(jù)庫(kù)往返最常見又讓人吃驚的問(wèn)題是n+1 selects問(wèn)題。這個(gè)問(wèn)題最簡(jiǎn)單的形式包括一個(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è)例子程序用來(lái)打印一個(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. ...  
  2. <ul>  
  3. {% for state in states %}  
  4. <li>{{ state.name }}  
  5.     <ul>  
  6.         {% for city in state.cities.all %}  
  7.         <li>{{ city.name }}</li>  
  8.         {% endfor %}  
  9.     </ul>  
  10. </li>  
  11. {% endfor %}  
  12. </ul>  
  13. ... 

如果將上面的代碼跑起來(lái),生成相應(yīng)的HTML,通過(guò)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問(wèn)題之前,我要給每個(gè)加一個(gè)屬性,就是它所屬的國(guó)家。這就引入另一個(gè)一對(duì)多關(guān)系。每個(gè)州只能屬于一個(gè)國(guó)家。

  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. <li>{{ state.name }} ({{ state.country.name }})  
  3. ... 

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

2N+1 queries, not good 

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

#p#

select_related

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

select_related通過(guò)在查詢主要對(duì)象(這里是州state)和其他對(duì)象(這里是國(guó)家country)之間的SQL做手腳起作用。這樣就可以省去為每個(gè)州都查一次國(guó)家。假如一次數(shù)據(jù)庫(kù)往返(網(wǎng)絡(luò)中轉(zhuǎn)->運(yùn)行->返回)用時(shí)20ms,加起來(lái)的話共有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的,能省去用來(lái)找國(guó)家的二級(jí)查詢。然而,這種解決有一個(gè)潛在的缺點(diǎn),即反復(fù)的返回同一個(gè)國(guó)家對(duì)象,從而不得不一次又一次的將這一行傳給ORM代碼,生成大量重復(fù)的對(duì)象。等下我們還會(huì)再說(shuō)說(shuō)這個(gè)。

在繼續(xù)往下之前得說(shuō)一下,在Django ORM中,如果關(guān)系中的一方有多個(gè)對(duì)象,select_related是沒用的。它能用來(lái)為一個(gè)州抓取對(duì)應(yīng)的國(guó)家,但如果調(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ù)庫(kù)往返,不會(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ì)國(guó)家跟市都采用了prefetch。前面說(shuō)過(guò)這里的州都屬同一國(guó)家,用select_related獲得州記錄時(shí),這意味著要取到并處理這一國(guó)家記錄N次。相反,用prefetch_related只要取一次。而這樣會(huì)引入一次額外的數(shù)據(jù)庫(kù)往返,有沒有可能綜合兩種方式,你得在你的機(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() 

2 queries, pretty good

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

要跨多個(gè)級(jí)別時(shí)怎么辦?select_related 和prefetch_related都可以通過(guò)雙下劃線遍歷關(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('<sql-query-for-nearby-cities>'))  
  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

責(zé)任編輯:林師授 來(lái)源: OSCHINA編譯
相關(guān)推薦

2011-04-21 09:59:48

WEBjavascript

2011-04-21 10:47:29

Webjavascript

2009-01-15 13:52:16

數(shù)據(jù)庫(kù)管理開銷

2013-09-10 16:16:19

移動(dòng)網(wǎng)站性能優(yōu)化移動(dòng)web

2011-06-14 09:27:43

高性能WEB開發(fā)

2015-03-13 19:34:41

2019-07-23 11:41:45

數(shù)據(jù)庫(kù)SQLDocker

2011-04-18 10:16:30

WEB高性能

2011-04-07 13:53:25

Web工具

2011-03-16 17:55:43

數(shù)據(jù)庫(kù)管理開銷

2018-06-01 14:00:00

數(shù)據(jù)庫(kù)MySQL分庫(kù)分表

2019-06-26 07:25:47

NoSQL數(shù)據(jù)庫(kù)開發(fā)

2023-11-14 08:24:59

性能Scylla系統(tǒng)架構(gòu)

2011-04-19 11:06:03

JavaScriptweb

2011-10-18 13:58:32

高性能web

2010-10-28 15:15:08

oracle內(nèi)存參數(shù)

2022-02-21 10:14:15

數(shù)據(jù)中心電力

2018-10-10 14:27:34

數(shù)據(jù)庫(kù)連接池MySQL

2011-04-07 13:39:24

WebHTTP

2011-04-27 10:57:29

高性能web開發(fā)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 久久精品日产第一区二区三区 | 女人牲交视频一级毛片 | 日日操夜夜操天天操 | 黄色片av | 久久精品| 日韩一区二区av | 国产成人福利 | 午夜影院操 | 黄色免费网 | 在线免费观看视频你懂的 | 81精品国产乱码久久久久久 | 激情五月激情综合网 | 在线观看中文字幕亚洲 | 在线免费观看黄视频 | 91精品国产综合久久婷婷香蕉 | 日本三级电影在线观看视频 | 亚洲一区 | 九九伊人sl水蜜桃色推荐 | 日韩国产在线观看 | 美女爽到呻吟久久久久 | 97精品久久 | 黄色大片视频 | 老子午夜影院 | 欧美激情国产精品 | 国产精品久久久久久久午夜片 | 欧洲一级视频 | 91色在线视频 | 手机看黄av免费网址 | 亚洲成人观看 | 精品国产乱码久久久久久图片 | 精品国产一区二区国模嫣然 | 日韩国产中文字幕 | 日韩在线中文 | 国产精品久久久久aaaa | 国产欧美一区二区三区久久人妖 | 国产成人综合亚洲欧美94在线 | 国产区高清| 国产精品久久久久久久久久久新郎 | 精品久久久久久亚洲精品 | 亚洲成av| 翔田千里一区二区 |