我有一个应用程序,允许用户根据非常多的标准过滤申请人。每个条件都由跨越数据库中多个表的布尔列表示。我认为最好使用纯 sql 并将大部分工作放在数据库中,而不是使用活动记录模型。为了做到这一点,我必须根据用户选择的条件构建一个相当复杂的 sql 查询,然后通过数据库上的 AR 运行它。有一个更好的方法吗?我想在同时拥有可维护和非易碎代码的同时最大限度地提高性能?任何帮助将不胜感激。
2 回答
在不了解更多细节的情况下很难完全回答这个问题,但无论如何我都会尝试。
虽然数据库在很多方面都做得不好,但它们非常擅长过滤数据,尤其是在处理大量数据时。
如果您在 Ruby on Rails(或几乎任何其他编程语言)中进行过滤,系统将不得不从数据库中检索所有未过滤的数据,这将导致大量磁盘 I/O 和网络(或进程间)流量. 然后它必须遍历内存中所有未过滤的结果,这对 RAM 和 CPU 来说可能是一个相当大的负担。
如果您在数据库中进行过滤,则很有可能大多数记录将永远不会真正从磁盘中检索,不会移交给 RoR,也不会被过滤。索引甚至存在的主要原因是为了避免昂贵的操作以加快速度。(是的,它们还有助于维护数据完整性)
但是,要完成这项工作,您可能需要帮助数据库以有效地完成其工作。您将必须创建与您的过滤条件匹配的索引,并且您可能必须研究某些类型的查询的性能问题(如何避免临时表等)。然而,这绝对是值得的。
话虽如此,实际上有几种类型的查询是给定数据库不擅长的。这些很少,而且相距甚远,但它们确实存在。在这些情况下,在 RoR 中实现可能是更好的方法。即使没有更多地了解您的情况,我会说您的查询不在其中,这是一个非常安全的赌注。
正如@hazzit 所说,没有太多细节很难回答,但这是我的两分钱。原始 SQL 通常用于执行复杂的操作,如聚合、计算等。但是,当涉及到搜索/过滤功能时,我经常发现使用原始 SQL 过大并且不太可维护。
这里的关键问题是:你能把你的问题分解成多个独立的过滤器吗?如果答案是肯定的,那么您应该利用 ActiveRecord 和 Arel 的强大功能。我经常发现自己在我的模型中实现了这样的东西:
scope :a_scope, ->{ where something: true }
scope :another_scope, ->( option ){ where an_option: option }
scope :using_arel, ->{ joins(:assoc).where Assoc.arel_table[:some_field].not_eq "foo" }
# cue a bunch of scopes
def self.search( options = {} )
output = relation
relation = relation.a_scope if options[:an_option]
relation = relation.another_scope( options[:another_option] ) unless options[:flag]
# add logic as you need it
end
这个解决方案的美妙之处在于您声明了一个干净的界面,您可以在其中直接从您的复选框和字段中倾倒所有参数,并返回一个关系。将查询分解为多个可重用的范围有助于保持事物的可读性和可维护性;使用search
类方法将它们联系在一起并允许完整的文档......总而言之,使用 Arel 有助于保护应用程序免受注入。
作为旁注,这不会阻止您使用原始 SQL,只要查询可以在范围内隔离。
如果此方法不适合您的需要,还有另一种选择:使用成熟的搜索/过滤解决方案,如Sunspot。这使用了与您的数据库分开的另一个存储,该存储为您的数据的定义部分编制索引,以便于轻松和高效地搜索。