进一步改进Active Record / Postgresql查询
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了进一步改进Active Record / Postgresql查询相关的知识,希望对你有一定的参考价值。
继续我的问题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...
这主要是有效的,但仍需要很长的时间。有没有办法提高性能?
你被这个问题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或整数来表示我们的关联。有关猴子修补核心导轨行为的常见警告适用。
我知道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的记录。
有一个游戏,看看它的外观 - 也批量大小,看看它如何影响事情。
有一点需要注意,你不能申请限制,所以要记住这一点。
我相信其他人会提出一个更加流畅的解决方案,但希望这可能有助于此期间。如果检查速度很糟糕,请告诉我,我会删除这个答案:)
以上是关于进一步改进Active Record / Postgresql查询的主要内容,如果未能解决你的问题,请参考以下文章
URL 中的 Rails slugs - 使用 Active Record 模型帖子的 Title 属性而不是 ID
在“要求”中:没有要加载的文件 -- active_record (LoadError)