• Tom Lane's avatar
    Rework planning and execution of UPDATE and DELETE. · 86dc9005
    Tom Lane authored
    This patch makes two closely related sets of changes:
    
    1. For UPDATE, the subplan of the ModifyTable node now only delivers
    the new values of the changed columns (i.e., the expressions computed
    in the query's SET clause) plus row identity information such as CTID.
    ModifyTable must re-fetch the original tuple to merge in the old
    values of any unchanged columns.  The core advantage of this is that
    the changed columns are uniform across all tables of an inherited or
    partitioned target relation, whereas the other columns might not be.
    A secondary advantage, when the UPDATE involves joins, is that less
    data needs to pass through the plan tree.  The disadvantage of course
    is an extra fetch of each tuple to be updated.  However, that seems to
    be very nearly free in context; even worst-case tests don't show it to
    add more than a couple percent to the total query cost.  At some point
    it might be interesting to combine the re-fetch with the tuple access
    that ModifyTable must do anyway to mark the old tuple dead; but that
    would require a good deal of refactoring and it seems it wouldn't buy
    all that much, so this patch doesn't attempt it.
    
    2. For inherited UPDATE/DELETE, instead of generating a separate
    subplan for each target relation, we now generate a single subplan
    that is just exactly like a SELECT's plan, then stick ModifyTable
    on top of that.  To let ModifyTable know which target relation a
    given incoming row refers to, a tableoid junk column is added to
    the row identity information.  This gets rid of the horrid hack
    that was inheritance_planner(), eliminating O(N^2) planning cost
    and memory consumption in cases where there were many unprunable
    target relations.
    
    Point 2 of course requires point 1, so that there is a uniform
    definition of the non-junk columns to be returned by the subplan.
    We can't insist on uniform definition of the row identity junk
    columns however, if we want to keep the ability to have both
    plain and foreign tables in a partitioning hierarchy.  Since
    it wouldn't scale very far to have every child table have its
    own row identity column, this patch includes provisions to merge
    similar row identity columns into one column of the subplan result.
    In particular, we can merge the whole-row Vars typically used as
    row identity by FDWs into one column by pretending they are type
    RECORD.  (It's still okay for the actual composite Datums to be
    labeled with the table's rowtype OID, though.)
    
    There is more that can be done to file down residual inefficiencies
    in this patch, but it seems to be committable now.
    
    FDW authors should note several API changes:
    
    * The argument list for AddForeignUpdateTargets() has changed, and so
    has the method it must use for adding junk columns to the query.  Call
    add_row_identity_var() instead of manipulating the parse tree directly.
    You might want to reconsider exactly what you're adding, too.
    
    * PlanDirectModify() must now work a little harder to find the
    ForeignScan plan node; if the foreign table is part of a partitioning
    hierarchy then the ForeignScan might not be the direct child of
    ModifyTable.  See postgres_fdw for sample code.
    
    * To check whether a relation is a target relation, it's no
    longer sufficient to compare its relid to root->parse->resultRelation.
    Instead, check it against all_result_relids or leaf_result_relids,
    as appropriate.
    
    Amit Langote and Tom Lane
    
    Discussion: https://postgr.es/m/CA+HiwqHpHdqdDn48yCEhynnniahH78rwcrv1rEX65-fsZGBOLQ@mail.gmail.com
    86dc9005
perform.sgml 86.2 KB