【问题标题】:When to use inherited tables in PostgreSQL?什么时候在 PostgreSQL 中使用继承表?
【发布时间】:2011-03-05 17:02:16
【问题描述】:

在哪些情况下应该使用继承表?我尝试非常简单地使用它们,而继承在 OOP 世界中看起来并不像。

我认为它是这样工作的:

users 包含所有用户级别所需的所有字段。 moderatorsadminsbloggers 等表,但字段从父级检查。例如,users 具有电子邮件字段,并且继承了 bloggers 现在也具有它,但它对于 usersbloggers 并不是唯一的。 IE。就像我将电子邮件字段添加到两个表一样。

我能想到的唯一用法是通常使用的字段,例如 row_is_deletedcreated_atmodified_at。这是继承表的唯一用途吗?

【问题讨论】:

    标签: postgresql


    【解决方案1】:

    在 postgres 中使用表继承有一些主要原因。

    假设我们有一些统计数据所需的表,每个月都会创建和填充这些表:

    statistics
        - statistics_2010_04 (inherits statistics)
        - statistics_2010_05 (inherits statistics)
    

    在此示例中,每个表中有 2.000.000 行。每个表都有一个 CHECK 约束,以确保只存储匹配月份的数据。

    那么是什么让继承成为一个很酷的特性——为什么拆分数据很酷?

    • 性能:选择数据时,我们选择 * FROM 统计数据,其中日期在 x 和 Y 之间,而 Postgres 仅使用有意义的表。例如。 SELECT * FROM statistics WHERE date BETWEEN '2010-04-01' AND '2010-04-15' 仅扫描表 statistics_2010_04,不会触及所有其他表 - 快!
    • 索引大小:我们没有大胖表在列日期上具有大索引。我们每个月都有小表,索引小 - 读取速度更快。
    • 维护:我们可以在每个月表上运行vacuum full、reindex、cluster,而无需锁定所有其他数据

    要正确使用表继承作为性能提升器,请查看 postgresql 手册。 您需要在每个表上设置 CHECK 约束来告诉数据库,您的数据在哪个键上被拆分(分区)。

    我大量使用表继承,尤其是在存储按月分组的日志数据时。提示:如果您存储永远不会更改的数据(日志数据),请使用 CREATE INDEX ON () WITH(fillfactor=100); 创建或索引这意味着索引中不会保留任何更新空间 - 磁盘上的索引更小。

    更新: fillfactor 默认为 100,来自http://www.postgresql.org/docs/9.1/static/sql-createtable.html

    表格的填充因子是 10 到 100 之间的百分比。默认值为 100(完全填充)

    【讨论】:

    • 另一个分区示例
    • 在您的第 1 项中,Postgres 如何理解需要在哪些表中进行搜索?您从父表中选择,日期范围只是拆分的一个方便示例。父表无法知道这个逻辑。还是我错了?
    • 在父表上执行查询实际上与在公共行上的每个后代表上执行 UNION ALL 查询相同。查询计划器知道定义每个分区的检查约束,只要它们不重叠分区,就可以使用它们来确定它可以跳过检查表,其中 CHECK 指示不会返回任何行。 Postgres docs on this
    • @avesus heh...上面的代码本身就值得这样讽刺。通常将这类事情包装到某种维护例程中。这可以像在某些条件下处理它的存储过程一样简单,一个 cron 作业或其他任何东西。按日期分区是很常见的,但我发现自己也时不时地按表空间分配进行分区,这需要一些外部信息——写一个分区保姆花费的 30 分钟对于控制来说是非常值得的它给了你。
    • 嗯。你确定它不会阻塞?我有类似的设置,但是当我在单个分区上运行 CLUSTER 命令时,另一个分区持有的数据的 SELECT 语句会阻塞!
    【解决方案2】:

    “表继承”意味着与“类继承”不同的东西,它们有不同的用途。

    Postgres 是关于数据定义的。有时非常复杂的数据定义。 OOP(在常见的 Java 色彩意义上)是关于将行为从属于单个原子结构中的数据定义。 “继承”这个词的目的和意义在这里明显不同。

    在 OOP 领域,我可能会定义(这里的语法和语义非常松散):

    import life
    
    class Animal(life.Autonomous):
      metabolism = biofunc(alive=True)
    
      def die(self):
        self.metabolism = False
    
    class Mammal(Animal):
      hair_color = color(foo=bar)
    
      def gray(self, mate):
        self.hair_color = age_effect('hair', self.age)
    
    class Human(Mammal):
      alcoholic = vice_boolean(baz=balls)
    

    此表可能如下所示:

    CREATE TABLE animal
      (name       varchar(20) PRIMARY KEY,
       metabolism boolean NOT NULL);
    
    CREATE TABLE mammal
      (hair_color  varchar(20) REFERENCES hair_color(code) NOT NULL,
       PRIMARY KEY (name))
      INHERITS (animal);
    
    CREATE TABLE human
      (alcoholic  boolean NOT NULL,
       FOREIGN KEY (hair_color) REFERENCES hair_color(code),
       PRIMARY KEY (name))
      INHERITS (mammal);
    

    但是行为在哪里?它们不适合任何地方。这不是数据库世界中讨论的“对象”的目的,因为数据库关注的是数据,而不是程序代码。您可以在数据库中编写函数来为您进行计算(通常是一个非常好的主意,但不是真正适合这种情况的东西)但函数与方法不同 - 以您正在谈论的 OOP 形式理解的方法about 故意降低了灵活性。

    关于继承作为示意图设备,还有一件事需要指出:从 Postgres 9.2 开始,无法同时跨所有分区/表族成员引用外键约束。您可以编写检查来执行此操作或以其他方式绕过它,但它不是内置功能(它归结为复杂索引的问题,实际上,没有人编写必要的位来使其自动化)。为了这个目的而不是使用表继承,通常在数据库中更好地匹配对象继承是对表进行示意性扩展。像这样的:

    CREATE TABLE animal
      (name       varchar(20) PRIMARY KEY,
       ilk        varchar(20) REFERENCES animal_ilk NOT NULL,
       metabolism boolean NOT NULL);
    
    CREATE TABLE mammal
      (animal      varchar(20) REFERENCES animal PRIMARY KEY,
       ilk         varchar(20) REFERENCES mammal_ilk NOT NULL,
       hair_color  varchar(20) REFERENCES hair_color(code) NOT NULL);
    
    
    CREATE TABLE human
      (mammal     varchar(20) REFERENCES mammal PRIMARY KEY,
       alcoholic  boolean NOT NULL);
    

    现在我们有一个动物实例的规范引用,我们可以可靠地将其用作外键引用,并且我们有一个“ilk”列,它引用了一个指向 xxx_ilk 定义表的表,该表指向扩展数据(如果同类是泛型类型本身,则表示没有)。针对这种模式编写表函数、视图等非常容易,以至于当您诉诸 OOP 风格的类继承来创建对象类型系列时,大多数 ORM 框架都会在后台执行此类操作。

    【讨论】:

    • 如果要添加所有已知的哺乳动物会怎样?你会继承自哺乳动物还是像这里一样拥有外键?我对外键的问题是你最终不得不做这么多的连接。
    • @puk 您首先需要确定为什么要添加所有已知的哺乳动物。数据的形状将由使用数据的方式决定(在这种情况下,可能没有必要为每个动物都有一个表——考虑游戏动物寓言的数据库,你确实拥有各种类型的暴徒)。在上面的例子中,我通常会添加一个最常见的 mammal JOIN human 视图,因为每次都写一个连接很烦人。但不要避免加入。连接是将 R 放入 RDBMS 的原因。如果你不喜欢连接,你应该使用不同的数据库类型。
    • @zxq9:我猜测由于大表而导致的大量、低效的连接是物化视图发挥作用的地方? (我已经很久没有使用 Postgres 了)
    • @MarkKCowan 连接并不是低效的。由于设计草率,试图加入非索引、非唯一字段(因为模式远未接近规范化)是低效的。在这些情况下,物化视图可能会有所帮助。如果您需要规范化数据作为原理图基础(通常是正确的),但还需要一些工作的、非规范化的表示形式,这些表示形式更容易使用,以提高处理效率(预先加载计算)或认知效率,物化视图也很有帮助。不过,如果你写的比读的多,那就是悲观了。
    • @MarkKCowan “慢”是一个相对术语。在大型业务系统和游戏服务器中,我们可以接受大约 50 毫秒来返回查询,根据我的经验,20 个表连接从来都不是问题(无论如何,在 Postgres 8+ 中)。但是,如果管理层希望对未索引数据(或派生值!)的 5 个以上表的 >10b 行连接做出
    【解决方案3】:

    只要您不需要在父表上创建外键,就可以在 OOP 范例中使用继承。例如,如果您有一个抽象类车辆存储在车辆表中,并且表 car 继承自它,则所有汽车都将在车辆表中可见,但车辆表上驱动程序表中的外键与这些不匹配记录。

    继承也可以用作partitionning 工具。当您的表打算永远增长(日志表等)时,这尤其有用。

    【讨论】:

    • 表约束不是继承的,所以它不仅仅是外键。您可以将表约束应用于在 DDL 中创建的子表,也可以编写触发器来实现相同的约束。
    【解决方案4】:

    继承的主要用途是用于分区,但有时它在其他情况下也很有用。在我的数据库中,有许多表仅在外键上有所不同。我的“抽象类”表“图像”包含一个“ID”(它的主键必须在每个表中)和 PostGIS 2.0 栅格。诸如“site_map”或“artifact_drawing”之类的继承表具有外键列(“site_map”的“site_name”文本列,“artifact_drawing”表的“artifact_id”整数列等)以及主键和外键约束;其余部分继承自“图像”表。我怀疑将来我可能必须在所有图像表中添加一个“描述”列,所以这可能会为我节省大量工作而不会产生真正的问题(好吧,数据库可能运行得慢一点)。

    编辑:另一个很好的用途:使用 two-table handling of unregistered users,其他 RDBMS 在处理这两个表时会遇到问题,但在 PostgreSQL 中这很容易 - 当您对继承的“未注册用户”中的数据不感兴趣时​​,只需添加 ONLY桌子。

    【讨论】:

      【解决方案5】:

      我对继承表的唯一经验是分区。它运行良好,但不是 PostgreSQL 中最复杂和最易于使用的部分。

      上周我们也在寻找同样的 OOP 问题,但我们在 Hibernate 方面遇到了太多问题(不喜欢我们的设置),所以我们没有在 PostgreSQL 中使用继承。

      【讨论】:

        【解决方案6】:

        当表之间的关系超过 1 对 1 时,我会使用继承。

        示例:假设您要存储具有 x、y、旋转、比例属性的对象地图位置。

        现在假设您有几种不同类型的对象要显示在地图上,并且每个对象都有自己的地图位置参数,并且地图参数永远不会被重复使用。

        在这些情况下,表继承对于避免维护未规范化的表或必须创建位置 ID 并将其交叉引用到其他表非常有用。

        【讨论】:

          【解决方案7】:

          尽量少用。这通常意味着永远不会,它归结为一种创建违反关系模型的结构的方式,例如通过打破信息原则和创建袋子而不是关系。

          相反,使用表分区与适当的关系建模相结合,包括更多的范式。

          【讨论】:

          • PostgreSQL的继承特性违反了关系模型,打破了信息化原则,这不是事实。信息原理说,关系数据库中的所有数据都由关系中的数据值表示,并且所有查询结果再次表示为关系。(en.wikipedia.org/wiki/Relational_model)总是如此,因为所有表都继承了另一个表,又是简单的表格。出于这个原因,也没有“包”之类的东西,不管它是什么意思。
          • 好吧,就关系模型而言,维基百科几乎不是参考;它拒绝承认 SQL 违反了关系模型。包是没有键的表,因为它可能有重复项,因此不是关系;关系必须是集合。
          • 这不是功能本身的问题,而是如何使用的问题。如果您使用 uuid 作为标识符,您将拥有所有子表的唯一键。
          • 你说得有道理,但这里的问题是继承导致建模者忽略了关系模型。 UUID 不是真正的键,而是代理键。仍然必须声明自然键。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-10-13
          • 2019-09-24
          • 1970-01-01
          • 1970-01-01
          • 2010-10-22
          • 1970-01-01
          相关资源
          最近更新 更多