何时在PostgreSQL中使用继承的表?


84

在哪些情况下应该使用继承的表?我试图非常简单地使用它们,并且在OOP世界中似乎没有继承。

我认为它像这样工作:

该表users具有所有用户级别所需的所有字段。表像moderatorsadminsbloggers,等,但字段从父检查。例如users具有电子邮件字段和继承bloggers现在已经太,但它不是唯一的两个users,并bloggers在同一时间。即。与我将电子邮件字段添加到两个表相同。

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

Answers:


111

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

可以说,我们有一些统计信息所需的表,每个月都会创建和填充这些表:

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

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

那么,什么使继承成为一个很酷的功能-为何拆分数据很酷?

  • 性能:选择数据时,我们从x和Y之间的日期中选择* FROM statistics,而Postgres仅使用有意义的表。例如。选择*从统计信息中'2010-04-01'和'2010-04-15'之间的日期仅扫描表statistics_2010_04,所有其他表都不会被触及-快速!
  • 索引大小:我们没有大的脂肪表,而该日期的脂肪指数也很大。我们每个月都有小表,索引也小-读取速度更快。
  • 维护:我们可以在每个月表上运行真空完全,重新索引,集群,而无需锁定所有其他数据

要正确使用表继承作为性能增强器,请查看postgresql手册。您需要在每个表上设置CHECK约束以告知数据库,您的数据将在哪个键上进行分割(分区)。

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

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

表格的填充系数是10到100之间的百分比。默认值为100(完全打包)


13
分手的另一个例子
Frank Heikens 2010年

4
在您的项目1中,Postgres如何理解要搜索哪些表?您从父表中选择,日期范围只是拆分的一个方便示例。父表无法知道此逻辑。还是我错了?
亚历山大·帕拉玛丘克

4
在父表上执行查询与在公共行上的每个后代表上对UNION ALL执行查询实际上是相同的。查询计划者知道定义每个分区的检查约束,只要它们不与分区重叠,就可以使用它们来确定它可以跳过检查表,如果CHECK指示不返回任何行。关于此的Postgres文档
zxq9

@avesus heh ...上面的代码本身值得这样的讽刺。通常将此类内容包装到某种维护例程中。这可以像在某种情况下执行cron作业之类的存储过程一样简单。按日期分区是很常见的,但是我发现自己也时不时通过表空间分配进行分区,这需要一些外部信息-编写分区保姆所花费的30分钟对于控制来说是值得的它给你。
zxq9 2015年

嗯 您确定它不会阻塞吗?我有类似的设置,但是当我在单个分区上运行CLUSTER命令时,对另一个分区保存的数据执行SELECT语句!
E. van Putten

37

“表继承”的含义不同于“类继承”,并且它们具有不同的用途。

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形式的方法故意降低灵活性。

关于作为一种原理性设备的继承,还有一点要指出:从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定义表,该表指向扩展数据的“下一个”表(或表示如果ilk是泛型类型本身则不存在)。针对这种模式编写表函数,视图等非常容易,以至于大多数ORM框架在诉诸OOP样式类继承来创建对象类型族时,都会在后台执行此类操作。


如果要添加所有已知的哺乳动物怎么办?您要继承哺乳动物还是像这里一样拥有外键?我使用外键的问题是您最终不得不进行许多次连接。
2015年

1
@puk首先,您需要确定为什么要添加每种已知的哺乳动物。数据的形状将取决于使用数据的方式(在这种情况下,可能不必为每个动物都拥有一张桌子-考虑一下您确实拥有各种生物的游戏兽人数据库) )。在上述情况下,我通常会添加一个视图,这是最常见的情况mammal JOIN human,只是因为每次都编写联接很烦人。但是不要避免加入。连接是将R放入RDBMS的原因。如果您不喜欢联接,则应使用其他数据库类型。
zxq9

@ zxq9:我猜由于大表导致的大规模,低效的联接在其中发挥了物化视图的作用?(我一直没有使用Postgres了)
Mark K Cowan

1
@MarkKCowan联接不是效率低下的。由于草率的设计,低效率的方法是尝试加入非索引,非唯一字段(因为架构没有接近标准化的地方)。在这些情况下,物化视图可能会有所帮助。物化视图在需要归一化数据作为逻辑示意图基础(通常为真)的情况下也很有帮助,但是还需要多个工作的,非归一化的表示形式,以便于提高处理效率(提前计算)或认知效率,更易于使用。但是,如果您写的多于读的,那是一种悲观。
zxq9

1
@MarkKCowan“慢”是一个相对术语。在我们可以接受〜50ms返回查询的大型业务系统和游戏服务器中,我的经验(无论如何在Postgres 8+中)从来都不是20个表联接的问题。但是,如果管理层希望对5个以上未索引数据(或派生值!)的表上的10b行以上的连接进行<1ms的响应……除了上个月进行此连接并将其存储起来之外,世界上没有任何系统会感到“快速”在快速的K / V存储中(本质上,物化视图可以在特殊情况下发挥作用)。在写或读时都无法避免折衷。
zxq9

6

只要您不需要在父表上创建外键,就可以在OOP范式中使用继承。例如,如果您有一个抽象类车辆存储在车辆表中,并且继承了该表车,则所有车辆都将在车辆表中可见,但该车辆表上驾驶员表的外键与这些不匹配记录。

继承也可以用作分区工具。当您有要永久增长的表(日志表等)时,此功能特别有用。


1
表约束不被继承,因此它不仅仅是外键。您可以将表约束应用于在DDL中创建的子表,也可以编写触发器来实现相同的约束。
Wexxor 2012年

3

继承的主要用途是进行分区,但有时在其他情况下也很有用。在我的数据库中,有许多只外键不同的表。我的“抽象类”表“图像”包含一个“ ID”(主键必须在每个表中)和PostGIS 2.0栅格。继承的表(例如“ site_map”或“ artifact_drawing”)具有外键列(“ site_map”的“ site_name”文本列,“ artifact_drawing”表的“ artifact_id”整数列等)以及主键和外键约束;其余的则继承自“ image”表。我怀疑将来可能需要在所有图像表中添加一个“说明”列,因此这可以为我节省很多工作,而不会造成实际问题(嗯,

编辑:另一个很好的用法:通过对未注册用户进行两表处理,其他RDBMS在处理这两个表时会遇到问题,但是在PostgreSQL中很容易-只需ONLY在不中断继承的“未注册用户”表中的数据时添加即可。


2

我对继承表的唯一经验是分配。它工作正常,但它不是PostgreSQL最复杂,最易于使用的部分。

上周,我们正在寻找相同的OOP问题,但是Hibernate存在太多问题(不喜欢我们的设置),因此我们在PostgreSQL中没有使用继承。


0

当我在表之间具有多于一对一的关系时,我会使用继承。

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

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

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


-4

尽可能少使用它。这通常意味着永远不会,它会沦落为一种创建违反关系模型的结构的方式,例如通过破坏信息原理并通过创建包而不是关系来实现。

而是将表分区与适当的关系建模(包括其他常规形式)结合使用。


4
PostgreSQL的继承功能违反了信息原理,因此违反了关系模型是不正确的。信息原理说,在关系数据库中的所有数据由数据值的关系,并代表所有查询结果再次表示为关系。(en.wikipedia.org/wiki/Relational_model)这始终是这种情况,因为所有的表继承另一个表,又是简单表。因此,也就没有所谓的“袋子”之类的东西。
罗兰

2
好吧,维基百科在关系模型方面几乎不是参考。它拒绝承认SQL违反了关系模型。袋子是没有钥匙的桌子,因为它可能具有重复项,因此不是关系。关系必须是集合。
Leandro

这不是功能本身的问题,而是功能的使用方式。如果使用uuid作为标识符,则在所有子表上都有唯一的键。
罗兰

您有一个要点,但是这里的问题是继承导致建模者忽略了关系模型。UUID不是真正的密钥,而是替代密钥。仍然必须声明自然键。
莱安德罗(Leandro)'18
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.