自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

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

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

背景

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

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

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

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

N+1 Selects問題

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

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

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

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

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

  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)在處理每個州時都得查詢一下它所屬的國家。注意,這里只能不停的檢索同一個州,因為這些州都是同一個國家的。

2N+1 queries, not good 

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

#p#

select_related

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

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

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

  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的,能省去用來找國家的二級查詢。然而,這種解決有一個潛在的缺點,即反復的返回同一個國家對象,從而不得不一次又一次的將這一行傳給ORM代碼,生成大量重復的對象。等下我們還會再說說這個。

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

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

prefetch_related

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

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

下面是這種方式生成的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扔掉是個大進步。3 * 20ms總是會比(2 * 50 + 1) * 20ms  小,甚至比用select_related時的 (50 + 1) * 20ms也小。 

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

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

2 queries, pretty good

 能支持多深的關系?

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

  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用在原生查詢

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

責任編輯:林師授 來源: OSCHINA編譯
相關推薦

2011-04-21 09:59:48

WEBjavascript

2011-04-21 10:47:29

Webjavascript

2009-01-15 13:52:16

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

2013-09-10 16:16:19

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

2011-06-14 09:27:43

高性能WEB開發(fā)

2015-03-13 19:34:41

2019-07-23 11:41:45

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

2011-03-16 17:55:43

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

2011-04-07 13:53:25

Web工具

2011-04-18 10:16:30

WEB高性能

2019-06-26 07:25:47

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

2018-06-01 14:00:00

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

2023-11-14 08:24:59

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

2010-10-28 15:15:08

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

2011-04-19 11:06:03

JavaScriptweb

2011-10-18 13:58:32

高性能web

2022-02-21 10:14:15

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

2018-10-10 14:27:34

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

2011-04-27 10:57:29

高性能web開發(fā)

2011-04-07 13:39:24

WebHTTP
點贊
收藏

51CTO技術棧公眾號