如果检查“为空”,则选择视图时性能不佳

Xsa*_*san 3 postgresql performance view postgresql-9.3 postgresql-performance

我有两个非常简单的表,以及在bigint字段上加入它们的视图:

CREATE TABLE foo.ao (
  id serial NOT NULL,
  ao_id bigint NOT NULL,
  ao_plz text,
  ao_community text,
  ao_street text,
  ao_ms_id text,
  ao_status text DEFAULT 'Standard'::character varying,
  ao_has_eear boolean,
  ao_last_update timestamp without time zone,
  CONSTRAINT ao_pkey PRIMARY KEY (id),
  CONSTRAINT ao_id_unique UNIQUE (ao_id)
);

CREATE TABLE foo.ms (
  id serial NOT NULL,
  ms_nis_number text NOT NULL,
  ms_plz text,
  ms_community text,
  ms_street text,
  ms_status text DEFAULT 'Standard'::character varying,
  ms_coord_x integer,
  ms_coord_y integer,
  ms_ao_id bigint,
  CONSTRAINT ms_pkey PRIMARY KEY (id),
  CONSTRAINT nis_number_unique UNIQUE (ms_nis_number)
)

CREATE OR REPLACE VIEW foo.ao_ms AS 
 SELECT ao.ao_id, 
    ao.ao_plz, 
    ao.ao_community, 
    ao.ao_street, 
    ao.ao_last_update, 
    ao.ao_ms_id, 
    ao.ao_status, 
    ao.ao_has_eear, 
    ms.ms_nis_number, 
    ms.ms_plz, 
    ms.ms_community, 
    ms.ms_street, 
    ms.ms_status, 
    ms.ms_coord_x, 
    ms.ms_coord_y, 
    ms.ms_ao_id
   FROM foo.ao
   FULL JOIN foo.ms ON ao.ao_id = ms.ms_ao_id;
Run Code Online (Sandbox Code Playgroud)

视图上的查询非常快 - 直到我检查特定字段is null,例如:

select count(*) from foo.ao_ms where ao_ms_id is null
Run Code Online (Sandbox Code Playgroud)

事实上,查询永远不会结束,我必须杀死 Postgres 进程,因为它消耗我的 CPU 和内存并且永远不会释放资源。

这是查询计划:

Aggregate  (cost=15699.31..15699.32 rows=1 width=0)
  ->  Hash Full Join  (cost=6467.13..15697.24 rows=829 width=0)
        Hash Cond: (ao.ao_id = ms.ms_ao_id)
        Filter: (ao.ao_ms_id IS NULL)
        ->  Seq Scan on ao  (cost=0.00..3664.65 rows=162465 width=40)
        ->  Hash  (cost=3747.39..3747.39 rows=165739 width=8)
              ->  Seq Scan on ms  (cost=0.00..3747.39 rows=165739 width=8)
Run Code Online (Sandbox Code Playgroud)

有趣的事实是;我的两个朋友导入了我的数据库。在一台机器上,它的行为完全相同,但在第三台机器上,执行在 200 毫秒内完成。在那里,查询计划如下所示:

Aggregate  (cost=15916.96..15916.97 rows=1 width=0)"
  ->  Hash Full Join  (cost=6647.46..15751.46 rows=66202 width=0)
        Hash Cond: (ms.ms_ao_id = ao.ao_id)
        Filter: (ao.ao_ms_id IS NULL)
        ->  Seq Scan on ms  (cost=0.00..3748.39 rows=165739 width=8)
        ->  Hash  (cost=3664.65..3664.65 rows=162465 width=17)
              ->  Seq Scan on ao  (cost=0.00..3664.65 rows=162465 width=17)
Run Code Online (Sandbox Code Playgroud)

然后我重新编写了我的视图以更改连接的两个表的顺序:

...
FROM foo.ms
   FULL JOIN foo.ao ON ms.ms_ao_id = ao.ao_id;
Run Code Online (Sandbox Code Playgroud)

但这没有效果。我已经实现了一些索引,将text字段的数据类型更改为character varying,并使用select count(1)代替(*),但性能仍然很差。顺便说一下,该ao_ms_id字段的值类似于XYZ1234567.

我们都安装了 PostgreSQL 9.3,那么为什么只在一台机器上执行更好的查询计划,而在另外两台机器上执行效率低下的计划呢?以及如何强制 Postgres 使用更快的查询计划?或者我是否以错误的方式设置了视图?

编辑:以下语句在几毫秒内完成,因此我认为它与is null语句有关(not null不会导致问题):

select count(*) from foo.ao_ms where ao_ms_id is not null
select count(*) from foo.ao_ms where ao_ms_id like ''
Run Code Online (Sandbox Code Playgroud)

更新: 该问题最初是通过将 Postgres 升级到 9.4 版来解决的,但现在又出现了。还是一样:在一台机器(生产系统)上,查询导致进程挂起,而在其他机器上它就像一个魅力。

当 I 时SET enable_seqscan = OFF,视图上所有有问题的查询的运行速度与在我的本地机器上一样快。当我执行以下查询时,查询计划看起来像这样,seqscan“已禁用”(这会导致有问题的机器上的挂起过程):

EXPLAIN ANALYZE
SELECT count(*) FROM foo.ao_ms 
   WHERE ( CAST(ao_ms_id AS TEXT) ilike '' or ao_ms_id is null )
Run Code Online (Sandbox Code Playgroud)

查询计划:

Aggregate  (cost=24927.24..24927.25 rows=1 width=0) (actual time=220.543..220.543 rows=1 loops=1)
  ->  Merge Full Join  (cost=0.84..24801.33 rows=50366 width=0) (actual time=0.030..214.889 rows=100919 loops=1)
        Merge Cond: (ao.ao_id = ms.ms_ao_id)
        Filter: (((ao.ao_ms_id)::text ~~* ''::text) OR (ao.ao_ms_id IS NULL))
        Rows Removed by Filter: 117809
        ->  Index Scan using idx_ao_id on ao  (cost=0.42..9559.38 rows=166434 width=17) (actual time=0.016..36.979 rows=166434 loops=1)
        ->  Index Only Scan using fki_ao_id on ms  (cost=0.42..12951.07 rows=170114 width=8) (actual time=0.011..48.784 rows=170114 loops=1)
              Heap Fetches: 170114
Run Code Online (Sandbox Code Playgroud)

我知道,正如Postgres 文档中所述,这不应该是一个永久的解决方案。所以我也将检查其他选项,比如default_statistics_target和使用计划器成本值。

有没有人有任何提示,在这种情况下,可以将哪些值设置为更高/更低/开/关以强制执行更好的刨床行为?

来自评论的反馈:

  • 预期行数和实际行数之间没有差异,或者只有很小的差异
  • 需要完全连接,或者至少它使客户端编程更容易,我在其中显示了两个网格,它们应该在与连接匹配的记录上“可锁定”。
  • 我在每一列上创建了 btree 索引以获得更好的性能,但它没有帮助。我还将列从 更改character varyingtext,结果相同。
  • 我不明白为什么 seq 扫描在ao我朋友的分析输出上显示 width=17,但对我来说 width=40。
  • 一张表在磁盘上为 28MB,另一张为 29MB。该值在真空后不会改变。

ype*_*eᵀᴹ 6

这更像是一种预感,因为我不知道优化器的详细信息和可能的重写,但FULL JOIN非常受限制。

所以,你可以通过分割重写视图FULL JOINUNION ALL2联接:

SELECT ... 
FROM ao LEFT JOIN ms 
    ON ao.ao_id = ms.ms_ao_id 

UNION ALL 

SELECT ... 
FROM ao RIGHT JOIN ms 
    ON ao.ao_id = ms.ms_ao_id 
WHERE ao.ao_id IS NULL ;
Run Code Online (Sandbox Code Playgroud)

这可能有助于优化器确定对于这种情况(where ao_ms_id is null),空值可以通过两种不同的方式产生,一种来自表本身,另一种作为外连接的副产品。因此,可以在第一部分将条件下推,union并在第二部分完全删除条件。

如果这不起作用,您可以count(*)自己“简化”查询:

SELECT 
  ( SELECT COUNT(*)
    FROM ao LEFT JOIN ms 
        ON ao.ao_id = ms.ms_ao_id 
    WHERE ao.ao_ms_id IS NULL
  )
    +
  ( SELECT COUNT(*)
    FROM ao RIGHT JOIN ms 
        ON ao.ao_id = ms.ms_ao_id 
    WHERE ao.ao_id IS NULL 
  --  AND ao.ao_ms_id IS NULL           -- removed
  ) ;
Run Code Online (Sandbox Code Playgroud)