【问题标题】:How to optimize a 2 table query where data can be only discriminated based on both tables?如何优化只能基于两个表区分数据的 2 表查询?
【发布时间】:2019-03-04 09:17:37
【问题描述】:

我有以下2个表和数据分布:

drop table if exists line;
drop table if exists header;

create table header (header_id serial primary key, type character);
create table line (line_id serial primary key, header_id serial not null, type character, constraint line_header foreign key (header_id) references header (header_id)) ;
create index inv_type_idx on header (type);
create index line_type_idx on line (type);

insert into header (type) select case when floor(random()*2+1) = 1 then  'A' else 'B' end from generate_series(1,100000);
insert into line (header_id, type) select header_id,  case when floor(random()*10000+1) = 1 then (case when type ='A' then 'B' else 'A' end) else type end from header, generate_series(1,5);
  • header 表有 100K 行:type A 的 50% 和 B 的 50%
  • line 表有 500K 行:
    • 每个header 有5 个lines
    • 总体上typeA的行数占50%,B的行数占50%
    • 在 99.99% 的情况下,line 中的 typeheader 相同,只有 0.01% 的情况不同

数据分布:

# select h.type header_type, l.type line_type, count(*) from line l inner join header h on l.header_id = h.header_id group by 1,2 order by 1,2;
 header_type | line_type | count  
-------------+-----------+--------
 A           | A         | 250865
 A           | B         |     25
 B           | A         |     29
 B           | B         | 249081
(4 rows)

我需要获得所有lines 和type B 的header 是A。即使总量非常有限(500000 行中有25 行)我获得的计划(PostgreSQL 10)如下,在两个表中执行顺序扫描

explain
select * from line l
   inner join header h on l.header_id = h.header_id
where h.type ='A' and l.type='B';

                                QUERY PLAN                                 
---------------------------------------------------------------------------
 Hash Join  (cost=2323.29..14632.89 rows=125545 width=19)
   Hash Cond: (l.header_id = h.header_id)
   ->  Seq Scan on line l  (cost=0.00..11656.00 rows=248983 width=13)
         Filter: (type = 'B'::bpchar)
   ->  Hash  (cost=1693.00..1693.00 rows=50423 width=6)
         ->  Seq Scan on header h  (cost=0.00..1693.00 rows=50423 width=6)
               Filter: (type = 'A'::bpchar)
(7 rows)

有什么方法可以优化这种数据歧视非常高但仅在组合来自多个表的信息时的查询?

当然,作为解决方法,我可以对来自header 的信息存储在lines 中的信息进行非规范化处理,这将使该查询的性能更高。但如果可能的话,我宁愿不必这样做,因为我需要维护这些重复的信息。

alter table line add column compound_type char(2);
create index compound_idx on line (compound_type);

update line l
   set compound_type = h.type || l.type
  from header h
 where h.header_id = l.header_id;

# explain select * from line where compound_type = 'BA';
                                 QUERY PLAN                                  
-----------------------------------------------------------------------------
 Index Scan using compound_idx on line  (cost=0.42..155.58 rows=50 width=13)
   Index Cond: (compound_type = 'BA'::bpchar)
(2 rows)

【问题讨论】:

  • 我不知道有一种方法可以避免对至少一个表进行完整扫描(并在另一个表中查找相应的行)。您不知道 50% 中的 哪些 有错误的类型。相反,设计您的数据结构以仅将类型存储在标头中,并在需要时查找它。
  • @GordonLinoff 我不知道如何构建它以使其在没有数据重复的情况下具有高性能
  • 有了 triggers 我觉得你的 compound_type 足够了。由于存在一些冗余,布尔列 types_differing = h.type != l.type 可能是更好的样式,尽管您需要检查两列。 或者如果有时间戳,您还可以有一个行表,其中包含在某个时间点发现的不同行,并根据需要更新该表。需要时间戳索引。
  • 您的索引不会有太大帮助,因为它们的选择性大约为 50%,因此规划者很可能永远不会使用它们。

标签: sql postgresql performance query-performance


【解决方案1】:

1) 您可以使用具有适当索引的物化视图。它可以在“背景”中更新。否则,它类似于您的组合索引。

2) 如果您在 (line.header_id, line.type) 上创建索引并像这样强制子查询,则可以将搜索反转为 header-to-line:

select header_id 
from header h 
where type='A' and 
    exists(select * from line l where l.header_id=h.header_id and l.type='B')

获得所有标题后,再次选择具有相应 header_id 的行。

在某些标头索引中包含类型可能是个好主意,以便查找所需的所有内容都需要 2 个索引。

它仍然会在标题索引中读取约 50K 行,并在第二个索引中查找每一行。一般情况下它是无效的,但如果索引完全适合内存,它可能不会那么糟糕。

【讨论】:

  • 1.物化视图是一种数据非规范化或重复的方式,这是我想要避免的,在这种情况下,我认为它并不比通过触发器更新的 compound_type 更好。 2. 在该查询的计划中,我仍然看到 2 个 seq 扫描(每个表中一个)
猜你喜欢
  • 2011-10-28
  • 1970-01-01
  • 2012-09-24
  • 2021-12-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-09
  • 2017-12-14
相关资源
最近更新 更多