Skip to content

Commit 42f70cd

Browse files
committed
Improve performance of tuple conversion map generation
Previously convert_tuples_by_name_map naively performed a search of each outdesc column starting at the first column in indesc and searched each indesc column until a match was found. When partitioned tables had many columns this could result in slow generation of the tuple conversion maps. For INSERT and UPDATE statements that touched few rows, this could mean a very large overhead indeed. We can do a bit better with this loop. It's quite likely that the columns in partitioned tables and their partitions are in the same order, so it makes sense to start searching for each column outer column at the inner column position 1 after where the previous match was found (per idea from Alexander Kuzmenkov). This makes the best case search O(N) instead of O(N^2). The worst case is still O(N^2), but it seems unlikely that would happen. Likewise, in the planner, make_inh_translation_list's search for the matching column could often end up falling back on an O(N^2) type search. This commit also improves that by first checking the column that follows the previous match, instead of the column with the same attnum. If we fail to match here we fallback on the syscache's hashtable lookup. Author: David Rowley Reviewed-by: Alexander Kuzmenkov Discussion: https://www.postgresql.org/message-id/CAKJS1f9-wijVgMdRp6_qDMEQDJJ%2BA_n%3DxzZuTmLx5Fz6cwf%2B8A%40mail.gmail.com
1 parent 130beba commit 42f70cd

File tree

2 files changed

+47
-28
lines changed

2 files changed

+47
-28
lines changed

src/backend/access/common/tupconvert.c

+28-8
Original file line numberDiff line numberDiff line change
@@ -295,12 +295,16 @@ convert_tuples_by_name_map(TupleDesc indesc,
295295
const char *msg)
296296
{
297297
AttrNumber *attrMap;
298-
int n;
298+
int outnatts;
299+
int innatts;
299300
int i;
301+
int nextindesc = -1;
300302

301-
n = outdesc->natts;
302-
attrMap = (AttrNumber *) palloc0(n * sizeof(AttrNumber));
303-
for (i = 0; i < n; i++)
303+
outnatts = outdesc->natts;
304+
innatts = indesc->natts;
305+
306+
attrMap = (AttrNumber *) palloc0(outnatts * sizeof(AttrNumber));
307+
for (i = 0; i < outnatts; i++)
304308
{
305309
Form_pg_attribute outatt = TupleDescAttr(outdesc, i);
306310
char *attname;
@@ -313,10 +317,27 @@ convert_tuples_by_name_map(TupleDesc indesc,
313317
attname = NameStr(outatt->attname);
314318
atttypid = outatt->atttypid;
315319
atttypmod = outatt->atttypmod;
316-
for (j = 0; j < indesc->natts; j++)
320+
321+
/*
322+
* Now search for an attribute with the same name in the indesc. It
323+
* seems likely that a partitioned table will have the attributes in
324+
* the same order as the partition, so the search below is optimized
325+
* for that case. It is possible that columns are dropped in one of
326+
* the relations, but not the other, so we use the 'nextindesc'
327+
* counter to track the starting point of the search. If the inner
328+
* loop encounters dropped columns then it will have to skip over
329+
* them, but it should leave 'nextindesc' at the correct position for
330+
* the next outer loop.
331+
*/
332+
for (j = 0; j < innatts; j++)
317333
{
318-
Form_pg_attribute inatt = TupleDescAttr(indesc, j);
334+
Form_pg_attribute inatt;
319335

336+
nextindesc++;
337+
if (nextindesc >= innatts)
338+
nextindesc = 0;
339+
340+
inatt = TupleDescAttr(indesc, nextindesc);
320341
if (inatt->attisdropped)
321342
continue;
322343
if (strcmp(attname, NameStr(inatt->attname)) == 0)
@@ -330,7 +351,7 @@ convert_tuples_by_name_map(TupleDesc indesc,
330351
attname,
331352
format_type_be(outdesc->tdtypeid),
332353
format_type_be(indesc->tdtypeid))));
333-
attrMap[i] = (AttrNumber) (j + 1);
354+
attrMap[i] = inatt->attnum;
334355
break;
335356
}
336357
}
@@ -343,7 +364,6 @@ convert_tuples_by_name_map(TupleDesc indesc,
343364
format_type_be(outdesc->tdtypeid),
344365
format_type_be(indesc->tdtypeid))));
345366
}
346-
347367
return attrMap;
348368
}
349369

src/backend/optimizer/prep/prepunion.c

+19-20
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
#include "utils/lsyscache.h"
5252
#include "utils/rel.h"
5353
#include "utils/selfuncs.h"
54+
#include "utils/syscache.h"
5455

5556

5657
typedef struct
@@ -1895,9 +1896,11 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation,
18951896
List *vars = NIL;
18961897
TupleDesc old_tupdesc = RelationGetDescr(oldrelation);
18971898
TupleDesc new_tupdesc = RelationGetDescr(newrelation);
1899+
Oid new_relid = RelationGetRelid(newrelation);
18981900
int oldnatts = old_tupdesc->natts;
18991901
int newnatts = new_tupdesc->natts;
19001902
int old_attno;
1903+
int new_attno = 0;
19011904

19021905
for (old_attno = 0; old_attno < oldnatts; old_attno++)
19031906
{
@@ -1906,7 +1909,6 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation,
19061909
Oid atttypid;
19071910
int32 atttypmod;
19081911
Oid attcollation;
1909-
int new_attno;
19101912

19111913
att = TupleDescAttr(old_tupdesc, old_attno);
19121914
if (att->attisdropped)
@@ -1939,29 +1941,25 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation,
19391941
* Otherwise we have to search for the matching column by name.
19401942
* There's no guarantee it'll have the same column position, because
19411943
* of cases like ALTER TABLE ADD COLUMN and multiple inheritance.
1942-
* However, in simple cases it will be the same column number, so try
1943-
* that before we go groveling through all the columns.
1944-
*
1945-
* Note: the test for (att = ...) != NULL cannot fail, it's just a
1946-
* notational device to include the assignment into the if-clause.
1944+
* However, in simple cases, the relative order of columns is mostly
1945+
* the same in both relations, so try the column of newrelation that
1946+
* follows immediately after the one that we just found, and if that
1947+
* fails, let syscache handle it.
19471948
*/
1948-
if (old_attno < newnatts &&
1949-
(att = TupleDescAttr(new_tupdesc, old_attno)) != NULL &&
1950-
!att->attisdropped &&
1951-
strcmp(attname, NameStr(att->attname)) == 0)
1952-
new_attno = old_attno;
1953-
else
1949+
if (new_attno >= newnatts ||
1950+
(att = TupleDescAttr(new_tupdesc, new_attno))->attisdropped ||
1951+
strcmp(attname, NameStr(att->attname)) != 0)
19541952
{
1955-
for (new_attno = 0; new_attno < newnatts; new_attno++)
1956-
{
1957-
att = TupleDescAttr(new_tupdesc, new_attno);
1958-
if (!att->attisdropped &&
1959-
strcmp(attname, NameStr(att->attname)) == 0)
1960-
break;
1961-
}
1962-
if (new_attno >= newnatts)
1953+
HeapTuple newtup;
1954+
1955+
newtup = SearchSysCacheAttName(new_relid, attname);
1956+
if (!newtup)
19631957
elog(ERROR, "could not find inherited attribute \"%s\" of relation \"%s\"",
19641958
attname, RelationGetRelationName(newrelation));
1959+
new_attno = ((Form_pg_attribute) GETSTRUCT(newtup))->attnum - 1;
1960+
ReleaseSysCache(newtup);
1961+
1962+
att = TupleDescAttr(new_tupdesc, new_attno);
19651963
}
19661964

19671965
/* Found it, check type and collation match */
@@ -1978,6 +1976,7 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation,
19781976
atttypmod,
19791977
attcollation,
19801978
0));
1979+
new_attno++;
19811980
}
19821981

19831982
*translated_vars = vars;

0 commit comments

Comments
 (0)