查询与优化
ActiveRecord 查询链。
查询与优化
这一课你将能做到
- 用
where/order/limit/joins写出安全的链式查询,杜绝 SQL 注入 - 定位并歼灭 N+1 查询,用
includes一把预加载关联 - 用
explain和日志分析慢查询,判断该不该加索引
为什么要学
Rails 把 SQL 包得太好,好到初学者常常「不知道自己发了多少条 SQL」。开发环境数据少,一切飞快;上线后数据量上来,页面突然要 5 秒——十有八九是 N+1 和缺索引。查询优化是 Rails 工程师从「能写」到「能扛流量」的分水岭。
对比本站:Spring Boot 用 JPA 时同样会踩 N+1(@OneToMany 懒加载),解法是 JOIN FETCH 或 EntityGraph——思路和 Rails 的 includes 完全一致。学会一边,另一边触类旁通。
先建立图像
N+1 问题一张图看懂:
@articles = Article.limit(10) # 1 条 SQL:取 10 篇文章
<% @articles.each do |article| %>
<%= article.comments.count %> # 每篇文章 1 条 SQL!
<% end %>
总共发出的 SQL:
SELECT * FROM articles LIMIT 10; ← 1 条
SELECT COUNT(*) FROM comments WHERE article_id=1; ← +1
SELECT COUNT(*) FROM comments WHERE article_id=2; ← +1
... 共 10 次循环 ← +10
合计 11 条 = 1 + N(N=10),数据越多越致命
修复后:
@articles = Article.includes(:comments).limit(10)
# 只发 2 条 SQL:一条取文章,一条 WHERE article_id IN (1..10) 取所有评论
跟着做
步骤 1:链式查询基础
bin/rails console
# 条件查询(占位符防注入)
Article.where(published: true)
Article.where("created_at > ?", 7.days.ago)
Article.where("title LIKE ?", "%#{keyword}%")
# 排序、限量、偏移(分页三件套)
Article.order(created_at: :desc).limit(10).offset(20)
# 只取需要的列
Article.select(:id, :title)
# 聚合
Article.where(published: true).count
Comment.group(:article_id).count # => {1=>5, 2=>3, ...}
链条可以任意组合,直到被遍历才真正执行 SQL(懒加载):
query = Article.where(published: true) # 此刻没查库
query = query.order(created_at: :desc) # 还没查
query.each { |a| puts a.title } # each 触发,一次 SELECT
步骤 2:用 scope 封装常用查询
# app/models/article.rb
class Article < ApplicationRecord
scope :published, -> { where(published: true) }
scope :recent, -> { order(created_at: :desc) }
scope :search, ->(keyword) { where("title LIKE ?", "%#{keyword}%") }
end
控制器里读起来像自然语言:
@articles = Article.published.recent.search(params[:q]).limit(20)