the5fire的技术博客

关注python、vim、linux、web开发和互联网--life is short, we need python.


[科普文]什么是ORM中的N+1

作者:the5fire | 标签:       | 发布:2017-06-09 2:52 p.m. | 阅读量: 352, 315

ORM能够让事情变得简单,也会让有些事情变得复杂。有人说,这不就是一个SQL语句的事嘛,干嘛在ORM里面就这么复杂。

上篇文章我们讲了什么是ORM(对象关系映射),不了解的可以看看上一篇文章。

这篇我们来解释什么是N+1的问题,在所有的ORM中,这都会是一个问题,新手很容易踩到坑。进而导致系统变慢,然后拖垮整个系统。

还是拿代码来说事,上篇我们定义了一个User的模型,这次还继续沿用,然后增加一个Post(文章)的模型。User和Post是一对多的关系,也就是User是Post的外键。代码如下:

from django.db import models


class User(models.Model):
    name = models.CharField(max_length=255)


class Post(models.Model):
    owner = models.ForeignKey(User)   # by the5fire
    title = models.CharField(max_length=255)
    content = models.TextField()

假设我们有这样的代码,现在系统里面有十个用户,每个用户写了一篇文章,也就是十篇文章。

接下来我们有一个需求,展示一个文章列表页,列表页上展示的信息包括:文章标题,文章作者名称。就这两个字段,也不需要分页。

我们要查询出这样的数据要怎么做呢。在ORM的世界中,我们直观的做法是这样:

posts = Post.objects.all()  #  获取所有的文章数据,注意此时不会执行sql语句  by the5fire
result = []
for post in posts:   # 此时会执行select * from post的查询
    result.append({
        'title': post.title,
        'owner': post.owner.name,  # 此时会执行  select * from user where user_id = <post.user_id>
    })

写到这就明白了吧。每次循环都要查一下user表,也就是说,如果我第一次查询是10条记录,那么最终我需要执行的查询语句就是10 + 1 = 11条语句。如果我第一次查询出来的是N条记录,那么最终需要执行的sql语句就是N+1次。

这就是N+1的问题。

但是如果懂SQL的话,就知道,其实这就是一个简单的JOIN语句。一条语句就能查出所有的数据,搞什么N+1.

SELECT t1.title, t2.name from post as t1 INNER JOIN user as t2 ON t2.id = t1.owner_id;

想到这里,是不是觉得ORM有时候挺碍事的。

其实现在的ORM框架基本都提供了解决的方案,比如Django中,对这类问题就是通过select_related来解决。上面的代码直接改造为:

posts_with_user = Post.objects.all().select_related('user)

这样产生的语句就是上面的那个JOIN语句。当然ORM还提供了其他类似的方法,比如prefetch_related,又是用来处理其他的问题。

总的来说,ORM给我们提供了便利,但某种程度上也对我们造成了限制,或者说是约束好了。


----EOF-----

扫码关注,或者搜索微信公众号:码农悟凡


其他分类: