]> granicus.if.org Git - postgresql/commitdiff
Restructure performance tips into a single chapter ('populating a
authorTom Lane <tgl@sss.pgh.pa.us>
Sat, 16 Dec 2000 02:29:36 +0000 (02:29 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Sat, 16 Dec 2000 02:29:36 +0000 (02:29 +0000)
database' was way too small to make a chapter).  Add a section about
using JOIN syntax to direct the planner.

doc/src/sgml/filelist.sgml
doc/src/sgml/perform.sgml [new file with mode: 0644]
doc/src/sgml/plan.sgml [deleted file]
doc/src/sgml/populate.sgml [deleted file]
doc/src/sgml/user.sgml

index 98b5014ce0b2c60b2bd603312237a692bf79c46f..8458ae7275f16a051870f370c7fad07d4a158b61 100644 (file)
@@ -1,4 +1,4 @@
-<!-- $Header: /cvsroot/pgsql/doc/src/sgml/filelist.sgml,v 1.2 2000/12/14 22:30:56 petere Exp $ -->
+<!-- $Header: /cvsroot/pgsql/doc/src/sgml/filelist.sgml,v 1.3 2000/12/16 02:29:36 tgl Exp $ -->
 
 <!entity about      SYSTEM "about.sgml">
 <!entity history    SYSTEM "history.sgml">
 <!entity inherit    SYSTEM "inherit.sgml">
 <!entity manage     SYSTEM "manage.sgml">
 <!entity mvcc       SYSTEM "mvcc.sgml">
-<!entity plan       SYSTEM "plan.sgml">
+<!entity perform    SYSTEM "perform.sgml">
 <!entity plperl     SYSTEM "plperl.sgml">
 <!entity plsql      SYSTEM "plsql.sgml">
 <!entity pltcl      SYSTEM "pltcl.sgml">
-<!entity populate   SYSTEM "populate.sgml">
 <!entity psql       SYSTEM "psql.sgml">
 <!entity query-ug   SYSTEM "query-ug.sgml">
 <!entity storage    SYSTEM "storage.sgml">
diff --git a/doc/src/sgml/perform.sgml b/doc/src/sgml/perform.sgml
new file mode 100644 (file)
index 0000000..8970737
--- /dev/null
@@ -0,0 +1,440 @@
+<!--
+$Header: /cvsroot/pgsql/doc/src/sgml/perform.sgml,v 1.1 2000/12/16 02:29:36 tgl Exp $
+-->
+
+ <chapter id="performance-tips">
+  <title>Performance Tips</title>
+
+  <para>
+   Query performance can be affected by many things. Some of these can 
+   be manipulated by the user, while others are fundamental to the underlying
+   design of the system.  This chapter provides some hints about understanding
+   and tuning <productname>Postgres</productname> performance.
+  </para>
+
+  <sect1 id="using-explain">
+   <title>Using <command>EXPLAIN</command></title>
+
+   <note>
+    <title>Author</title>
+    <para>
+     Written by Tom Lane, from e-mail dated 2000-03-27.
+    </para>
+   </note>
+
+   <para>
+    <productname>Postgres</productname> devises a <firstterm>query
+    plan</firstterm> for each query it is given.  Choosing the right
+    plan to match the query structure and the properties of the data
+    is absolutely critical for good performance.  You can use the
+    <command>EXPLAIN</command> command to see what query plan the system
+    creates for any query.  Unfortunately,
+    plan-reading is an art that deserves a tutorial, and I haven't
+    had time to write one.  Here is some quick &amp; dirty explanation.
+   </para>
+
+   <para>
+    The numbers that are currently quoted by EXPLAIN are:
+
+    <itemizedlist>
+     <listitem>
+      <para>
+       Estimated start-up cost (time expended before output scan can start,
+       eg, time to do the sorting in a SORT node).
+      </para>
+     </listitem>
+
+     <listitem>
+      <para>
+       Estimated total cost (if all tuples are retrieved, which they may not
+       be --- a query with a LIMIT will stop short of paying the total cost,
+       for example).
+      </para>
+     </listitem>
+
+     <listitem>
+      <para>
+       Estimated number of rows output by this plan node (again, without
+       regard for any LIMIT).
+      </para>
+     </listitem>
+
+     <listitem>
+      <para>
+       Estimated average width (in bytes) of rows output by this plan
+       node.
+      </para>
+     </listitem>
+    </itemizedlist>
+   </para>
+
+   <para>
+    The costs are measured in units of disk page fetches.  (CPU effort
+    estimates are converted into disk-page units using some
+    fairly arbitrary fudge-factors. If you want to experiment with these
+    factors, see the list of run-time configuration parameters in the
+    <citetitle>Administrator's Guide</citetitle>.)
+   </para>
+
+   <para>
+    It's important to note that the cost of an upper-level node includes
+    the cost of all its child nodes.  It's also important to realize that
+    the cost only reflects things that the planner/optimizer cares about.
+    In particular, the cost does not consider the time spent transmitting
+    result tuples to the frontend --- which could be a pretty dominant
+    factor in the true elapsed time, but the planner ignores it because
+    it cannot change it by altering the plan.  (Every correct plan will
+    output the same tuple set, we trust.)
+   </para>
+
+   <para>
+    Rows output is a little tricky because it is <emphasis>not</emphasis> the
+    number of rows
+    processed/scanned by the query --- it is usually less, reflecting the
+    estimated selectivity of any WHERE-clause constraints that are being
+    applied at this node.  Ideally the top-level rows estimate will
+    approximate the number of rows actually returned, updated, or deleted
+    by the query (again, without considering the effects of LIMIT).
+   </para>
+
+   <para>
+    Average width is pretty bogus because the thing really doesn't have
+    any idea of the average length of variable-length columns.  I'm thinking
+    about improving that in the future, but it may not be worth the trouble,
+    because the width isn't used for very much.
+   </para>
+
+   <para>
+    Here are some examples (using the regress test database after a
+    vacuum analyze, and almost-7.0 sources):
+
+    <programlisting>
+regression=# explain select * from tenk1;
+NOTICE:  QUERY PLAN:
+
+Seq Scan on tenk1  (cost=0.00..333.00 rows=10000 width=148)
+    </programlisting>
+   </para>
+
+   <para>
+    This is about as straightforward as it gets.  If you do
+
+    <programlisting>
+select * from pg_class where relname = 'tenk1';
+    </programlisting>
+
+    you'll find out that tenk1 has 233 disk
+    pages and 10000 tuples.  So the cost is estimated at 233 block
+    reads, defined as 1.0 apiece, plus 10000 * cpu_tuple_cost which is
+    currently 0.01 (try <command>show cpu_tuple_cost</command>).
+   </para>
+
+   <para>
+    Now let's modify the query to add a qualification clause:
+
+    <programlisting>
+regression=# explain select * from tenk1 where unique1 &lt; 1000;
+NOTICE:  QUERY PLAN:
+
+Seq Scan on tenk1  (cost=0.00..358.00 rows=1000 width=148)
+    </programlisting>
+
+    The estimate of output rows has gone down because of the WHERE clause.
+    (This estimate is uncannily accurate because tenk1 is a particularly
+    simple case --- the unique1 column has 10000 distinct values ranging
+    from 0 to 9999, so the estimator's linear interpolation between min and
+    max column values is dead-on.)  However, the scan will still have to
+    visit all 10000 rows, so the cost hasn't decreased; in fact it has gone
+    up a bit to reflect the extra CPU time spent checking the WHERE
+    condition.
+   </para>
+
+   <para>
+    Modify the query to restrict the qualification even more:
+
+    <programlisting>
+regression=# explain select * from tenk1 where unique1 &lt; 100;
+NOTICE:  QUERY PLAN:
+
+Index Scan using tenk1_unique1 on tenk1  (cost=0.00..89.35 rows=100 width=148)
+    </programlisting>
+
+    and you will see that if we make the WHERE condition selective
+    enough, the planner will
+    eventually decide that an indexscan is cheaper than a sequential scan.
+    This plan will only have to visit 100 tuples because of the index,
+    so it wins despite the fact that each individual fetch is expensive.
+   </para>
+
+   <para>
+    Add another condition to the qualification:
+
+    <programlisting>
+regression=# explain select * from tenk1 where unique1 &lt; 100 and
+regression-# stringu1 = 'xxx';
+NOTICE:  QUERY PLAN:
+
+Index Scan using tenk1_unique1 on tenk1  (cost=0.00..89.60 rows=1 width=148)
+    </programlisting>
+
+    The added clause "stringu1 = 'xxx'" reduces the output-rows estimate,
+    but not the cost because we still have to visit the same set of tuples.
+   </para>
+
+   <para>
+    Let's try joining two tables, using the fields we have been discussing:
+
+    <programlisting>
+regression=# explain select * from tenk1 t1, tenk2 t2 where t1.unique1 &lt; 100
+regression-# and t1.unique2 = t2.unique2;
+NOTICE:  QUERY PLAN:
+
+Nested Loop  (cost=0.00..144.07 rows=100 width=296)
+  -&gt;  Index Scan using tenk1_unique1 on tenk1 t1
+             (cost=0.00..89.35 rows=100 width=148)
+  -&gt;  Index Scan using tenk2_unique2 on tenk2 t2
+             (cost=0.00..0.53 rows=1 width=148)
+    </programlisting>
+   </para>
+
+   <para>
+    In this nested-loop join, the outer scan is the same indexscan we had
+    in the example before last, and so its cost and row count are the same
+    because we are applying the "unique1 &lt; 100" WHERE clause at that node.
+    The "t1.unique2 = t2.unique2" clause isn't relevant yet, so it doesn't
+    affect the outer scan's row count.  For the inner scan, the
+    current
+    outer-scan tuple's unique2 value is plugged into the inner indexscan
+    to produce an indexqual like
+    "t2.unique2 = <replaceable>constant</replaceable>".  So we get the
+     same inner-scan plan and costs that we'd get from, say, "explain select
+     * from tenk2 where unique2 = 42".  The loop node's costs are then set
+     on the basis of the outer scan's cost, plus one repetition of the
+     inner scan for each outer tuple (100 * 0.53, here), plus a little CPU
+     time for join processing.
+   </para>
+
+   <para>
+    In this example the loop's output row count is the same as the product
+    of the two scans' row counts, but that's not true in general, because
+    in general you can have WHERE clauses that mention both relations and
+    so can only be applied at the join point, not to either input scan.
+    For example, if we added "WHERE ... AND t1.hundred &lt; t2.hundred",
+    that'd decrease the output row count of the join node, but not change
+    either input scan.
+   </para>
+
+   <para>
+    One way to look at variant plans is to force the planner to disregard
+    whatever strategy it thought was the winner, using the enable/disable
+    flags for each plan type.  (This is a crude tool, but useful.  See
+    also <xref linkend="explicit-joins">.)
+
+    <programlisting>
+regression=# set enable_nestloop = off;
+SET VARIABLE
+regression=# explain select * from tenk1 t1, tenk2 t2 where t1.unique1 < 100
+regression-# and t1.unique2 = t2.unique2;
+NOTICE:  QUERY PLAN:
+
+Hash Join  (cost=89.60..574.10 rows=100 width=296)
+  -&gt;  Seq Scan on tenk2 t2
+               (cost=0.00..333.00 rows=10000 width=148)
+  -&gt;  Hash  (cost=89.35..89.35 rows=100 width=148)
+        -&gt;  Index Scan using tenk1_unique1 on tenk1 t1
+               (cost=0.00..89.35 rows=100 width=148)
+    </programlisting>
+
+    This plan proposes to extract the 100 interesting rows of tenk1
+    using ye same olde indexscan, stash them into an in-memory hash table,
+    and then do a sequential scan of tenk2, probing into the hash table
+    for possible matches of "t1.unique2 = t2.unique2" at each tenk2 tuple.
+    The cost to read tenk1 and set up the hash table is entirely start-up
+    cost for the hash join, since we won't get any tuples out until we can
+    start reading tenk2.  The total time estimate for the join also
+    includes a pretty hefty charge for CPU time to probe the hash table
+    10000 times.  Note, however, that we are NOT charging 10000 times 89.35;
+    the hash table setup is only done once in this plan type.
+   </para>
+  </sect1>
+
+ <sect1 id="explicit-joins">
+  <title>Controlling the Planner with Explicit JOINs</title>
+
+  <para>
+   Beginning with <productname>Postgres</productname> 7.1 it is possible
+   to control the query planner to some extent by using explicit JOIN
+   syntax.  To see why this matters, we first need some background.
+  </para>
+
+  <para>
+   In a simple join query, such as
+    <programlisting>
+SELECT * FROM a,b,c WHERE a.id = b.id AND b.ref = c.id;
+    </programlisting>
+   the planner is free to join the given tables in any order.  For example,
+   it could generate a query plan that joins A to B, using the WHERE clause
+   a.id = b.id, and then joins C to this joined table, using the other
+   WHERE clause.  Or it could join B to C and then join A to that result.
+   Or it could join A to C and then join them with B --- but that would
+   be inefficient, since the full Cartesian product of A and C would have
+   to be formed, there being no applicable WHERE clause to allow optimization
+   of the join.
+   (All joins in the <productname>Postgres</productname> executor happen
+   between two input tables, so it's necessary to build up the result in one
+   or another of these fashions.)  The important point is that these different
+   join possibilities give semantically equivalent results but may have hugely
+   different execution costs.  Therefore, the planner will explore all of them
+   to try to find the most efficient query plan.
+  </para>
+
+  <para>
+   When a query only involves two or three tables, there aren't many join
+   orders to worry about.  But the number of possible join orders grows
+   exponentially as the number of tables expands.  Beyond ten or so input
+   tables it's no longer practical to do an exhaustive search of all the
+   possibilities, and even for six or seven tables planning may take an
+   annoyingly long time.  When there are too many input tables, the
+   <productname>Postgres</productname> planner will switch from exhaustive
+   search to a <firstterm>genetic</firstterm> probabilistic search
+   through a limited number of possibilities.  (The switchover threshold is
+   set by the GEQO_THRESHOLD run-time
+   parameter described in the <citetitle>Administrator's Guide</citetitle>.)
+   The genetic search takes less time, but it won't
+   necessarily find the best possible plan.
+  </para>
+
+  <para>
+   When the query involves outer joins, the planner has much less freedom
+   than it does for plain (inner) joins. For example, consider
+    <programlisting>
+SELECT * FROM a LEFT JOIN (b JOIN c ON (b.ref = c.id)) ON (a.id = b.id);
+    </programlisting>
+   Although this query's restrictions are superficially similar to the
+   previous example, the semantics are different because a row must be
+   emitted for each row of A that has no matching row in the join of B and C.
+   Therefore the planner has no choice of join order here: it must join
+   B to C and then join A to that result.  Accordingly, this query takes
+   less time to plan than the previous query.
+  </para>
+
+  <para>
+   In <productname>Postgres</productname> 7.1, the planner treats all
+   explicit JOIN syntaxes as constraining the join order, even though
+   it is not logically necessary to make such a constraint for inner
+   joins.  Therefore, although all of these queries give the same result:
+    <programlisting>
+SELECT * FROM a,b,c WHERE a.id = b.id AND b.ref = c.id;
+SELECT * FROM a CROSS JOIN b CROSS JOIN c WHERE a.id = b.id AND b.ref = c.id;
+SELECT * FROM a JOIN (b JOIN c ON (b.ref = c.id)) ON (a.id = b.id);
+    </programlisting>
+   the second and third take less time to plan than the first.  This effect
+   is not worth worrying about for only three tables, but it can be a
+   lifesaver with many tables.
+  </para>
+
+  <para>
+   You do not need to constrain the join order completely in order to
+   cut search time, because it's OK to use JOIN operators in a plain
+   FROM list.  For example,
+    <programlisting>
+SELECT * FROM a CROSS JOIN b, c, d, e WHERE ...;
+    </programlisting>
+   forces the planner to join A to B before joining them to other tables,
+   but doesn't constrain its choices otherwise.  In this example, the
+   number of possible join orders is reduced by a factor of 5.
+  </para>
+
+  <para>
+   If you have a mix of outer and inner joins in a complex query, you
+   might not want to constrain the planner's search for a good ordering
+   of inner joins inside an outer join.  You can't do that directly in the
+   JOIN syntax, but you can get around the syntactic limitation by using
+   views.  For example,
+    <programlisting>
+CREATE VIEW v1 AS SELECT ... FROM a, b, c WHERE ...;
+SELECT * FROM d LEFT JOIN v1 ON (...);
+    </programlisting>
+   Here, joining D must be the last step in the query plan, but the
+   planner is free to consider various join orders for A,B,C.
+  </para>
+
+  <para>
+   Constraining the planner's search in this way is a useful technique
+   both for reducing planning time and for directing the planner to a
+   good query plan.  If the planner chooses a bad join order by default,
+   you can force it to choose a better order via JOIN syntax --- assuming
+   that you know of a better order, that is.  Experimentation is recommended.
+  </para>
+ </sect1>
+
+ <sect1 id="populate">
+  <title>Populating a Database</title>
+
+  <para>
+   One may need to do a large number of table insertions when first
+   populating a database. Here are some tips and techniques for making that as
+   efficient as possible.
+  </para>
+
+  <sect2 id="disable-autocommit">
+   <title>Disable Auto-commit</title>
+
+   <para>
+    Turn off auto-commit and just do one commit at
+    the end.  Otherwise <productname>Postgres</productname> is doing a
+    lot of work for each record
+    added.  In general when you are doing bulk inserts, you want
+    to turn off some of the database features to gain speed.
+   </para>
+  </sect2>
+
+  <sect2 id="populate-copy-from">
+   <title>Use COPY FROM</title>
+
+   <para>
+    Use <command>COPY FROM STDIN</command> to load all the records in one
+    command, instead
+    of a series of INSERT commands.  This reduces parsing, planning, etc
+    overhead a great deal. If you do this then it's not necessary to fool
+    around with autocommit.
+   </para>
+  </sect2>
+
+  <sect2 id="populate-rm-indices">
+   <title>Remove Indices</title>
+
+   <para>
+    If you are loading a freshly created table, the fastest way is to
+    create the table, bulk-load with COPY, then create any indexes needed
+    for the table.  Creating an index on pre-existing data is quicker than
+    updating it incrementally as each record is loaded.
+   </para>
+
+   <para>
+    If you are augmenting an existing table, you can <command>DROP
+     INDEX</command>, load the table, then recreate the index. Of
+    course, the database performance for other users may be adversely 
+    affected during the time that the index is missing.
+   </para>
+  </sect2>
+  </sect1>
+
+ </chapter>
+
+<!-- Keep this comment at the end of the file
+Local variables:
+mode:sgml
+sgml-omittag:nil
+sgml-shorttag:t
+sgml-minimize-attributes:nil
+sgml-always-quote-attributes:t
+sgml-indent-step:1
+sgml-indent-data:t
+sgml-parent-document:nil
+sgml-default-dtd-file:"./reference.ced"
+sgml-exposed-tags:nil
+sgml-local-catalogs:("/usr/lib/sgml/catalog")
+sgml-local-ecat-files:nil
+End:
+-->
diff --git a/doc/src/sgml/plan.sgml b/doc/src/sgml/plan.sgml
deleted file mode 100644 (file)
index bfc592b..0000000
+++ /dev/null
@@ -1,269 +0,0 @@
-<!--
-$Header: /cvsroot/pgsql/doc/src/sgml/Attic/plan.sgml,v 2.5 2000/09/29 20:21:34 petere Exp $
--->
-
- <chapter id="understand-performance">
-  <title>Understanding Performance</title>
-
-  <para>
-   Query performance can be affected by many things. Some of these can 
-   be manipulated by the user, while others are fundamental to the underlying
-   design of the system.
-  </para>
-
-  <para>
-   Some performance issues, such as index creation and bulk data
-   loading, are covered elsewhere. This chapter will discuss the
-   <command>EXPLAIN</command> command, and will show how the details
-   of a query can affect the query plan, and hence overall
-   performance.
-  </para>
-
-  <sect1 id="using-explain">
-   <title>Using <command>EXPLAIN</command></title>
-
-   <note>
-    <title>Author</title>
-    <para>
-     Written by Tom Lane, from e-mail dated 2000-03-27.
-    </para>
-   </note>
-
-   <para>
-    Plan-reading is an art that deserves a tutorial, and I haven't
-    had time to write one.  Here is some quick &amp; dirty explanation.
-   </para>
-
-   <para>
-    The numbers that are currently quoted by EXPLAIN are:
-
-    <itemizedlist>
-     <listitem>
-      <para>
-       Estimated start-up cost (time expended before output scan can start,
-       eg, time to do the sorting in a SORT node).
-      </para>
-     </listitem>
-
-     <listitem>
-      <para>
-       Estimated total cost (if all tuples are retrieved, which they may not
-       be --- LIMIT will stop short of paying the total cost, for
-       example).
-      </para>
-     </listitem>
-
-     <listitem>
-      <para>
-       Estimated number of rows output by this plan node.
-      </para>
-     </listitem>
-
-     <listitem>
-      <para>
-       Estimated average width (in bytes) of rows output by this plan
-       node.
-      </para>
-     </listitem>
-    </itemizedlist>
-   </para>
-
-   <para>
-    The costs are measured in units of disk page fetches.  (CPU effort
-    estimates are converted into disk-page units using some
-    fairly arbitrary fudge-factors.  See the <command>SET</command>
-    reference page if you want to experiment with these.)
-    It's important to note that the cost of an upper-level node includes
-    the cost of all its child nodes.  It's also important to realize that
-    the cost only reflects things that the planner/optimizer cares about.
-    In particular, the cost does not consider the time spent transmitting
-    result tuples to the frontend --- which could be a pretty dominant
-    factor in the true elapsed time, but the planner ignores it because
-    it cannot change it by altering the plan.  (Every correct plan will
-    output the same tuple set, we trust.)
-   </para>
-
-   <para>
-    Rows output is a little tricky because it is <emphasis>not</emphasis> the number of rows
-    processed/scanned by the query --- it is usually less, reflecting the
-    estimated selectivity of any WHERE-clause constraints that are being
-    applied at this node.
-   </para>
-
-   <para>
-    Average width is pretty bogus because the thing really doesn't have
-    any idea of the average length of variable-length columns.  I'm thinking
-    about improving that in the future, but it may not be worth the trouble,
-    because the width isn't used for very much.
-   </para>
-
-   <para>
-    Here are some examples (using the regress test database after a
-    vacuum analyze, and almost-7.0 sources):
-
-    <programlisting>
-regression=# explain select * from tenk1;
-NOTICE:  QUERY PLAN:
-
-Seq Scan on tenk1  (cost=0.00..333.00 rows=10000 width=148)
-    </programlisting>
-   </para>
-
-   <para>
-    This is about as straightforward as it gets.  If you do
-
-    <programlisting>
-select * from pg_class where relname = 'tenk1';
-    </programlisting>
-
-    you'll find out that tenk1 has 233 disk
-    pages and 10000 tuples.  So the cost is estimated at 233 block
-    reads, defined as 1.0 apiece, plus 10000 * cpu_tuple_cost which is
-    currently 0.01 (try <command>show cpu_tuple_cost</command>).
-   </para>
-
-   <para>
-    Now let's modify the query to add a qualification clause:
-
-    <programlisting>
-regression=# explain select * from tenk1 where unique1 &lt; 1000;
-NOTICE:  QUERY PLAN:
-
-Seq Scan on tenk1  (cost=0.00..358.00 rows=1000 width=148)
-    </programlisting>
-
-    The estimate of output rows has gone down because of the WHERE clause.
-    (The uncannily accurate estimate is just because tenk1 is a particularly
-    simple case --- the unique1 column has 10000 distinct values ranging
-    from 0 to 9999, so the estimator's linear interpolation between min and
-    max column values is dead-on.)  However, the scan will still have to
-    visit all 10000 rows, so the cost hasn't decreased; in fact it has gone
-    up a bit to reflect the extra CPU time spent checking the WHERE
-    condition.
-   </para>
-
-   <para>
-    Modify the query to restrict the qualification even more:
-
-    <programlisting>
-regression=# explain select * from tenk1 where unique1 &lt; 100;
-NOTICE:  QUERY PLAN:
-
-Index Scan using tenk1_unique1 on tenk1  (cost=0.00..89.35 rows=100 width=148)
-    </programlisting>
-
-    and you will see that if we make the WHERE condition selective
-    enough, the planner will
-    eventually decide that an indexscan is cheaper than a sequential scan.
-    This plan will only have to visit 100 tuples because of the index,
-    so it wins despite the fact that each individual fetch is expensive.
-   </para>
-
-   <para>
-    Add another condition to the qualification:
-
-    <programlisting>
-regression=# explain select * from tenk1 where unique1 &lt; 100 and
-regression-# stringu1 = 'xxx';
-NOTICE:  QUERY PLAN:
-
-Index Scan using tenk1_unique1 on tenk1  (cost=0.00..89.60 rows=1 width=148)
-    </programlisting>
-
-    The added clause "stringu1 = 'xxx'" reduces the output-rows estimate,
-    but not the cost because we still have to visit the same set of tuples.
-   </para>
-
-   <para>
-    Let's try joining two tables, using the fields we have been discussing:
-
-    <programlisting>
-regression=# explain select * from tenk1 t1, tenk2 t2 where t1.unique1 &lt; 100
-regression-# and t1.unique2 = t2.unique2;
-NOTICE:  QUERY PLAN:
-
-Nested Loop  (cost=0.00..144.07 rows=100 width=296)
-  -&gt;  Index Scan using tenk1_unique1 on tenk1 t1
-             (cost=0.00..89.35 rows=100 width=148)
-  -&gt;  Index Scan using tenk2_unique2 on tenk2 t2
-             (cost=0.00..0.53 rows=1 width=148)
-    </programlisting>
-   </para>
-
-   <para>
-    In this nested-loop join, the outer scan is the same indexscan we had
-    in the example before last, and so its cost and row count are the same
-    because we are applying the "unique1 &lt; 100" WHERE clause at that node.
-    The "t1.unique2 = t2.unique2" clause isn't relevant yet, so it doesn't
-    affect the outer scan's row count.  For the inner scan, the
-    current
-    outer-scan tuple's unique2 value is plugged into the inner indexscan
-    to produce an indexqual like
-    "t2.unique2 = <replaceable>constant</replaceable>".  So we get the
-     same inner-scan plan and costs that we'd get from, say, "explain select
-     * from tenk2 where unique2 = 42".  The loop node's costs are then set
-     on the basis of the outer scan's cost, plus one repetition of the
-     inner scan for each outer tuple (100 * 0.53, here), plus a little CPU
-     time for join processing.
-   </para>
-
-   <para>
-    In this example the loop's output row count is the same as the product
-    of the two scans' row counts, but that's not true in general, because
-    in general you can have WHERE clauses that mention both relations and
-    so can only be applied at the join point, not to either input scan.
-    For example, if we added "WHERE ... AND t1.hundred &lt; t2.hundred",
-    that'd decrease the output row count of the join node, but not change
-    either input scan.
-   </para>
-
-   <para>
-    We can look at variant plans by forcing the planner to disregard
-    whatever strategy it thought was the winner (a pretty crude tool,
-    but it's what we've got at the moment):
-
-    <programlisting>
-regression=# set enable_nestloop = off;
-SET VARIABLE
-regression=# explain select * from tenk1 t1, tenk2 t2 where t1.unique1 < 100
-regression-# and t1.unique2 = t2.unique2;
-NOTICE:  QUERY PLAN:
-
-Hash Join  (cost=89.60..574.10 rows=100 width=296)
-  -&gt;  Seq Scan on tenk2 t2
-               (cost=0.00..333.00 rows=10000 width=148)
-  -&gt;  Hash  (cost=89.35..89.35 rows=100 width=148)
-        -&gt;  Index Scan using tenk1_unique1 on tenk1 t1
-               (cost=0.00..89.35 rows=100 width=148)
-    </programlisting>
-
-    This plan proposes to extract the 100 interesting rows of tenk1
-    using ye same olde indexscan, stash them into an in-memory hash table,
-    and then do a sequential scan of tenk2, probing into the hash table
-    for possible matches of "t1.unique2 = t2.unique2" at each tenk2 tuple.
-    The cost to read tenk1 and set up the hash table is entirely start-up
-    cost for the hash join, since we won't get any tuples out until we can
-    start reading tenk2.  The total time estimate for the join also
-    includes a pretty hefty charge for CPU time to probe the hash table
-    10000 times.  Note, however, that we are NOT charging 10000 times 89.35;
-    the hash table setup is only done once in this plan type.
-   </para>
-  </sect1>
- </chapter>
-
-<!-- Keep this comment at the end of the file
-Local variables:
-mode:sgml
-sgml-omittag:nil
-sgml-shorttag:t
-sgml-minimize-attributes:nil
-sgml-always-quote-attributes:t
-sgml-indent-step:1
-sgml-indent-data:t
-sgml-parent-document:nil
-sgml-default-dtd-file:"./reference.ced"
-sgml-exposed-tags:nil
-sgml-local-catalogs:("/usr/lib/sgml/catalog")
-sgml-local-ecat-files:nil
-End:
--->
diff --git a/doc/src/sgml/populate.sgml b/doc/src/sgml/populate.sgml
deleted file mode 100644 (file)
index abac068..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-<!--
-$Header: /cvsroot/pgsql/doc/src/sgml/Attic/populate.sgml,v 2.3 2000/09/29 20:21:34 petere Exp $
--->
-
- <chapter id="populate">
-  <title>Populating a Database</title>
-
-  <note>
-   <title>Author</title>
-   <para>
-    Written by Tom Lane, from an e-mail message dated 1999-12-05.
-   </para>
-  </note>
-
-  <para>
-   One may need to do a large number of table insertions when first
-   populating a database. Here are some tips and techniques for making that as
-   efficient as possible.
-  </para>
-
-  <sect1 id="disable-autocommit">
-   <title>Disable Auto-commit</title>
-
-   <para>
-    Turn off auto-commit and just do one commit at
-    the end.  Otherwise <productname>Postgres</productname> is doing a
-    lot of work for each record
-    added.  In general when you are doing bulk inserts, you want
-    to turn off some of the database features to gain speed.
-   </para>
-  </sect1>
-
-  <sect1 id="populate-copy-from">
-   <title>Use COPY FROM</title>
-
-   <para>
-    Use <command>COPY FROM STDIN</command> to load all the records in one
-    command, instead
-    of a series of INSERT commands.  This reduces parsing, planning, etc
-    overhead a great deal. If you do this then it's not necessary to fool
-    around with autocommit.
-   </para>
-  </sect1>
-
-  <sect1 id="populate-rm-indices">
-   <title>Remove Indices</title>
-
-   <para>
-    If you are loading a freshly created table, the fastest way is to
-    create the table, bulk-load with COPY, then create any indexes needed
-    for the table.  Creating an index on pre-existing data is quicker than
-    updating it incrementally as each record is loaded.
-   </para>
-
-   <para>
-    If you are augmenting an existing table, you can <command>DROP
-     INDEX</command>, load the table, then recreate the index. Of
-    course, the database performance for other users may be adversely 
-    affected during the time that the index is missing.
-   </para>
-  </sect1>
- </chapter>
-
-<!-- Keep this comment at the end of the file
-Local variables:
-mode:sgml
-sgml-omittag:nil
-sgml-shorttag:t
-sgml-minimize-attributes:nil
-sgml-always-quote-attributes:t
-sgml-indent-step:1
-sgml-indent-data:t
-sgml-parent-document:nil
-sgml-default-dtd-file:"./reference.ced"
-sgml-exposed-tags:nil
-sgml-local-catalogs:("/usr/lib/sgml/catalog")
-sgml-local-ecat-files:nil
-End:
--->
index 64922acc399eb09f781461dcb9b0e23b4c8b1dae..e2f78744839e0ce9d6d214244465ff8d05c701ec 100644 (file)
@@ -1,5 +1,5 @@
 <!--
-$Header: /cvsroot/pgsql/doc/src/sgml/Attic/user.sgml,v 1.21 2000/12/14 22:30:56 petere Exp $
+$Header: /cvsroot/pgsql/doc/src/sgml/Attic/user.sgml,v 1.22 2000/12/16 02:29:36 tgl Exp $
 -->
 
 <book id="user">
@@ -57,8 +57,7 @@ $Header: /cvsroot/pgsql/doc/src/sgml/Attic/user.sgml,v 1.21 2000/12/14 22:30:56
  &environ;
  &manage;
  &storage;
- &plan;
- &populate
+ &perform;
 
  <!-- appendices -->