Help us learn about your current experience with the documentation. Take the survey.
单表继承
摘要:不要使用单表继承(STI)设计新表。对于使用 STI 模式的现有表,避免添加新类型,并考虑将它们拆分为单独的表。
STI 是一种数据库设计模式,其中单个表存储不同类型的记录。这些记录具有共享列的子集和另一列,该列指示应用程序该记录应表示为哪个对象。例如,这可用于在同一表中存储两种不同类型的 SSH 密钥。ActiveRecord 利用它并提供了一些使 STI 使用更方便的功能。
我们不再允许新的 STI 表,因为它们:
- 会导致表包含大量行,而我们应努力保持表较小。
- 需要额外的索引,增加了我们对轻量级锁的使用,其饱和可能导致事故。
- 通过必须按值过滤所有数据来增加开销,导致读取时更多的页面访问。
- 使用
class_name为对象加载正确的类,但存储类名是昂贵且不必要的。
除了使用 STI,请考虑以下替代方案:
- 为每种类型使用不同的表。
- 避免添加
*_type列。这是一种代码异味,可能表明将来会添加新类型,并且将来的重构会更加困难。 - 如果你已经有一个实际上是基于
_type列的 STI 表,请考虑:- 将现有数据拆分为多个表。
- 重构,以便新类型可以作为新表添加,同时保留现有表(例如,将基类的逻辑移到 concern 中)。
如果,在考虑了上述所有缺点和替代方案之后,STI 是手头问题的唯一解决方案,我们至少可以通过使用枚举类型和 EnumInheritance concern 来避免在记录中保存类名的问题:
class Animal < ActiveRecord::Base
include EnumInheritance
enum species: {
dog: 1,
cat: 2
}
def self.inheritance_column_to_class_map = {
dog: 'Dog',
cat: 'Cat'
}
def self.inheritance_column = 'species'
end
class Dog < Animal
self.allow_legacy_sti_class = true
end
class Cat < Animal
self.allow_legacy_sti_class = true
end如果你的表已经有 *_type,可以根据需要添加不同类型的新类。
在迁移中
每当在迁移中使用模型时,都应禁用单表继承。由于 Rails 加载关联的方式(即使在迁移中),未能禁用 STI 可能会导致加载意外的代码或关联,这可能在升级过程中导致意外的副作用或故障。
class SomeMigration < Gitlab::Database::Migration[2.1]
class Services < MigrationRecord
self.table_name = 'services'
self.inheritance_column = :_type_disabled
end
def up
...除了禁用 STI 或 EachBatch 之外,如果模型不需要添加任何内容,请使用辅助方法 define_batchable_model 而不是定义类。这确保迁移隔离地加载迁移的列,并且该辅助方法默认禁用 STI。
class EnqueueSomeBackgroundMigration < Gitlab::Database::Migration[2.1]
disable_ddl_transaction!
def up
define_batchable_model('services').select(:id).in_batches do |relation|
jobs = relation.pluck(:id).map do |id|
['ExtractServicesUrl', [id]]
end
BackgroundMigrationWorker.bulk_perform_async(jobs)
end
end
...