【问题标题】:Rails Memoization on a has many relationRails Memoization 有很多关系
【发布时间】:2018-10-13 20:24:22
【问题描述】:

是否可以通过对包含 has_many 关联的 SQL 查询的记忆来缓存实例变量?在下面的两个示例方法中, some_instance 方法将按预期进行缓存,但 some_other_instance 将缓存 Artist 而不是歌曲关联,这会导致每次运行该方法时都会进行 SQL 查询。

当方法返回“ActiveRecord::Associations::CollectionProxy”或“ActiveRecord::Relation”时,它似乎不会完全缓存。

class Artist < ApplicationRecord
    has_many :artist_songs
    has_many :songs, through: :artist_songs

    def self.some_instance
        @some_instance ||= find(10)
    end

    def self.some_other_instance
        @some_other_instance ||= find(10).songs
    end
end

【问题讨论】:

  • 说“第一次工作正常”你真的是说它不使用select * from songs ... SQL 查询对songs 表执行选择吗? “第二个仍将执行 SQL 查询”-您到底在说哪个查询?如果您对在第二种方法中找到 Artist 的查询感到恼火,那么只需使用第一种方法来记忆它:@some_other_instance ||= some_instance.songs
  • 抱歉不清楚。 “some_instance”方法会缓存变量,并且在再次运行该方法时不会执行更多 SQL 查询。 “some_other_instance”方法缓存了 Artist,但不缓存歌曲关联,所以当再次运行该方法时,我看到了歌曲集合的 SQL 查询。
  • 我刚刚尝试了您关于重写“some_other_instance”方法的建议,但不幸的是遇到了同样的问题。
  • 你能显示你对some_other_instance方法的两次调用的输出吗?

标签: ruby-on-rails


【解决方案1】:

some_instance 方法缓存find(10) 方法执行数据库查询并返回记录的结果。

但是some_other_instance 方法缓存的是一个关系对象,而不是数据库查询的结果。实际上,如果您在 Artist.some_other_instance;note ; at the end)这样的 rails 控制台中运行,则不会有任何数据库查询,因为您不使用查询的结果。但是当你只运行Artist.some_other_instance 时就会出现查询,因为结果是用来打印的。

所以要真正缓存some_other_instance 方法,您需要添加to_a 之类的方法,例如:

def self.some_other_instance @some_other_instance ||= find(10).songs.to_a end

但在这种情况下,此方法不会是“可链接的”。

【讨论】:

  • to_a 达到了我的预期,谢谢。除非有人有办法做到这一点而不必变成数组,否则这似乎是最好的选择。
  • 实际上,如果不通过to_a 或其他方式获取数据库查询,就不可能缓存关系结果。因为关系只是 SQL 查询的包装器,当您链接一些关系时,结果 SQL 查询也会发生变化。因此不合逻辑。
  • 复制。那就这样走吧。我在上面添加了一个关于 to_a 本身性能的后续问题。
【解决方案2】:

to_a 提供了缓存关系的解决方案。

在下面的日志中,SQL 查询需要 1883.4 毫秒,构建视图需要 623.4 毫秒。总时间为 15053 毫秒,这意味着在对象上添加 to_a 需要 12546.2 毫秒。如果我关闭 to_a,总时间会减少到大约 2000 毫秒。

构建对象需要这么长时间是正常的吗?

Started GET "/build" for 127.0.0.1 at 2018-10-14 16:35:30 -0700
Processing by ArtistsController#build as HTML
  Artist Load (208.4ms)  SELECT `artists`.`id`, `artists`.`name`, `artists`.`uri`, `artists`.`artwork_url`, updates.popularity, updates.created_at AS pop_created_at, reachupdates.reach, reachupdates.created_at AS reach_created_at FROM `artists` INNER JOIN `updates` ON `updates`.`artist_id` = `artists`.`id` INNER JOIN `reachupdates` ON `reachupdates`.`artist_id` = `artists`.`id` WHERE `artists`.`id` = 1
  Song Load (510.5ms)  SELECT songs.*, songupdates.* FROM `songs` INNER JOIN `artist_songs` ON `artist_songs`.`song_id` = `songs`.`id` INNER JOIN `artists` ON `artists`.`id` = `artist_songs`.`artist_id` INNER JOIN `songupdates` ON `songupdates`.`song_id` = `songs`.`id` WHERE `artists`.`id` = 1
  Playlist Load (1089.2ms)  SELECT playlists.*, playlistupdates.* FROM `playlists` INNER JOIN `playlist_songs` ON `playlist_songs`.`playlist_id` = `playlists`.`id` INNER JOIN `songs` ON `songs`.`id` = `playlist_songs`.`song_id` INNER JOIN `playlistupdates` ON `playlistupdates`.`playlist_id` = `playlists`.`id` LEFT OUTER JOIN `artist_songs` ON `artist_songs`.`song_id` = `songs`.`id` LEFT OUTER JOIN `artists` ON `artists`.`id` = `artist_songs`.`artist_id` WHERE `artists`.`id` = 1 AND `playlists`.`relevant` = 1 AND (playlistupdates.id IN (SELECT MAX(playlistupdates.id) AS id FROM playlistupdates GROUP BY playlistupdates.playlist_id)) ORDER BY `playlistupdates`.`followers` DESC
  Rendering artists/build.html.haml within layouts/application
  Rendered artists/build.html.haml within layouts/application (15.6ms)
  Rendered layouts/_head.html.haml (45.8ms)
  Rendered svg/_settings.html.erb (0.9ms)
  Rendered svg/_search.html.erb (0.4ms)
  Rendered layouts/_header_new.html.haml (73.3ms)
  Rendered shared/_autocomplete-box.html.haml (14.2ms)
Completed 200 OK in 15053ms (Views: 623.4ms | ActiveRecord: 1883.4ms)

【讨论】:

  • 时间方面是完全不同的问题。因为我们不知道表有多大,这个特定页面需要多少数据等。例如,这可能需要很长时间,因为您从数据库中检索了大量记录,然后 Ruby 尝试在其中构建 AR 实例记忆,它可能你甚至不需要那个。换句话说,这是一个完全不同的问题。
猜你喜欢
  • 2014-05-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-05-24
  • 2019-02-01
相关资源
最近更新 更多