Ruby on Rails网站很慢;可能是SQL查询的原因吗?
Ruby on Rails网站很慢;可能是SQL查询的原因吗?
我一直在使用Ruby on Rails开发一个网页应用。我已经“完成”了网站,但是它运行非常慢,有时加载页面需要十秒左右的时间。我将这篇文章分成了三个部分:\n
- \n
- 概述
- 诊断
- 可能的想法
\n
\n
\n
\n概述\n作为一个非常粗略的概述,这个网站显示个人项目,每个项目都有自己的页面。每个项目(我在代码中称之为“帖子”)都存储在一个表中。我不确定将整个帖子存储在数据库中是否是一个坏主意,因为一些帖子在正文中有相当多的文本和图片。在posts
表中,每个条目都有以下属性:\n
# == Schema Information # # 表名: posts # # id :integer not null, primary key # title :string # body :text # description :text # slug :string # created_at :datetime not null # updated_at :datetime not null # image_file_name :string # image_content_type :string # image_file_size :integer # image_updated_at :datetime # thumbnail_file_name :string # thumbnail_content_type :string # thumbnail_file_size :integer # thumbnail_updated_at :datetime # published :boolean default("f") # published_at :datetime #
\n诊断\n我正在使用Heroku托管应用程序。我已经升级到Hobby级别的dynos,这样我就可以访问它们提供的一些指标,比如吞吐量、内存使用等等。我不认为Heroku是导致网站缓慢的原因,而是我的代码。为了调试,我添加了一个第三方插件Scout,用于跟踪应用程序中的瓶颈。\nScout提供了一个跟踪功能,可以量化应用程序中哪些代码路径花费了最多的时间。如下图所示(在图片的下半部分),有大量的跟踪花费了十秒以上的时间。这并不好...\n\n当我点击第二行(6/17 2:04 PM)时,它给出了响应时间的详细信息:\n\n展开SQL语句可以看到,大部分耗时的查询是对帖子数据库的操作,有时还有对帖子进行排序/订购(见下图)。\n\n可能的想法\n我在这里有什么明显的错误吗?我很困惑,不确定如何加快速度。根据Scout的说法,我有两个想法:\n
- \n
- 控制器/控制器调用的SQL查询很慢
- HTML中嵌入的Ruby代码很慢。
\n
\n
\n控制器/控制器调用的SQL查询很慢:\n下面的代码显示了我在PostsController
中为@posts
赋值的代码。当用户访问主页时,将运行home
方法,当用户转到帖子页面时,将运行index
方法。这些查询是否很慢是因为数据库中有相当多的数据(5个帖子的文本和图片)?\n
class PostsController < ApplicationController #before_action :authenticate_user! before_action :set_post, only: [:show, :edit, :update, :destroy, :publish, :unpublish] def home @posts = Post.all.published.order('published_at DESC') end # GET /posts # GET /posts.json def index if user_signed_in? and current_user.admin @posts = Post.all.order("id") else @posts = Post.all.published end end
\nHTML中嵌入的Ruby代码很慢:\n我在一些HTML代码中使用Ruby来按日期排序帖子和确定最新的帖子。例如,在网站的侧边栏(在主页左边),有一个显示“最新”的部分,其逻辑如下:\n
最新
<% @post=Post.all.published.order("published_at").last %> <% if @post == nil or @post.published_at == nil %> 即将推出! <% else %> <%= render partial: "layouts/media", locals: {post: @post} %> <% end %>
\n同样,在侧边栏的“存档”部分,我按日期排序帖子,做出以下逻辑:\n
<% if Post.published.length != 0 %>存档
<% @published_posts = Post.published %> <% archives = Hash.new(0) %> <% @published_posts.each do |post| %> <% if archives[post.date] == 0 %> <% archives[post.date] = 1%> <% else %> <% archives[post.date] += 1 %> <% end %> <% end %> <% archives.each do |key, value| %> <% @published_posts.each do |post| %> <% if post.date == key %> <%= link_to post.title, post_path(@post) %> <% end %> <% end %> <% end %> <% end %>
\n我的想法是,也许遍历帖子需要很长时间,但我不完全确定。我觉得这是可以使用的有效代码,但也许其中某些地方非常慢。你们有什么想法吗?还值得注意的是,应用程序的内存使用量非常高:大约500MB。也许这些查询之所以慢是因为获取了大量的数据,但是我不确定对于这样的网页应用程序来说,“大量”数据是什么。当然,我关于网站缓慢的假设可能完全错误,所以我非常愿意听取你们的想法。最后,如果我使用的SQL查询/代码很慢,有没有办法可以加快速度/提高性能?提前感谢你们的帮助!
Ruby on Rails 网站运行缓慢的问题可能是由于SQL查询引起的。在这篇文章中,我将整理出这个问题的出现原因以及解决方法。
首先,在文章中提到,在PostsController
的home
方法中,原先使用了Post.all.published.order('published_at DESC')
这个查询语句来获取所有已发布的文章。然而,这个查询语句加载了所有的文章和它们的属性到内存中,导致了响应时间非常慢。为了解决这个问题,作者使用了Post.select("attribute")
来只选择需要加载的属性,从而显著提高了查询的响应时间。
作者还在其他部分的代码中使用了类似的优化方法。在PostsController
的index
方法中,作者根据用户的权限选择了不同的查询语句,只加载了必要的属性。在嵌入在HTML中的Ruby代码中,作者使用了Post.select
来选择需要加载的属性,从而避免了加载不必要的数据。
总结起来,通过选择需要加载的属性,避免加载不必要的数据,可以显著提高Ruby on Rails网站的响应时间。
然而,作者也指出了这种方法的局限性,即需要手动指定除了需要避免的属性以外的所有属性,这很难维护,并且对于其他关于Post
的查询,如Post.find
,这种方法也不适用。因此,作者提出了一个更好的解决方案,即将body
字段拆分成一个独立的PostBody
模型和表,并与Post
建立belongs_to
的关联。这样就可以实现懒加载,然后使用delegate :body, to: :post_body
来实现透明访问。
最后,还有他在工作中遇到的类似问题,这个问题几乎发生在20年前。
Ruby on Rails网站运行缓慢,可能是由于SQL查询导致的。这个问题的原因是缺乏SQL索引和对Post.all
的过多调用。
首先,慢查询涉及到WHERE published = ?
。如果posts.published
没有建立索引,查询将需要扫描整个表而不仅仅是已发布的帖子。另外,如果没有索引,按posts.published_at
排序也会很慢。
为了解决这个问题,在迁移中为posts.published
和posts.published_at
添加索引。
add_index(:posts, :published) add_index(:posts, :published_at)
其次,如果使用Post.all
或Post.published
,意味着将数据库中的所有帖子加载到内存中。如果不是全部使用,这样做就是一种巨大的时间浪费。
例如,在首页和主页上显示每篇帖子是不方便的。相反,应该使用分页来每次只获取和显示一页的帖子。可以使用一些gem来实现分页,如kaminari和will_paginate,或者使用更大的管理解决方案,如ActiveAdmin。如果不喜欢页面链接,也可以找到使用"无限滚动"的示例。
最后,可以添加缓存。由于网站不会经常更新,可以在多个层次进行缓存。可以阅读Caching with Rails: An Overview了解更多信息。
但是,缓存也会带来自身的问题。在进行基本性能优化之后再考虑是否需要缓存。
另外,可以查阅"SQL EXPLAIN"。虽然OP没有说明他们使用的是MySQL、PostgreSQL还是其他数据库,但是所有数据库都有相应的EXPLAIN系统。而且,ActiveRecord通过.explain方法支持这些系统。EXPLAIN会查询数据库执行查询的计划,并指定是否使用索引。如果数据库在执行计划的某个阶段不使用索引,将出现可怕的"Table Scan"。
总之,在解决Ruby on Rails网站运行缓慢的问题时,可以通过添加SQL索引、减少对Post.all
的调用、使用分页和缓存来优化性能。此外,还可以使用"SQL EXPLAIN"来查看数据库执行计划并确定是否使用索引。