编辑:我使用Ruby 2.3.3和Rails 5.2.4.3,供参考。
我有一个名为Event
的ActiveRecord模型,在我的数据库中有超过700万条记录。当我在Rails控制台中输入以下命令时,Rails记录器告诉我发生了I/O查找,这大约需要12.0ms:
irb(main):006:0> @events = Event.where("id > 0")
Event Load (12.0ms) SELECT `events`.* FROM `events` WHERE (id > 0) LIMIT 11
我的期望是,如果我使用||=
操作(如@events ||= 'foobar'
)有条件地将@events
重置为另一个值,我将不看到记录到屏幕上的第二个Event Load
语句(因为@events
已经存在,所以||=
意味着不需要计算表达式的后半部分)。然而,如果实际上看到第二次查找发生:
irb(main):007:0> @events ||= 'foobar'
Event Load (0.5ms) SELECT `events`.* FROM `events` WHERE (id > 0) LIMIT 11
当然,查找要快得多(0.5ms
vs12.0ms
),但是I/O发生的事实让我感到困惑。我觉得我误解了一些关于ActiveRecord如何对待||=
语句的基本知识,但我不确定那是什么。
我的目标是在实例变量中缓存第一个ActiveRecord查询的结果,这样对该实例变量的后续引用将不会调用任何类型的额外I/O调用,因此将节省本来用于此类I/O调用的时间。
编辑:以下是我输入到Rails控制台的完整命令序列的类似版本(这次使用的是我的应用程序的Role
模型),以及删节的结果:
irb(main):001:0> @roles = Role.where("id > ?", 0)
(3.9ms) SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci, @@SESSION.sql_mode = 'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION', @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483
Role Load (22.0ms) SELECT `roles`.* FROM `roles` WHERE (id > 0) LIMIT 11
=> #<ActiveRecord::Relation [#<Role id: 1, name: "Engineering Intern", account_id: 1, created_at: "2013-05-14 23:03:54", updated_at: "2013-05-14 23:03:54", deleted_at: nil>, #<Role id: 2, name: "Operations", account_id: 1, created_at: "2013-05-14 23:04:02", updated_at: "2013-05-14 23:04:02", deleted_at: nil>,
......
irb(main):002:0> @roles ||= :foobar
Role Load (0.4ms) SELECT `roles`.* FROM `roles` WHERE (id > 0) LIMIT 11
=> #<ActiveRecord::Relation [#<Role id: 1, name: "Engineering Intern", account_id: 1, created_at: "2013-05-14 23:03:54", updated_at: "2013-05-14 23:03:54", deleted_at: nil>, #<Role id: 2, name: "Operations", account_id: 1, created_at: "2013-05-14 23:04:02", updated_at: "2013-05-14 23:04:02", deleted_at: nil>,
......
编辑:我猜测,也许在底层,Ruby解释器读取x ||= y
和x = x || y
的方式之间可能存在细微的差异(至少对我来说),所以我也尝试了@roles = @roles || :foobar
,但我仍然看到了一个记录到REPL的SQL查询。
我认为你所看到的行为与主机有关。如果您在Rails应用程序的上下文中这样做,它会像预期的那样工作。例如,我有一个Client
模型,我编写了一个' get_them_all'方法,如下所示:
def self.get_them_all
@clients = Client.where("id > 52000")
puts "got them"
@clients ||= "foobar"
puts "still have them?"
@clients
end
当我在Rails控制台中运行Client.get_them_all
时,我看到对数据库的单个查询。同样有趣的是,在两个puts
语句之后运行单个查询。Rails只在实际需要使用结果时才访问数据库。在此之前,它只是在@clients变量中有一个我称之为新生的查询。
此行为意味着您可以将Client#get_them_all
方法与其他查询片段链接起来,因为它是ActiveRecord::Relation
。因此,在rails控制台
$> Client.get_them_all.class.name #=> ActiveRecord::Relation, not Array
$> Client.get_them_all.where(lastName: 'Escobar') # I can append 'where'
负载(和块)应该能帮你解决这个问题。
如果还没有加载记录,则导致从数据库加载记录。如果出于某种原因需要在实际使用之前显式加载一些记录,则可以使用此方法。返回值是关系本身,而不是记录。
https://api.rubyonrails.org/v6.1.4/classes/ActiveRecord/Relation.html method-i-load
@events = Event.where("id > 0").load
@events ||= 'foobar'