在ActiveRecord中存储序列化哈希和键/值数据库对象的优缺点是什么?

18

如果我有几个对象,每个对象基本上都有一个Profile,我用它来存储随机属性,以下是:

  1. 将序列化的哈希存储在记录的列中,还是
  2. 存储一堆belong_to主对象的键/值对象。

代码

假设您有这样的STI记录:

class Building < ActiveRecord::Base
  has_one :profile, :as => :profilable
end
class OfficeBuilding < Building; end
class Home < Building; end
class Restaurant < Building; end

每个has_one: profile

选项1. 序列化哈希

class SerializedProfile < ActiveRecord::Base
  serialize :settings
end

create_table :profiles, :force => true do |t|
  t.string   :name
  t.string   :website
  t.string   :email
  t.string   :phone
  t.string   :type
  t.text     :settings
  t.integer  :profilable_id
  t.string   :profilable_type
  t.timestamp
end

选项2. 键/值存储

class KeyValueProfile < ActiveRecord::Base
  has_many :settings
end

create_table :profiles, :force => true do |t|
  t.string   :name
  t.string   :website
  t.string   :email
  t.string   :phone
  t.string   :type
  t.integer  :profilable_id
  t.string   :profilable_type
  t.timestamp
end

create_table :settings, :force => true do |t|
  t.string   :key
  t.text     :value
  t.integer  :profile_id
  t.string   :profile_type
  t.timestamp
end

你会选择哪个?
假设99%的时间我不需要通过自定义“设置”进行搜索。只是想知道在性能和未来问题的可能性方面的权衡。而自定义“设置”的数量可能会在10-50个之间。
我宁愿选择第二个选项,使用设置表,因为它遵循ActiveRecord面向对象的约定。但我想知道在这种情况下,这是否会带来过高的性能成本。
注意:我只关心关系型数据库。这对MongoDB / Redis / CouchDB等非关系型数据库来说是完美的,但我想纯粹了解SQL方面的利弊。

2
如果可以避免,你真的不想将东西序列化存储。在检索方面没有真正的速度优势,而在搜索和更新方面则存在劣势。我必须使用包含另一个数据库中约50个字段的XML的DB记录来工作,并将其存储为文本字段。搜索需要使用“like”语句,我必须解析和搜索XML以获取所需的数据。这是一个可怕的设计,最终会消失。 - the Tin Man
2
@the Tin Man:一些严肃的数据库,包括PostgreSQL,支持XML数据或文本列,并能够轻松地搜索(XPath)、索引和创建XML数据视图。请查看:XML数据类型XML函数 - gertas
@gertas,我很想使用Postgres,但他们没有问我。此外,即使Postgres说除非提供索引字符串,否则我们仍然被困在字符串搜索中。一旦你知道它是哪个记录,那么你可以使用XPath将数据提取为字段,但我可以通过检索整个XML文本并在Ruby或Perl中执行它来更轻松地完成这项工作。 - the Tin Man
3个回答

14

我曾经遇到过同样的问题,但最终做出了决定。

哈希序列化选项会导致维护问题。很难查询、扩展或重构这样的数据 - 任何微小的更改都需要进行迁移,这意味着需要读取每个记录并反序列化和序列化回去,而且根据重构序列化可能会发生异常。

我尝试了二进制序列化和 JSON - 第二种方法更容易提取和修复,但仍然太麻烦。

现在我正在尝试使用单独的设置表 - 这样更容易维护。我计划使用Preferences gem来实现这一点,它主要为方便使用而做了所有抽象。我不确定它是否已经支持 Rails 3 - 不过它很小,如果需要可以扩展它。

更新于 2013 年 11 月

最近发布的 Rails 4 支持 PostgreSQL 9.1+ 的许多新功能,例如 hstorejson 列类型用于动态数据集。这里有一篇文章介绍了在 Rails 4 中使用 hstore。这两种类型都支持索引和高级查询功能(Json 需要 Pg 9.3)。对于 Rails 3 用户,也可以使用activerecord-postgres-hstore gem 来使用 Hstore。

我正在将我的项目中一些非关键的首选项表迁移到 hstores。在迁移过程中,我只需更新表定义并为每个表执行一个 SQL 查询以移动数据。


5
我建议创建一个称为“属性(Attribute)”的模型,并让需要多个属性的每个对象都具有has_many关联。这样,您就不必处理序列化或任何脆弱的内容。如果您使用:join语法,则不会遇到任何实际性能问题。
将数据序列化到关系型数据库(RDBMS)中几乎总是不明智的。这不仅涉及查询,还涉及描述和迁移数据的能力(而序列化破坏了这种能力)。
class Building < ActiveRecord::Base
  has_many :attributes
end

class Attribute < ActiveRecord::Base
   belongs_to :building
end

create_table :attributes, :force => true do |t|
  t.integer :building_id
  t.string :att_name
  t.string :data
  t.timestamp
end

2
我曾面临您所描述的同样困境,最终选择了键值对表实现,因为其他人提到的潜在维护优势。未来迁移时,更容易思考如何选择和更新数据库中单独行的信息,而不是序列化哈希值。
使用序列化哈希值时,我个人经历的另一个问题是必须小心存储的序列化数据不能超过数据库文本字段可容纳的大小。如果不小心,您很容易出现缺失或损坏的数据。例如,使用您描述的SerializedProfile类和表,可能会导致此行为:
profile = SerializedProfile.create(:settings=>{})
100.times{ |i| profile.settings[i] = "A value" }
profile.save!
profile.reload
profile.settings.class #=> Hash
profile.settings.size #=> 100

5000.times{ |i| profile.settings[i] = "A value" }
profile.save!
profile.reload
profile.settings.class #=> String
profile.settings.size #=> 65535

需要注意你的数据库限制,否则当下次检索序列化数据时会被裁剪,导致ActiveRecord无法重新进行序列化。

如果您想使用序列化哈希,请放心尝试!我认为它在某些情况下有潜力表现得很好。我偶然发现了activerecord-attribute-fakers插件,它看起来很适合这种情况。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接