【IT168 技术】Web程序的后端主要有两个东西:渲染(生成HTML,或数据序列化)和IO(数据库操作,或内部服务调用)。今天要讲的是后面那个,关注一下如何减少数据库往返这个问题。最快的查询是不存在的,没有最快,只有更快!
开始讲之前我得提一下Schema的重要性,但不会在这花太多时间。单独一个因素不会影响程序的整体响应速度,有调数据的能力,比有一个好的数据(库)Schema要强得多。这些东西以后会细讲,但Schema问题常会限制你的选择,所以现在提一下。
我也会提一下缓存。在理想情况下,我要讨论的东西能有效减少返回不能缓存或缓存丢失的数据的时间,但跟通过优化查询减少数据库往返次数一样,避免将全部东西扔进缓存里是个极大的进步。
最后得提一下的是,文中我用的是Python(Django),但原理在其他语言或ORM框架里也适用。我以前搞过Java(Hibernate),不太顺手,后来搞Perl(DBIX::Class)、Ruby(Rails)以及其他几种东西去了。
N+1 Selects问题
关于数据库往返最常见又让人吃惊的问题是n+1 selects问题。这个问题最简单的形式包括一个有子对象的实体,和一对多的关系。下面是一个小例子。
from django.db import models
class State(models.Model):
name = models.CharField(max_length=64)
country = models.ForeignKey(Country, related_name='states')
class Meta:
ordering = ('name',)
class City(models.Model):
name = models.CharField(max_length=64)
state = models.ForeignKey(State, related_name='cities')
class Meta:
ordering = ('name',)
class State(models.Model):
name = models.CharField(max_length=64)
country = models.ForeignKey(Country, related_name='states')
class Meta:
ordering = ('name',)
class City(models.Model):
name = models.CharField(max_length=64)
state = models.ForeignKey(State, related_name='cities')
class Meta:
ordering = ('name',)
上面定义了州跟市,一个州有0或多个市,这个例子程序用来打印一个州跟市的内联列表。
Alaska
Anchorage
Fairbanks
Willow
California
Berkeley
Monterey
Palo Alto
San Diego
San Francisco
Santa Cruz
Kentucky
Albany
Monticello
Lexington
Louisville
Somerset
Stamping Ground
Anchorage
Fairbanks
Willow
California
Berkeley
Monterey
Palo Alto
San Diego
San Francisco
Santa Cruz
Kentucky
Albany
Monticello
Lexington
Louisville
Somerset
Stamping Ground
要完成这个功能的代码如下:
from django.shortcuts import render_to_response
from django.template.context import RequestContext
from locations.models import State
def list_locations(request):
data = {'states': State.objects.all()}
return render_to_response('list_locations.html', data,
RequestContext(request))
from django.template.context import RequestContext
from locations.models import State
def list_locations(request):
data = {'states': State.objects.all()}
return render_to_response('list_locations.html', data,
RequestContext(request))
如果将上面的代码跑起来,生成相应的HTML,通过django-debug-toolbar就会看到有一个用于列出全部的州查询,然后对应每个州有一个查询,用于列出这个州下面的市。如果只有3个州,这不是很多,但如果是50个,“+1”部分还是一个查询,为了得到全部对应的市,“N"则变成了50。
...
<ul>
{% for state in states %}
<li>{{ state.name }}
<ul>
{% for city in state.cities.all %}
<li>{{ city.name }}</li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
...
<ul>
{% for state in states %}
<li>{{ state.name }}
<ul>
{% for city in state.cities.all %}
<li>{{ city.name }}</li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
...