按主键查找一行,当你不知道它在哪个表中时?

Seb*_*iot 2 performance postgresql-performance

我是负责专有 OODBMS 的(非正式)DBA。管理层希望我们迁移到 Postgres,以降低许可成本。移动应该是渐进的,因此我们也应该保持与 OODBMS 中相同的数据结构。幸运的是,有了对数组和表继承的支持,我们可以在 Postgres 中创建完全相同的模式。所有表都将使用 bigint 作为主键,并且所有(间接)从同一个基表继承。

最大的问题是:我们的 bigint 键在所有表中(并且必须)是唯一的,我们必须能够根据主键快速加载一组行,而无需知道它们在哪个表中。行将分布在任何和所有表上。这里主要关注的是速度,而不是磁盘或内存使用。

换句话说,我们需要的是一个跨所有表的唯一索引。AFAIK,这在 Postgres 中是不可能的。什么是“次优”选项?我对任何解决方案持开放态度,包括使用甚至编码一些“Postgres 扩展”。

为了给出有关实际 DB 的一些提示,我们谈论的是 300 个表、130M 行和大约 300GB 的大小(OODBMS 大小)。

a_h*_*ame 5

所有表都将使用 bigint 作为主键,并且所有(间接)继承自同一个基表

我不确定我是否喜欢“所有表都继承自基表”,但鉴于此,Postgres 听起来是可行的:

要为所有表生成主键,请创建一个序列:

create sequence one_for_all as bigint;
Run Code Online (Sandbox Code Playgroud)

使用该序列创建基表以生成值:

create table base (id integer primary key default nextval('one_for_all'));
Run Code Online (Sandbox Code Playgroud)

请注意,不会对子表强制执行主键!

然后创建子表:

create table t1 (t1_data integer, primary key (id)) inherits (base);
create table t2 (t2_data integer, primary key (id)) inherits (base);
Run Code Online (Sandbox Code Playgroud)

如果您现在插入到子表中,该序列将用于生成 ID:

insert into t1 (t1_data) values (100);
insert into t2 (t2_data) values (200);
Run Code Online (Sandbox Code Playgroud)

要查找行位于哪个表中,请从基表中选择并包括tableoid列,该列标识该行所在的实际表:

select b.*, tableoid::regclass as actual_table
from base b
where id = 42;
Run Code Online (Sandbox Code Playgroud)

您仍然需要另一个查询来返回子表的完整行。

在线示例:https : //dbfiddle.uk/?rdbms=postgres_11&fiddle=4305e86b996e7a94c24faed733257232


另一种不需要继承的方法是生成对表名进行编码的 ID 值(不寒而栗)。

沿线的东西:

create sequence one_for_all as bigint;
Run Code Online (Sandbox Code Playgroud)

通过将序列中的值乘以 1000,我们基本上可以使用较低的 3 位数字来为每个表编码一个唯一的数字。为了能够查找这些数字,我们需要额外的表格。

请注意,如果查找表不包含所有表,这将失败!

然后,而不是使用nextval()use,get_id()函数:

create table base (id integer primary key default nextval('one_for_all'));
Run Code Online (Sandbox Code Playgroud)

然后你可以这样做:

insert into t1 (some_value) values (42);
insert into t2 (some_data) values ('foo');
insert into t1 (some_value) values (117);
insert into t2 (some_data) values ('bar');
Run Code Online (Sandbox Code Playgroud)

所以 t2 中的行现在转到 ID 2003 和 4003。该函数get_tablename()可用于检索 ID 所属的表名:

create table t1 (t1_data integer, primary key (id)) inherits (base);
create table t2 (t2_data integer, primary key (id)) inherits (base);
Run Code Online (Sandbox Code Playgroud)

这在查找表名方面肯定更快,并且不会携带巨大的继承树的包袱。所以在性能方面它可能会更快。然而,这将成为维护的噩梦。

每当创建或删除表时,查找表的填充也许可以通过事件触发器来完成。


如果您可以更改您的应用程序以支持 varchar 主键,而不是 bigint(并且您可以接受稍微更高的存储要求),您还可以将 ID 生成为仅包含表名的字符串:

create function get_id(p_tablename text)
  returns text
as
$$
  select concat(nextval('one_for_all'), '_', p_tablename)
$$
language sql;
Run Code Online (Sandbox Code Playgroud)

不过,排序和范围查询之类的事情会很复杂。