Performance

通過主鍵查找一行,當你不知道它在哪個表中時?

  • October 8, 2019

我是負責專有 OODBMS 的(非官方)DBA。管理層希望我們遷移到 Postgres,以降低許可成本。移動應該是漸進的,因此我們還應該保持與 OODBMS 中相同的資料結構。幸運的是,通過對 ARRAY 和表繼承的支持,我們可以在 Postgres 中創建完全相同的模式。所有表都將使用 bigint 作為主鍵,並且所有(間接)都從同一個基表繼承。

最大的問題是:我們的 bigint 鍵在所有表中都是(並且必須是)唯一的,並且我們必須能夠根據它們的主鍵快速載入一組行,而無需知道它們在哪個表中。這些行將分佈在任何和所有表中。這裡主要關心的是速度,而不是磁碟或記憶體的使用。

換一種說法,我們需要的是跨所有表的唯一索引。AFAIK,這在 Postgres 中是不可能的。“下一個最佳”選項是什麼?我對任何解決方案持開放態度,包括使用或什至編碼一些“Postgres 擴展”。

為了給實際數據庫一些提示,我們談論的是 300 個表、130M 行和大約 300GB 的大小(OODBMS 大小)。

所有表都將使用 bigint 作為主鍵,並且所有(間接)都從同一個基表繼承

我不確定我是否喜歡這種“所有表都從基表繼承”,但考慮到這一點,使用 Postgres 聽起來是可行的:

要為所有表生成主鍵,請創建一個序列:

create sequence one_for_all as bigint;

使用該序列創建基表以生成值:

create table base (id integer primary key default nextval('one_for_all'));

請注意,不會對子表強制執行主鍵!

然後創建子表:

create table t1 (t1_data integer, primary key (id)) inherits (base);
create table t2 (t2_data integer, primary key (id)) inherits (base);

如果您現在插入到子表中,序列將用於生成 ID:

insert into t1 (t1_data) values (100);
insert into t2 (t2_data) values (200);

要查找行所在的表,請從基表中選擇並包含tableoid列,該列標識行所在的實際表:

select b.*, tableoid::regclass as actual_table
from base b
where id = 42;

您仍然需要另一個查詢來返回子表的整行。

線上範例:https ://dbfiddle.uk/?rdbms=postgres_11&fiddle=4305e86b996e7a94c24faed733257232


另一種不需要繼承的方法是生成對錶名進行編碼的 ID 值(不寒而栗)。

類似的東西:

create sequence one_for_all as bigint;
create table lookup (table_name text primary key, code serial unique);
insert into lookup (table_name) values ('base'), ('t1'), ('t2');

create function get_id(p_tablename text)
 returns bigint
as
$$
 select nextval('one_for_all') * 1000 + code
 from lookup
 where table_name = p_tablename;
$$
language sql;

create function get_tablename(p_id bigint)
 returns text
as
$$
 select table_name
 from lookup
 where code = p_id % 1000;  
$$
language sql;

通過將序列中的值乘以 1000,我們基本上可以使用低 3 位來為每個表編碼一個唯一的數字。為了能夠查找這些數字,我們需要那個附加表。

請注意,如果查找表不包含所有表,這將失敗!

然後,而不是使用nextval()使用,get_id()功能:

create table base (id integer primary key default get_id('base'));
create table t1 (id integer primary key default get_id('t1'), some_value integer);
create table t2 (id integer primary key default get_id('t2'), some_data text);

然後你可以這樣做:

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');

因此 t2 中的行現在轉到 ID 2003 和 4003。該函式get_tablename()可用於檢索 ID 所屬的表名:

select get_tablename(2003);

get_tablename
-------------
t2           

就查找表名而言,這肯定更快,並且不會隨身攜帶巨大的繼承樹的包袱。所以在性能方面它可能更快。然而,這將成為維護的噩夢。

每當創建或刪除表時,查找表的填充也許可以通過事件觸發器來完成。


如果您可以更改您的應用程序以支持 varchar 主鍵,而不是 bigint(並且您可以忍受稍高的儲存要求),您還可以將 ID 生成為僅包含表名的字元串:

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

不過,排序和範圍查詢之類的事情會很複雜。

引用自:https://dba.stackexchange.com/questions/250518