Recent Postgresql Optimizer Improvements: Tom Lane Postgresql - Red Hat Edition Group Red Hat, Inc
Recent Postgresql Optimizer Improvements: Tom Lane Postgresql - Red Hat Edition Group Red Hat, Inc
Tom Lane PostgreSQL - Red Hat Edition Group Red Hat, Inc.
Tom Lane OReilly Open Source Convention, July 2003
Outline
I plan to divide this talk into two roughly equal parts: 1. Whats a query optimizer, anyway? There are multiple possible query plans We need to devise a good plan, as quickly as possible
2. Whats new in PostgreSQL 7.4? Several important planner improvements are coming Hashing is used much more than before
Tom Lane
Tom Lane
CREATE VIEW aview AS SELECT basetable.key, ... FROM basetable, anothertable WHERE basetable.key = anothertable.aref;
Tom Lane
SELECT ... FROM (SELECT ... FROM basetable, anothertable WHERE basetable.key = anothertable.aref) AS aview, mytable WHERE aview.key = mytable.ref;
Now the planner can atten this into:
SELECT ... FROM basetable, anothertable, mytable WHERE basetable.key = anothertable.aref AND basetable.key = mytable.ref;
Now we have the ability to consider different join orders, which the sub-SELECT form would not let us do.
Tom Lane OReilly Open Source Convention, July 2003
CREATE FUNCTION log(numeric) RETURNS numeric AS SELECT log(10, $1) LANGUAGE SQL;
so
Tom Lane
Tom Lane
Query analysis
In query analysis operations, we begin to disassemble the query into the pieces that the main planning steps will deal with. One interesting step examines the equality constraints enforced by the querys WHERE clause. This is useful primarily because it lets the planner make inferences about sort order: if we have WHERE a = b, then join output that is sorted by a is also sorted by b. Knowing this might let us save a sort step. A valuable byproduct of determining the equality properties is that we can deduce implied equalities. Consider our previous example:
SELECT ... FROM basetable, anothertable, mytable WHERE basetable.key = anothertable.aref AND basetable.key = mytable.ref;
Tom Lane
SELECT ... FROM basetable, anothertable, mytable WHERE basetable.key = anothertable.aref AND basetable.key = mytable.ref AND anothertable.aref = mytable.ref;
This opens the possibility of joining anothertable to mytable rst, which could be a win if theyre both small while basetable is large. Although this appears to add a redundant comparison to the query, whichever comparison ends up being the third to be checked will be discarded; so theres no runtime penalty.
Tom Lane
10
SELECT ... FROM basetable, anothertable WHERE basetable.key = anothertable.aref AND basetable.key = 42;
Tom Lane
11
SELECT ... FROM basetable, anothertable WHERE basetable.key = anothertable.aref AND basetable.key = 42 AND anothertable.aref = 42;
and now the key = aref comparison is found to be redundant, leaving:
SELECT ... FROM basetable, anothertable WHERE basetable.key = 42 AND anothertable.aref = 42;
In this form the query is trivial to build an efcient plan for. Note that the user could not have expressed the query in this way to begin with; aview might not have exposed anothertable.aref to him at all.
Tom Lane
12
Tom Lane
13
Tom Lane
14
Nested Loop
aar aai
15
Merge Join
aad
Table 2 aas
Hash Join
Tom Lane
16
SELECT ... FROM tab1, tab2, tab3 WHERE tab1.col1 = tab2.col2 AND tab1.col3 = tab3.col4;
the rst pass generates sequential and index scan plans for each of tab1,
tab2, tab3 separately. The second pass generates plans that join tab1 and tab2, as well as plans that join tab1 and tab3. (We could also consider plans that join tab2 and tab3, but this avenue is rejected
because there is no WHERE clause that constrains that join, so wed have to generate the entire cross-product of those two tables.) Finally, the third pass generates plans that combine tab3 with the join of tab1 and tab2, and also plans that combine tab2 with the join of tab1 and tab3.
Tom Lane OReilly Open Source Convention, July 2003
17
Tom Lane
18
tab2.col2, and there is an index on tab1.col1, then the cheapest plan for scanning tab1 is a sequential scan. A full index scan using the index on col1 is certainly going to be slower. But the indexscan could be used
directly as an input for merge join, so it might be better to use that than to do a sequential scan and sort. We end up keeping the cheapest plan for each interesting sort order (where interesting means is related to a candidate merge join clause or an ORDER BY or GROUP BY key). So usually there will be several plans for a given table or join set that survive for consideration at the next level.
Tom Lane OReilly Open Source Convention, July 2003
19
SELECT ... WHERE EXISTS (SELECT * FROM subtab WHERE subcol = col);
This still is a nestloop but if subtab.subcol is indexed, you can at least make it be a nestloop with inner indexscan.
Tom Lane
20
Tom Lane
21
NOT IN and for cases where the IN operator is nested inside other
boolean operations. A limitation is that the hash table will perform badly, or even fail outright, if the sub-SELECT produces more rows than will t in memory.
Tom Lane
22
SELECT ... FROM tab WHERE foo IN (SELECT bar FROM subtab)
is almost the same as
Tom Lane
23
EXISTS to get it to take notice of. Not only that, but it can also consider
merge or hash joins, which might be considerably superior depending on the numbers of rows involved.
Tom Lane
24
SELECT * FROM big table WHERE key IN (SELECT id FROM small table);
If big table.key is indexed, then its probably going to be best to change the sub-SELECT to SELECT DISTINCT and use the result as the outer side of a nestloop with inner index scan. This is the only implementation that can avoid reading all the rows of big table.
Tom Lane OReilly Open Source Convention, July 2003
25
Tom Lane
26
Tom Lane
27
28
Tom Lane
29
SELECT ... FROM tab1 LEFT JOIN tab2 ON condition ... SELECT ... FROM tab1 [INNER] JOIN tab2 ON condition ...
When there are nested JOIN constructs, the 7.3 planner does not search for the best join order it only considers the join order that corresponds to the JOIN syntax structure. This is semantically necessary for many situations involving outer joins, but if the joins are all INNER then JOIN is really just another way to write FROM tab1, tab2 . . . WHERE . . .
Tom Lane
30
Tom Lane
31
A success story
A few months ago, the Ars Digita guys (who now work at Red Hat) came to me for help with porting their ACS system to PostgreSQL. They had problems with four queries that were used in displaying web pages in ACS. These queries each took several seconds to execute in PostgreSQL, making the performance unacceptable. The same queries ran ne in Oracle. But . . . they were testing on PostgreSQL 7.2. I was able to tell them that all their problems were already solved.
Tom Lane
32
A success story
Measured runtimes for the problem queries (same data, same machine): PG 7.2 Query 1 4560.62 msec PG 7.3 4461.84 msec As of 14-Mar-03 213.14 msec
Win from: searching for good join order instead of following JOIN structure
Query 2
6612.94 msec
1.27 msec
1.05 msec
Win from: pushing down WHERE clauses into UNION (already done in 7.3)
Query 3
10375.64 msec
3276.02 msec
3.23 msec
7.3 win from: evaluating IN last among clauses attached to same plan node 7.4 win from: converting IN to special-purpose hash table
Query 4
3314.57 msec
2729.78 msec
0.54 msec
Tom Lane
33
A success story
Mind you, these are pretty ugly queries heres the rst one:
select * from (select * from vc objects join acs objects on (vc objects.object id=acs objects.object id) join cms items on (vc objects.object id=cms items.item id)) results where (cms items ancestors<=19056/) and (cms items ancestors=substr(19056/,1, length(cms items ancestors))) order by cms items ancestors;
But they are real-world examples.
Tom Lane
34
Summary
Theres lots of good new stuff coming in 7.4. Since internal hash tables are options in many more places than before, and each one can use up to SORT MEM kilobytes, its more important than ever to make sure you set that parameter intelligently. Too small will handicap performance by preventing hash-based plans from being considered, but too large can drive your system into swap hell. If youve hand-optimized IN queries into EXISTS queries, you might want to think about switching back. Under 7.4 using EXISTS could be a de-optimization, because it constrains the planner to use only one of the types of plans it would consider if you used IN.
Tom Lane