在orm中有一个经典的问题,那就是N+1问题,比如hibernate就有这个问题,这一般都是不可避免的。

【N+1问题是怎么出现的】

N+1一般出现在一对多查询中,下面以Group和User为例,Group和User是一对多的关系。

在sql中如果我们要查询user表中的字段,并需要让每个user都有group表中的信息,也就是多对一查询,

我们可以用如下sql:

select u.*,g.* from user as u left join group as g on u.group_id = g.id;

这样查询出来的user表是附带了group信息的,也就是比如我要查询一个用户,包括他的所属组的信息都可以一条sql查询出来。

而如果反过来呢?我要用查询group,还需要保证group中有user的信息该怎么办呢?

在sql中我们确实可以用right join来解决:

select g.*,u.* from group as g right join user as u on g.id=u.group_id;

这样查询出来的结果表是group的字段在表中是重复的,只有后面拼接的user信息是不一样的。在sql中这样做确实能满足需求,但是在orm中却不能做到。

在orm中,需要对象和数据库对应起来,所以上面的关系在pojo类中大概是这个样子的:

01 public class Group{
02   private List<User> users;
03  
04   //set
05   //get
06 }
07  
08 public class User{
09    private Group group;
10     
11    //set
12    //get
13 }

如果是开头说的多对一,则问题好解决,咱们orm生成查询语句的时候

select t0.id,t0.name,t0.age,t1.id,t1.group_name from user as t0 left join group as t1......

类似这样,orm会为每个字段和表名生成别名,这样在进行结果包装的时候只需要t0的字段set到user中,将t1的字段set到group中,最后将group对象set到user中,此时就完成包装了,这样查询出的user对象就可以用user.getGroup().getGroupName()来取值了。

如果是一对多,如果使用right join则group信息是重复的,没办法组装一个Group对象以及一个List<User>对象

从而也没办法把List<User>  set到Group对象中,也就没法儿组装结果集了。

所以orm一对多查询只能是先将主表Group查询出来,然后将每个group对应的user对象查询出来,伪代码如下:

1、select * from group;--->组装成List<Group>结合

2、for(Group group:groups){

select * from user where group_id = ?(group.getId()) --->组装成List<User> users;

group.setUsers(users)

}

这样就完成一对多的结果组装了。

可以发现如果我们查询出的group有100行数据,那么我们执行的sql语句是1+100条,100条就是循环中执行的。

这样就出现了N+1问题,严重影响了性能,要知道不断得去数据库提交sql请求是很耗性能的,N+1问题并不是只有hibernate有,而是所有orm都会遇到这个问题,只是各自有各自的解决办法提高性能,然后要从根本上解决这个问题是不可能的。

所以使用了orm的情况下要尽量少使用一对多,如果使用的多对一查询,则需要使用左外连接查询,比如hibernate中有fetch="join"可以设置,默认是fetch="select",为什么不是默认前者呢?这是因为hibernate还有懒加载机制,如果fetch="join"的话就不是懒加载了,不管怎样都会即时加载。

hibernate没怎么用过,但原理是这样的。

【怎么解决N+1问题】

上面说了,N+1问题是orm无法避免的问题,所以是无法根治的,只能优化,提高性能。

拿hibernate来说,我们可以关闭一对多的级联抓取,也就是每次都只把Group查询出来,然后循环List<Group>

在使用hibernate的懒加载去查询每个Group对象对应的List<User>属性,这样当没有用到某个group对象的getUsers()方法时是不会去执行查询的。

再者就是使用二级缓存,虽然第一次查询还是N+1,但是以后查询就会变得很快了,因为结果集是直接从缓存中去取的。

【我们的解决办法】

公司正在做自己的orm,我们也有一种解决方案。

上面不是说了吗,查询出group对象的时候需要遍历group,也就一对多在查询出一的一边的时候需要遍历一的结果集去查询多的一遍,

在查询多的一边的时候生成的sql语句是:

1 for(int i=0;i<groups.size();i++){
2   select * from user where group_id = groups.get(i).getId();//伪代码
3 }

如果需要循环一百次,我们是不是可以想办法让它只需要循环十次呢?也就是只提交11条sql查询

当然。

1 select * from user where group_id = 1 or group_id = 2 or group_id=3.....or group_id = 10;//伪代码

这样只需要对groups进行10次循环就可以查询出所有结果,而且在数据库中or是可以使用索引的,所以性能肯定会高,只要不or多了就行。

一直没有完全理解N+1,今天有机会学习了下,欢迎前辈继续深入赐教。

ORM中的N+1问题的更多相关文章

  1. C#基础系列:实现自己的ORM(反射以及Attribute在ORM中的应用)

    反射以及Attribute在ORM中的应用 一. 反射什么是反射?简单点吧,反射就是在运行时动态获取对象信息的方法,比如运行时知道对象有哪些属性,方法,委托等等等等.反射有什么用呢?反射不但让你在运行 ...

  2. Django ORM 中的批量操作

    Django ORM 中的批量操作 在Hibenate中,通过批量提交SQL操作,部分地实现了数据库的批量操作.但在Django的ORM中的批量操作却要完美得多,真是一个惊喜. 数据模型定义 首先,定 ...

  3. 重构 ORM 中的 Sql 生成

    Rafy 领域实体框架设计 - 重构 ORM 中的 Sql 生成   前言 Rafy 领域实体框架作为一个使用领域驱动设计作为指导思想的开发框架,必然要处理领域实体到数据库表之间的映射,即包含了 OR ...

  4. bbs项目学习到的知识点(orm中的extra)

    注册 form组件给input 的标签 添加样式类  参见这篇博客(点击) 上传图像 1.解决 一点击图像就会直接打开上传文件的按钮 #这儿利用了 label标签和input的特殊的联动功能 < ...

  5. Django ORM中常用字段和参数

    一些说明: 表myapp_person的名称是自动生成的,如果你要自定义表名,需要在model的Meta类中指定 db_table 参数,强烈建议使用小写表名,特别是使用MySQL作为后端数据库时. ...

  6. laravel orm 中的一对多关系 hasMany

    个人对于laravel orm 中对于一对多关系的理解 文章表 article,文章自然可以评论,表 comment 记录文章的评论,文章和评论的关系就是一对多,一篇文章可以有多个评论. 在 comm ...

  7. {Django基础六之ORM中的锁和事务}一 锁 二 事务

    Django基础六之ORM中的锁和事务 本节目录 一 锁 二 事务 一 锁 行级锁 select_for_update(nowait=False, skip_locked=False) #注意必须用在 ...

  8. 优化Django ORM中的性能问题(含prefetch_related 和 select_related)

    Django是个好工具,使用的很广泛. 在应用比较小的时候,会觉得它很快,但是随着应用复杂和壮大,就显得没那么高效了.当你了解所用的Web框架一些内部机制之后,才能写成比较高效的代码. 怎么查问题 W ...

  9. ORM中的一对一和多对多

    ORM中的一对一和多对多 Django ORM  ORM 一对一 什么时候用一对一? 当 一张表的某一些字段查询的比较频繁,另外一些字段查询的不是特别频繁 把不怎么常用的字段 单独拿出来做成一张表 然 ...

随机推荐

  1. win7 redis

    <?php /* windows下php安装redis扩展 php_redis下载地址:https://pecl.php.net/package/redis 点击redis安装版本后面的 DLL ...

  2. 算法(6)3Sum Closest

    kSum问题是一类问题,基本的方法是两个循环,其他一个查找,但是今天碰到了一个超级棘手的问题,是3Sum问题的一个变型 问题:给定一个数组,给定一个整数k,要求找出在数组中找到3个整数,使得这三个整数 ...

  3. Struts2监听Action结果的监听器

    作者:禅楼望月 在前面我们学到了在特定的Action中配置结果监听器,在Action完成控制处理之后,struts2转入实际的物理视图之前被回调.但是这种方式的缺点是,结果的监听器不能被复用.根据设计 ...

  4. clique 解题报告

    clique 题目描述 数轴上有 \(n\) 个点,第 \(i\) 个点的坐标为 \(x_i\),权值为 \(w_i\).两个点 \(i\),\(j\) 之间存在一条边当且仅当 \(abs(x_i-x ...

  5. codeforces 1060 B

    https://codeforces.com/contest/1060/problem/B 题意:给你一个数C ,你要找到两个数A.B,使得A+B=C并且A的每个位的数的和最大,求最大的和是多少 题解 ...

  6. 如何把SSL公钥和私钥转化为PFX格式

    1.登陆   https://myssl.com/cert_convert.html 2.原格式选择为 “PEM”,目标格式选择为 “PKCS12” 3.上传cer到 ”证书文件“,上传key到 ”私 ...

  7. ubuntu12.04 Qt WebKit编译

    转载自:http://my.oschina.net/u/257674/blog/167050 官方文档: http://trac.webkit.org/wiki/BuildingQtOnLinux#D ...

  8. CommonJs/ES6/AMD模块的用法以及区别

    github地址: 一直以来对CommonJs/AMD/CMD/ES6的文件模块加载一直懵懵懂懂.甚至有时会将CommonJs的exports和ES6的export.default搞混.趁着学习web ...

  9. Android4.4中WebView无法显示图片解决方案

    在Android4.4之前我们在使用WebView时为了提高加载速度我设置了(myWebView.getSettings().setBlockNetworkImage(true);//图片加载放在最后 ...

  10. js页面 读身份证

    硬件是 神思读卡器, 需要安装这个东西的驱动. 在web页面读取的话, 下载cab的包 进行注册, <OBJECT classid="clsid:F1317711-6BDE-4658- ...