Hi experts,
One of our customers has complained of the performance of a query that should be fast because one of the tables was empty:
select count(*) from empty_table c, big_table b
WHERE c.id1 = b.id1
AND c.id2 = b.id2
AND c.id3 = b.id3
AND c.col = 354828;
the query takes 27 seconds (join without where clause takes 50ms)
Looking at the query plan, Vertica was choosing the empty table in the outer part of the join and the big one in the Inner part, I noticed that this only happens when 1- there is a predicate on the empty table and 2- there is a primary key defined on the big table (on which we do the join).
I managed to reproduce this behavior with a simple example (below ), does anyone know why Vertica doesn't make an optimal choice in this case? or should I ask the customer to open a support case?
My example:
CREATE TABLE public.should_be_outer
(
id IDENTITY ,
col1 char(20),
col2 int,
col3 int,
col4 char(12)
);
ALTER TABLE public.should_be_outer ADD CONSTRAINT C_PRIMARY PRIMARY KEY (id) ENABLED;
CREATE TABLE public.should_be_inner
(
id IDENTITY ,
val int
);
Query: takes 1474.912ms
dbadmin=> select count(*) from should_be_inner a JOIN should_be_outer b
dbadmin-> ON a.id=b.id
dbadmin-> WHERE a.val=1;
count
-------
0
(1 row)
Time: First fetch (1 row): 1474.885 ms. All rows formatted: 1474.912 ms
Explain plan:
explain select count(*) from should_be_inner a JOIN should_be_outer b ON a.id=b.id WHERE a.val=1; Access Path: +-GROUPBY NOTHING [Cost: 47K, Rows: 1 (PREDICATE VALUE OUT-OF-RANGE)] (PATH ID: 1) | Aggregates: count(*) | Execute on: All Nodes | +---> JOIN HASH [Cost: 47K, Rows: 1 (PREDICATE VALUE OUT-OF-RANGE)] (PATH ID: 2) Outer (BROADCAST)(LOCAL ROUND ROBIN) | | Join Cond: (a.id = b.id) | | Execute on: All Nodes | | +-- Outer -> STORAGE ACCESS for a [Cost: 22, Rows: 1 (PREDICATE VALUE OUT-OF-RANGE)] (PATH ID: 3) | | | Projection: public.should_be_inner_b0 | | | Materialize: a.id | | | Filter: (a.val = 1) | | | Execute on: All Nodes | | +-- Inner -> STORAGE ACCESS for b [Cost: 28K, Rows: 48M] (PATH ID: 4) | | | Projection: public.should_be_outer_b0 | | | Materialize: b.id | | | Execute on: All Nodes
When doing just a join without a where clause:
explain select count(*) from should_be_inner a JOIN should_be_outer b ON a.id=b.id ; Access Path: +-GROUPBY NOTHING [Cost: 33K, Rows: 1] (PATH ID: 1) | Aggregates: count(*) | Execute on: All Nodes | +---> JOIN HASH [Cost: 33K, Rows: 10] (PATH ID: 2) Inner (BROADCAST) | | Join Cond: (a.id = b.id) | | Execute on: All Nodes | | +-- Outer -> STORAGE ACCESS for b [Cost: 28K, Rows: 48M] (PATH ID: 3) | | | Projection: public.should_be_outer_b0 | | | Materialize: b.id | | | Execute on: All Nodes | | | Runtime Filter: (SIP1(HashJoin): b.id) | | +-- Inner -> STORAGE ACCESS for a [Cost: 17, Rows: 10] (PUSHED GROUPING) (PATH ID: 4) | | | Projection: public.should_be_inner_b0 | | | Materialize: a.id | | | Execute on: All Nodes
When forcing the join order on the query: 276.077 ms
dbadmin=> select /*+syntactic_join*/ count(*) from should_be_outer b JOIN should_be_inner a
dbadmin-> ON a.id=b.id
dbadmin-> WHERE a.val=1;
count
-------
0
(1 row)
Time: First fetch (1 row): 276.034 ms. All rows formatted: 276.077 ms
Access Path:
+-GROUPBY NOTHING [Cost: 33K, Rows: 1 (PREDICATE VALUE OUT-OF-RANGE)] (PATH ID: 1)
| Aggregates: count(*)
| Execute on: All Nodes
| +---> JOIN HASH [Cost: 33K, Rows: 1 (PREDICATE VALUE OUT-OF-RANGE)] (PATH ID: 2) Inner (BROADCAST)
| | Join Cond: (a.id = b.id)
| | Execute on: All Nodes
| | +-- Outer -> STORAGE ACCESS for b [Cost: 28K, Rows: 48M] (PATH ID: 3)
| | | Projection: public.should_be_outer_b0
| | | Materialize: b.id
| | | Execute on: All Nodes
| | | Runtime Filter: (SIP1(HashJoin): b.id)
| | +-- Inner -> STORAGE ACCESS for a [Cost: 22, Rows: 1 (PREDICATE VALUE OUT-OF-RANGE)] (PUSHED GROUPING) (PATH ID: 4)
| | | Projection: public.should_be_inner_b0
| | | Materialize: a.id
| | | Filter: (a.val = 1)
| | | Execute on: All Nodes
When I drop the primary key:
dbadmin=> select count(*) from should_be_inner a JOIN should_be_outer b
dbadmin-> ON a.id=b.id
dbadmin-> WHERE a.val=1;
count
-------
0
(1 row)
Time: First fetch (1 row): 265.322 ms. All rows formatted: 265.349 ms
explain select count(*) from should_be_inner a JOIN should_be_outer b
ON a.id=b.id
WHERE a.val=1;
Access Path:
+-GROUPBY NOTHING [Cost: 33K, Rows: 1 (PREDICATE VALUE OUT-OF-RANGE)] (PATH ID: 1)
| Aggregates: count(*)
| Execute on: All Nodes
| +---> JOIN HASH [Cost: 33K, Rows: 2 (PREDICATE VALUE OUT-OF-RANGE)] (PATH ID: 2) Inner (BROADCAST)
| | Join Cond: (a.id = b.id)
| | Execute on: All Nodes
| | +-- Outer -> STORAGE ACCESS for b [Cost: 28K, Rows: 48M] (PATH ID: 3)
| | | Projection: public.should_be_outer_b0
| | | Materialize: b.id
| | | Execute on: All Nodes
| | | Runtime Filter: (SIP1(HashJoin): b.id)
| | +-- Inner -> STORAGE ACCESS for a [Cost: 22, Rows: 1 (PREDICATE VALUE OUT-OF-RANGE)] (PUSHED GROUPING) (PATH ID: 4)
| | | Projection: public.should_be_inner_b0
| | | Materialize: a.id
| | | Filter: (a.val = 1)
| | | Execute on: All Nodes
Many thanks,
Chaima