Postgres视图条件下推对加入不起作用

时间:2018-05-28 08:04:53

标签: sql postgresql query-optimization query-performance

我观察到奇怪的postgres行为并坚持正确的查询优化。

结构和测试数据:

CREATE table t_base(
    id serial PRIMARY KEY,
    value text
);    

SELECT *
FROM t_base;

CREATE TABLE t1 (
    id serial PRIMARY KEY,
    base_id int REFERENCES t_base(id),
    value text
);

CREATE TABLE t2 (
    id serial PRIMARY KEY,
    base_id int REFERENCES t_base(id),
    value text
);

CREATE VIEW v_all AS
    SELECT
        id, base_id, value, 't1' as tname
    FROM t1
    UNION ALL
    SELECT
        id, base_id, value, 't2' as tname
    FROM t2;

CREATE TABLE t_data (
    tname text,
    t_id int
);

INSERT INTO t_base (value)
SELECT 'val' || i FROM generate_series(1, 100000) s(i);

INSERT INTO t1 (base_id, value)
SELECT i, 't1_val' || i FROM generate_series(1, 50000) s(i);

INSERT INTO t2 (base_id, value)
SELECT i, 't2_val' || i FROM generate_series(50001, 100000) s(i);

INSERT INTO t_data VALUES ('t1', 1), ('t1', 4);

INSERT INTO t_data
SELECT 't1', (random()*100)::int
FROM generate_series(1, 3000) s(i);

VACUUM ANALYZE VERBOSE t_base;
VACUUM ANALYZE VERBOSE t1;
VACUUM ANALYZE VERBOSE t2;
VACUUM ANALYZE VERBOSE t_data;

在这种情况下简化了视图v_all,实际上我有9个表,其中大多数都有很多行。

现在我尝试查询它:

EXPLAIN ANALYZE
SELECT *
FROM v_all
WHERE tname = 't1' and id = 2;

QUERY PLAN                                                                                                        
----------------------------------- 
 Append  (cost=0.29..8.31 rows=1 width=51) (actual time=0.056..0.058 rows=1 loops=1)                               
   ->  Index Scan using t1_pkey on t1  (cost=0.29..8.31 rows=1 width=51) (actual time=0.055..0.056 rows=1 loops=1) 
         Index Cond: (id = 2)                                                                                      
 Planning time: 0.264 ms                                                                                           
 Execution time: 0.103 ms

出色!正是我想要的:

  1. 仅扫描表t1
  2. 使用指数
  3. 现在我希望通过加入完成同样的事情:

    EXPLAIN ANALYZE
    SELECT *
    FROM t_data d
        JOIN v_all v ON (v.tname = d.tname AND v.id = d.t_id);
    
     QUERY PLAN                                                                                                                 
     ------------------------------------------------------------------
     Nested Loop  (cost=0.29..3427.50 rows=3840 width=58) (actual time=0.058..15.649 rows=2986 loops=1)                         
       ->  Seq Scan on t_data d  (cost=0.00..44.00 rows=3000 width=7) (actual time=0.023..0.727 rows=3000 loops=1)              
       ->  Append  (cost=0.29..1.11 rows=2 width=51) (actual time=0.003..0.004 rows=1 loops=3000)                               
             ->  Index Scan using t1_pkey on t1  (cost=0.29..0.92 rows=1 width=51) (actual time=0.002..0.003 rows=1 loops=3000) 
                   Index Cond: (id = d.t_id)                                                                                    
                   Filter: (d.tname = 't1'::text)                                                                               
             ->  Index Scan using t2_pkey on t2  (cost=0.15..0.19 rows=1 width=72) (actual time=0.001..0.001 rows=0 loops=3000) 
                   Index Cond: (id = d.t_id)                                                                                    
                   Filter: (d.tname = 't2'::text)                                                                               
     Planning time: 0.626 ms                                                                                                    
     Execution time: 16.095 ms 
    

    结果并不是我希望同时扫描t1t2个表来搜索ID,而不是仅仅跳过那个" branch"通过常数而不是Filter: (d.tname = 't1'::text)

    我尝试了postgres 10.3和9.4:

    select version();
    PostgreSQL 10.3 (Debian 10.3-1.pgdg90+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 6.3.0-18+deb9u1) 6.3.0 20170516, 64-bit
    

    是否有一些技巧可以教导postgres仅使用UNION ALL子句查看JOINs首先检查常量条件?

    SqlFiddle

1 个答案:

答案 0 :(得分:0)

请记住PostgreSQL在5次执行后“修复”执行计划。

这意味着,如果在第五次执行后错误或不完整,则不会在之后修复它 - 直到重新初始化它为止。

  • 在第一个示例中,PostgreSQL避免使用第二个表,因为您使用了常量值。有零的机会它需要另一张桌子。 PostgreSQL 可以立即将计划固定到这种情况,因为根本不需要扫描其他表格。

  • 然而,在第二种情况下,PostgreSQL无法修复计划并排除第二个表永远。现在删除它可能是合理的,但明天或后一周呢? PostgreSQL无法冒险修复适合当前值的执行计划......未来可能会出错。这就是为什么它“保持其选择开放”,用简单的英语说。

无论如何,这样的计划并不昂贵。它只是一个索引扫描,将在任何时间(今天)执行。如果下周你为“t2”添加值,那么它会很有意义,并且会花费必要的时间来适当地执行。