简述n+1问题
2018-11-13 本文已影响0人
biubiudog
在Rails ActiveRecord中,常会嵌套遍历很多orm模型。
如:常用的一对多(用户和文章)模型中
class User
has_many :articles
end
class Article
belongs_to :user
end
如果有十个用户,那么查询每个用户对应的文章需要查询11次。
User.all.map{|user| user.articles}
select * from users
select * from articles where user_id = 1
...
select * from articles where user_id = 10
为了解决这个问题,一般的解决方案是:
1. 预加载
使用includes
, preload
, eager_load
User.includes(:articles).map{|user| user.acticles }
select * from users
select * from articles where user_id in (1..10)
只需要2条查询语句
includes 和preload 、eager_load的区别
Rails 提供了4种方式来加载关联表的数据:
- proload:使用一条附加的查询语句来加载关联数据,总是会生成两个sql语句。
User.preload(:articles)
是默认的User.includes(:articles)
加载方式。
加入查询条件时使用preload
会报错:
User.preload(:articles).where("articles.content = '123' ")
ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: articles.content: SELECT "users".* FROM "users" WHERE (articles.content = '123' ) LIMIT ?
includes加入查询条件不会报错
User.includes(:articles).where("articles.content = '123' ")
而preload的条件中加入users表的查询条件则没有问题:
User.preload(:articles).where("users.name = '123' ")
- eager_load
eager_load使用left outer join
进行单次查询,并加载所有关联数据。 - joins
joins使用inner join
来加载关联数据
2.使用Goldiloader
(参考 https://github.com/salsify/goldiloader)
安装 gem 'goldiloader'
默认情况下,所有关联在首次访问时都会自动加载。
你可以手动添加代码auto_include
,禁止自动预先加载:
User.auto_include(false)
在关联关系中使用proc来禁止自动预先加载。
class User < ActiveRecord::Base
has_many :articles, ->{ auto_include(false) }
end
fully_load
选项可用于强制ActiveRecord完全加载关联(并执行任何必要的自动切换加载)
class Blog <ActiveRecord :: Base
has_many :posts,fully_load: true
end