进一步改进Active Record / Postgresql查询

问题描述 投票:1回答:2

继续我的问题here,我正在努力进一步改进搜索。我们首先搜索重播表(搜索2k记录),然后获得与该表相关联的唯一玩家(每个10个,所以20k记录)并渲染JSON。这是通过控制器完成的,搜索内容如下:

def index
 @replays = Replay.includes(:players).where(map_id: params['map_id'].to_i).order(id: :desc).limit(2000)
 render json: @replays[0..2000].to_json(include: [:players])
end 

表现:

Completed 200 OK in 254032ms (Views: 34.1ms | ActiveRecord: 20682.4ms)

实际的Active Record搜索读作:

Replay Load (80.4ms)  SELECT  "replays".* FROM "replays" WHERE "replays"."map_id" = $1 ORDER BY "replays"."id" DESC LIMIT $2  [["map_id", 1], ["LIMIT", 2000]]
Player Load (20602.0ms)  SELECT "players".* FROM "players" WHERE "players"."replay_id" IN (117217...

这主要是有效的,但仍需要很长的时间。有没有办法提高性能?

ruby-on-rails ruby postgresql activerecord
2个回答
0
投票

你被这个问题https://postgres.cz/wiki/PostgreSQL_SQL_Tricks_I#Predicate_IN_optimalization咬了

当值列表长于八十个数字时,我发现了关于IN谓词的最优化可能性的注释pg_performance。对于更长的列表,最好使用多值创建常量子查询:

SELECT * FROM tab WHERE x IN(1,2,3,.. n); - n> 70

- 更快的情况SELECT * FROM tab WHERE x IN(VALUES(10),(20));

对于较大数量的项目,使用VALUES会更快,因此请勿将其用于较小的值集合。

基本上,SELECT * FROM WHERE IN ((1),(2)...)具有很长的值列表非常慢。如果你可以把它转换成值列表,比如SELECT * FROM WHERE IN (VALUES(1),(2) ...),它的速度会快得多

不幸的是,由于这是在活动记录中发生的,因此对查询进行控制有点棘手。您可以避免使用includes调用,只需手动构造SQL以加载所有子记录,然后手动建立关联。

或者,您可以猴子补丁活动记录。这是我在rails 4.2中在初始化程序中所做的。

module PreloaderPerformance
  private
  def query_scope(ids)
    if ids.count > 100
      type = klass.columns_hash[association_key_name.to_s].sql_type
      values_list = ids.map do |id|
        if id.kind_of?(Integer)
          " (#{id})"
        elsif type == "uuid"
          " ('#{id.to_s}'::uuid)"
        else
          " ('#{id.to_s}')"
        end
      end.join(",")

      scope.where("#{association_key_name} in (VALUES #{values_list})")
    else
      super
    end
  end
end

module ActiveRecord
  module Associations
    class Preloader
      class Association #:nodoc:
        prepend PreloaderPerformance
      end
    end
  end
end

这样做我看到我的一些查询速度提高了50倍,而且还没有任何问题。注意它没有经过完全的战斗测试,我敢打赌如果你的关联是使用foreign_key关系的唯一数据类型会有一些问题。在我的数据库中,我只使用uuids或整数来表示我们的关联。有关猴子修补核心导轨行为的常见警告适用。


0
投票

我知道find_each可以用于批处理查询,这可能会减轻这里的内存负载。你可以试试下面的内容,看看它对时间的影响吗?

Replay.where(map_id: params['map_id'].to_i).includes(:players).find_each(batch_size: 100).map do |replay|
  replay.to_json(includes: :players)
end

我不确定这会有效。可能是映射否定了批处理的好处 - 肯定有更多的查询,但它会使用更少的内存,因为它不需要一次存储> 20k的记录。

有一个游戏,看看它的外观 - 也批量大小,看看它如何影响事情。

有一点需要注意,你不能申请限制,所以要记住这一点。

我相信其他人会提出一个更加流畅的解决方案,但希望这可能有助于此期间。如果检查速度很糟糕,请告诉我,我会删除这个答案:)

© www.soinside.com 2019 - 2024. All rights reserved.