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和使用计划器成本值。
有没有人有任何提示,在这种情况下,可以将哪些值设置为更高/更低/开/关以强制执行更好的刨床行为?
来自评论的反馈:
character varying为text,结果相同。ao我朋友的分析输出上显示 width=17,但对我来说 width=40。这更像是一种预感,因为我不知道优化器的详细信息和可能的重写,但FULL JOIN非常受限制。
所以,你可以通过分割重写视图FULL JOIN到UNION 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)