通過主鍵查找一行,當你不知道它在哪個表中時?
我是負責專有 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;
不過,排序和範圍查詢之類的事情會很複雜。