PostgreSQL 引用多个表最有效的方法

Gui*_*iik 7 postgresql performance database-design postgresql-9.6 postgresql-11 postgresql-performance

当一次只能进行一个引用时,我正在寻找在一个表中引用多个表的最有效方法。这意味着表 A 和 B 被表 C 引用,但 A 和 B 不能在 C 的一行中引用,并且我在编写查询时选择要查看的表。
我考虑过 4 种方法:

  • 有一个列type和一个列FK,这样我就可以进行像这样的连接type = 'A' AND a.pk = c.a_fk(这就是我们现在使用的)
  • 使用继承:A 和 B 都从“父”表继承序列,并且 C 具有“父”表的外键,但这在 PostgreSQL 中是不可能的
  • 使用与继承相同的原理,但使用另一个表 D。A 和 B 都有对 D 的引用,C 也有对 D 的引用,我可以像这样进行连接FROM c JOIN a ON a.d_fk = c.d_fk
  • 使用按表列我想要一个外键

在我尝试的每个解决方案中,查询规划器对于将返回多少行的判断都是错误的。
这是一个例子:我创建了一个简单的数据库,其中有 3 个表,如下所示:

                            Table "public.a"
 Column |  Type  | Collation | Nullable |            Default            
--------+--------+-----------+----------+-------------------------------
 pk     | bigint |           | not null | nextval('a_pk_seq'::regclass)

                            Table "public.b"
 Column |  Type  | Collation | Nullable |            Default            
--------+--------+-----------+----------+-------------------------------
 pk     | bigint |           | not null | nextval('b_pk_seq'::regclass)

                           Table "public.c"
 Column |  Type  | Collation | Nullable |            Default            
--------+--------+-----------+----------+-------------------------------
 pk     | bigint |           | not null | nextval('c_pk_seq'::regclass)
 a_fk   | bigint |           |          | 
 b_fk   | bigint |           |          | 
Indexes:
    "c_pkey" PRIMARY KEY, btree (pk)
    "c_a_fk_idx" btree (a_fk)
    "c_b_fk_idx" btree (b_fk)
Foreign-key constraints:
    "c_a_fk_fkey" FOREIGN KEY (a_fk) REFERENCES a(pk)
    "c_b_fk_fkey" FOREIGN KEY (b_fk) REFERENCES b(pk)
Run Code Online (Sandbox Code Playgroud)
  • A 填充 20 行,B 填充 10000。
  • C也填10000,a_fk和b_fk不能同时填。除了填充 a_fk 的 5 行(也必须是唯一的)之外,所有行都用 b_fk 填充(它必须是唯一的)。

所以当我运行这个查询时:

SELECT * FROM a JOIN c ON a.pk = c.a_fk;
Run Code Online (Sandbox Code Playgroud)

我不希望规划者认为查询将返回 5 行。
相反,这就是我得到的:

 Merge Join  (cost=1.76..1.94 rows=10000 width=32) (actual time=0.035..0.051 rows=5 loops=1)
   Merge Cond: (a.pk = c.a_fk)
   ->  Sort  (cost=1.63..1.68 rows=20 width=8) (actual time=0.024..0.028 rows=15 loops=1)
         Sort Key: a.pk
         Sort Method: quicksort  Memory: 25kB
         ->  Seq Scan on a  (cost=0.00..1.20 rows=20 width=8) (actual time=0.009..0.013 rows=20 loops=1)
   ->  Index Scan using c_a_fk_idx1 on c  (cost=0.13..24.21 rows=10000 width=24) (actual time=0.006..0.012 rows=5 loops=1)
 Planning time: 0.297 ms
 Execution time: 0.088 ms
Run Code Online (Sandbox Code Playgroud)

我尝试创建部分索引CREATE UNIQUE INDEX ON c (b_fk) WHERE a_fk IS NULL,但它没有改变任何内容。

唯一改进该计划的是在 b_fk 上添加 NULL 检查,如下所示:

SELECT * FROM a JOIN c ON a.pk = c.a_fk WHERE c.a_fk IS NOT NULL;
Run Code Online (Sandbox Code Playgroud)

这给出了这个计划:

 Merge Join  (cost=1.92..6.05 rows=5 width=32) (actual time=0.031..0.046 rows=5 loops=1)
   Merge Cond: (c.a_fk = a.pk)
   ->  Index Scan using c_a_fk_idx on c  (cost=0.29..20.37 rows=5 width=24) (actual time=0.005..0.012 rows=5 loops=1)
         Index Cond: (a_fk IS NOT NULL)
   ->  Sort  (cost=1.63..1.68 rows=20 width=8) (actual time=0.022..0.024 rows=15 loops=1)
         Sort Key: a.pk
         Sort Method: quicksort  Memory: 25kB
         ->  Seq Scan on a  (cost=0.00..1.20 rows=20 width=8) (actual time=0.006..0.007 rows=20 loops=1)
 Planning time: 0.559 ms
 Execution time: 0.086 ms
Run Code Online (Sandbox Code Playgroud)

但它仍然对 A 中的行出错,我认为必须有另一种方法来帮助查询规划器,这种方法不涉及为每个应该为 NULL 的列添加“无用”的 NULL 检查。

我尝试了 Postgres 9.6 和 11,我更喜欢 9.6 的解决方案,因为这是我们使用的,但我会接受 11 的解决方案。

这是使用“类型”解决方案的现实生活示例:

SELECT *
  FROM BinDescription b
  JOIN RobotDescription r
    ON b.physicalContainerType = 'ROBOT'
   AND b.physicalContainerFK = r.robotPK
  JOIN RobotMissionDescription rm
    ON rm.robotFK = r.robotPK
;
Run Code Online (Sandbox Code Playgroud)

这给了我这个计划:

 Merge Join  (cost=225.12..395.31 rows=12026 width=301) (actual time=4.372..67.206 rows=47293 loops=1)
   Merge Cond: (b.physicalcontainerfk = r.robotpk)
   ->  Nested Loop  (cost=222.91..15184.24 rows=88 width=152) (actual time=4.321..44.448 rows=47293 loops=1)
         ->  Index Scan using bindescription_physicalcontainertype_physicalcontainerfk_key on bindescription b  (cost=0.29..13.66 rows=4 width=76) (actual time=0.011..0.017 rows=4 loops=1)
               Index Cond: (physicalcontainertype = 'ROBOT'::physicalcontainertype)
         ->  Bitmap Heap Scan on robotmissiondescription rm  (cost=222.62..3672.39 rows=12026 width=76) (actual time=2.431..7.985 rows=11823 loops=4)
               Recheck Cond: (robotfk = b.physicalcontainerfk)
               Heap Blocks: exact=14246
               ->  Bitmap Index Scan on idx_robotmissiondescription_robotfk  (cost=0.00..219.62 rows=12026 width=0) (actual time=1.629..1.629 rows=11823 loops=4)
                     Index Cond: (robotfk = b.physicalcontainerfk)
   ->  Sort  (cost=2.20..2.29 rows=34 width=149) (actual time=0.044..4.752 rows=47321 loops=1)
         Sort Key: r.robotpk
         Sort Method: quicksort  Memory: 34kB
         ->  Seq Scan on robotdescription r  (cost=0.00..1.34 rows=34 width=149) (actual time=0.007..0.014 rows=34 loops=1)
 Planning time: 1.559 ms
 Execution time: 69.480 ms
Run Code Online (Sandbox Code Playgroud)

Nei*_*gan 1

A 和 B 都从“父”表继承序列,并且 C 具有“父”表的外键,但这在 PostgreSQL 中是不可能的

这对于任何 SQL 数据库都是可能的。为什么你认为 PostgreSQL 不可能?

您可能会将 SQL 表继承与 PostgreSQL 的“表继承”混淆,它们是不同的东西

以下是如何在 SQL 中执行此操作。这称为类表继承:

create table parties (
  party_id serial primary key
);

create table individuals (
  party_id int primary key references parties(party_id),
  given_name text,
  ...
);

create table organizations (
  party_id int primary key references parties(party_id),
  organization_name text,
  ...
);

create table sales_orders (
  order_id serial primary key,
  customer_id int references parties(party_id),
  ...
);
Run Code Online (Sandbox Code Playgroud)

如果您想要更快的性能,请使用单表继承:

create table parties (
  party_id serial primary key,
  party_type text check(party_type in ('Individual','Organization')),
  given_name text null,
  organization_name text null,
  ...
  /* add some check constraints for party_type vs values for individuals/orgs */
);
Run Code Online (Sandbox Code Playgroud)

另外,如果你给出真实的例子而不是抽象的“A”和“B”,那么帮助你会容易100倍