技术开发 频道

高性能Web开发:减少数据库往返实例

  2N+1 (不,这不算个事)

  在开始搞这个N+1问题之前,我要给每个州加一个属性,就是它所属的国家。这就引入另一个一对多关系。每个州只能属于一个国家。

Alaska (United States)
...

...

class Country(models.Model):
    name
= models.CharField(max_length=64)

class State(models.Model):
    name
= models.CharField(max_length=64)
    country
= models.ForeignKey(Country, related_name='states')

...

...
<li>{{ state.name }} ({{ state.country.name }})
...

  在django-debug-toolbar的SQL窗口里,能看到现在处理每个州时都得查询一下它所属的国家。注意,这里只能不停的检索同一个州,因为这些州都是同一个国家的。

  现在就有两个有趣的问题了,这是每个Django ORM方案都要面对的问题。

  select_related

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

  select_related通过在查询主要对象(这里是州state)和其他对象(这里是国家country)之间的SQL做手脚起作用。这样就可以省去为每个州都查一次国家。假如一次数据库往返(网络中转->运行->返回)用时20ms,加起来的话共有N*20ms。如果N足够大,这样做挺费时的。

  下面是新的检索州的查询:

SELECT ... FROM "locations_state"
    
INNER JOIN "locations_country" ON
        ("locations_state"."country_id"
= "locations_country"."id")
    
ORDER BY "locations_state"."name" ASC
...

  用上面这个查询取代旧的,能省去用来找国家的二级查询。然而,这种解决有一个潜在的缺点,即反复的返回同一个国家对象,从而不得不一次又一次的将这一行传给ORM代码,生成大量重复的对象。等下我们还会再说说这个。

  在继续往下之前得说一下,在Django ORM中,如果关系中的一方有多个对象,select_related是没用的。它能用来为一个州抓取对应的国家,但如果调用时添上“市”,它什么都不干。其他ORM框架(如Hibernate)没有这种限制,但要用类似功能时得特别小心,这类框架会在join的时候为二级对象重复生成一级对象,然后很快就会失控,ORM滞在那里不停的处理大量的数据或结果行。

  综上所述,select_related的最好是在取单独一个对象、同时又想抓取到关联的(一个)对象时用。这样只有一次数据库往返,不会引入大量重复数据,这在Django ORM只有一对一关系时都适用。

0
相关文章