rails基础-解决 n+1

N+1问题是新手常犯的一个问题,这里记录如何解决rails项目中的N+1问题

rails 支持的关联

ActiveRecord 支持6种关联

  • belongs_to
  • has_one
  • has_many
  • has_many_and_belongs_to
  • has_one :thought
  • has_many :thought

以为为model作为示例:

# rails中使用声明的形式来为模型添加功能,当声明了一种关联关系后,rails会维护这两个model的主键-外键
class Book < ApplicationRecord
  belongs_to :author
end

class Credit < ApplicationRecord
  belongs_to :author
end

class Author < ApplicationRecord
  has_many :books
  has_one  :credit
end

rails 支持的加载关联数据方法

preload

preload 会根据关联关系生成附加的SQL语句来加载关联关系

class User < AppilicationRecord

end

class Credit < AppilicationRecord
    belongs_to :user
end

credits = Credit.perload(:user)
# => SELECT `credits`.* FROM `credits`
     SELECT `users`.* FROM `users` WHERE `users`.`id` IN [# 这里是credit中user_id 的集合]

可以看见,preload 在查找了所有的credit记录后,又生成一条sql去加载credit关联的user记录,在根据 credits.first.user.association 时不产生新的sql语句

注意这种情况只是预加载了关联的对象,但是并没有加载关联关系(两条单独的sql),所以想根据user的属性去查找credit是行不通的

credits.where(user: {mobile_number: 173xxxx5384})
# =>
ActiveRecord::StatementInvalid: Mysql2::Error: Unknown column 'user.mobile_number' in 'where clause': SELECT `credits`.* FROM `credits` WHERE `user`.`mobile_number` = '173xxxx5384'

eager_load

left outer joins,从左表(table1)返回所有的行,即使右表(table2)中没有匹配。如果右表中没有匹配,则结果为 NULL
eager_load 会生成一条sql,并加载了所有的关联数据

Credit.eager_load(:user)
# =>
SELECT  "credits"."id" AS t0_r0, "credits"."user_id" AS t0_r1, "users"."id" AS t1_r0, "users"."name" AS t1_r1, "users"."email" AS t1_r2, "users"."nickname" AS t1_r3, "users"."introduction" AS t1_r4, "users"."password_digest" AS t1_r5, "users"."credit_id" AS t1_r6, "users"."created_at" AS t1_r7, "users"."updated_at" AS t1_r8, "users"."auth_token" AS t1_r9 FROM "credits" LEFT OUTER JOIN "users" ON "users"."id" = "credits"."user_id" LIMIT 1

eager_load 会根据关联关系生成一条SQL语句,加载关联对象也加载了关联关系,但存在一个问题就是随着关联对象的增加,SQL语句会愈加的复杂,影响SQL效率

Joins

rails 中的joins都是inner joins,取得是交集
joins 的接受者可以是model也是ActiveRecord::Relation实例

  1. belongs_to
  Book.joins(:author)
  # SELECT `books`.* FROM `books` INNER JOIN `authors` ON `authors`.`id` = `books`.`author_id`
  1. has_many
  Author.joins(:books)
  # sql:  SELECT `authors`.* FROM `authors` INNER JOIN `books` ON `books`.`author_id` = `authors`.`id`

可以使用joins关联多个对象

  # 以下两个关联功能一致
  Author.joins(:books, :credit)
  Author.joins(:books).joins(:credit)
  
  #sql
   SELECT `authors`.* FROM `authors` 
   INNER JOIN `books` ON `books`.`author_id` = `authors`.`id` 
   INNER JOIN `credits` ON `credits`.`author_id` = `authors`.`id`

使用joins这样链式关联多个对象会产生重复记录,可以用uniq 去除

includes

includes 单独使用时(不加条件)和preload 是一样的,根据关联关系单独生成SQL

User.includes(:credit)
# =>
SELECT `users`.* FROM `users`
SELECT `credits`.* FROM `credits` WHERE `credits`.`user_id` IN [# 这里是user的id数组]

includes 加条件(where)时,会自动转为一条sql

Credit.includes(:user).where(user_id: 1)
# => 
SELECT  "credits".* FROM "credits" WHERE "credits"."user_id" = ? LIMIT ?

在rails中有个灵活的用法 includes + reference, 功能类似于eager_load

class Credit < ApplicationRecord
  belongs_to :user
end
class User < ApplicationRecord
end

Credit.includes(:user).references(:user)

SELECT  "credits"."id" AS t0_r0, "credits"."user_id" AS t0_r1, "users"."id" AS t1_r0, "users"."name" AS t1_r1, "users"."email" AS t1_r2, "users"."nickname" AS t1_r3, "users"."introduction" AS t1_r4, "users"."password_digest" AS t1_r5, "users"."credit_id" AS t1_r6, "users"."created_at" AS t1_r7, "users"."updated_at" AS t1_r8, "users"."auth_token" AS t1_r9 FROM "credits" LEFT OUTER JOIN "users" ON "users"."id" = "credits"."user_id" LIMIT ?

总结

  1. preload 和includes 都可以预加载,includes能分段查询就分段查询, 不能分段查询就自动转为一条sql查询(where条件)
  2. preload 分段查询,不会加载关联关系
  3. eager_load 和 includes + references 是一样的,也可以预加载
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351

推荐阅读更多精彩内容