mirror of
https://github.com/postgres/postgres.git
synced 2025-07-03 20:02:46 +03:00
Avoid creating duplicate cached plans for inherited FK constraints.
When a foreign key constraint is applied to a partitioned table, each leaf partition inherits a similar FK constraint. We were processing all of those constraints independently, meaning that in large partitioning trees we'd build up large collections of cached FK-checking query plans. However, in all cases but one, the generated queries are actually identical for all members of the inheritance tree (because, in most cases, the query only mentions the topmost table of the other side of the FK relationship). So we can share a single cached plan among all the partitions, saving memory, not to mention time to build and maintain the cached plans. Keisuke Kuroda and Amit Langote Discussion: https://postgr.es/m/cab4b85d-9292-967d-adf2-be0d803c3e23@nttcom.co.jp_1
This commit is contained in:
@ -101,7 +101,10 @@ typedef struct RI_ConstraintInfo
|
||||
{
|
||||
Oid constraint_id; /* OID of pg_constraint entry (hash key) */
|
||||
bool valid; /* successfully initialized? */
|
||||
uint32 oidHashValue; /* hash value of pg_constraint OID */
|
||||
Oid constraint_root_id; /* OID of topmost ancestor constraint;
|
||||
* same as constraint_id if not inherited */
|
||||
uint32 oidHashValue; /* hash value of constraint_id */
|
||||
uint32 rootHashValue; /* hash value of constraint_root_id */
|
||||
NameData conname; /* name of the FK constraint */
|
||||
Oid pk_relid; /* referenced relation */
|
||||
Oid fk_relid; /* referencing relation */
|
||||
@ -207,6 +210,7 @@ static void ri_CheckTrigger(FunctionCallInfo fcinfo, const char *funcname,
|
||||
static const RI_ConstraintInfo *ri_FetchConstraintInfo(Trigger *trigger,
|
||||
Relation trig_rel, bool rel_is_pk);
|
||||
static const RI_ConstraintInfo *ri_LoadConstraintInfo(Oid constraintOid);
|
||||
static Oid get_ri_constraint_root(Oid constrOid);
|
||||
static SPIPlanPtr ri_PlanCheck(const char *querystr, int nargs, Oid *argtypes,
|
||||
RI_QueryKey *qkey, Relation fk_rel, Relation pk_rel);
|
||||
static bool ri_PerformCheck(const RI_ConstraintInfo *riinfo,
|
||||
@ -1892,7 +1896,7 @@ ri_GenerateQualCollation(StringInfo buf, Oid collation)
|
||||
* Construct a hashtable key for a prepared SPI plan of an FK constraint.
|
||||
*
|
||||
* key: output argument, *key is filled in based on the other arguments
|
||||
* riinfo: info from pg_constraint entry
|
||||
* riinfo: info derived from pg_constraint entry
|
||||
* constr_queryno: an internal number identifying the query type
|
||||
* (see RI_PLAN_XXX constants at head of file)
|
||||
* ----------
|
||||
@ -1902,10 +1906,27 @@ ri_BuildQueryKey(RI_QueryKey *key, const RI_ConstraintInfo *riinfo,
|
||||
int32 constr_queryno)
|
||||
{
|
||||
/*
|
||||
* Inherited constraints with a common ancestor can share ri_query_cache
|
||||
* entries for all query types except RI_PLAN_CHECK_LOOKUPPK_FROM_PK.
|
||||
* Except in that case, the query processes the other table involved in
|
||||
* the FK constraint (i.e., not the table on which the trigger has been
|
||||
* fired), and so it will be the same for all members of the inheritance
|
||||
* tree. So we may use the root constraint's OID in the hash key, rather
|
||||
* than the constraint's own OID. This avoids creating duplicate SPI
|
||||
* plans, saving lots of work and memory when there are many partitions
|
||||
* with similar FK constraints.
|
||||
*
|
||||
* (Note that we must still have a separate RI_ConstraintInfo for each
|
||||
* constraint, because partitions can have different column orders,
|
||||
* resulting in different pk_attnums[] or fk_attnums[] array contents.)
|
||||
*
|
||||
* We assume struct RI_QueryKey contains no padding bytes, else we'd need
|
||||
* to use memset to clear them.
|
||||
*/
|
||||
key->constr_id = riinfo->constraint_id;
|
||||
if (constr_queryno != RI_PLAN_CHECK_LOOKUPPK_FROM_PK)
|
||||
key->constr_id = riinfo->constraint_root_id;
|
||||
else
|
||||
key->constr_id = riinfo->constraint_id;
|
||||
key->constr_queryno = constr_queryno;
|
||||
}
|
||||
|
||||
@ -2051,8 +2072,15 @@ ri_LoadConstraintInfo(Oid constraintOid)
|
||||
|
||||
/* And extract data */
|
||||
Assert(riinfo->constraint_id == constraintOid);
|
||||
if (OidIsValid(conForm->conparentid))
|
||||
riinfo->constraint_root_id =
|
||||
get_ri_constraint_root(conForm->conparentid);
|
||||
else
|
||||
riinfo->constraint_root_id = constraintOid;
|
||||
riinfo->oidHashValue = GetSysCacheHashValue1(CONSTROID,
|
||||
ObjectIdGetDatum(constraintOid));
|
||||
riinfo->rootHashValue = GetSysCacheHashValue1(CONSTROID,
|
||||
ObjectIdGetDatum(riinfo->constraint_root_id));
|
||||
memcpy(&riinfo->conname, &conForm->conname, sizeof(NameData));
|
||||
riinfo->pk_relid = conForm->confrelid;
|
||||
riinfo->fk_relid = conForm->conrelid;
|
||||
@ -2082,6 +2110,30 @@ ri_LoadConstraintInfo(Oid constraintOid)
|
||||
return riinfo;
|
||||
}
|
||||
|
||||
/*
|
||||
* get_ri_constraint_root
|
||||
* Returns the OID of the constraint's root parent
|
||||
*/
|
||||
static Oid
|
||||
get_ri_constraint_root(Oid constrOid)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
HeapTuple tuple;
|
||||
Oid constrParentOid;
|
||||
|
||||
tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constrOid));
|
||||
if (!HeapTupleIsValid(tuple))
|
||||
elog(ERROR, "cache lookup failed for constraint %u", constrOid);
|
||||
constrParentOid = ((Form_pg_constraint) GETSTRUCT(tuple))->conparentid;
|
||||
ReleaseSysCache(tuple);
|
||||
if (!OidIsValid(constrParentOid))
|
||||
break; /* we reached the root constraint */
|
||||
constrOid = constrParentOid;
|
||||
}
|
||||
return constrOid;
|
||||
}
|
||||
|
||||
/*
|
||||
* Callback for pg_constraint inval events
|
||||
*
|
||||
@ -2117,7 +2169,14 @@ InvalidateConstraintCacheCallBack(Datum arg, int cacheid, uint32 hashvalue)
|
||||
RI_ConstraintInfo *riinfo = dlist_container(RI_ConstraintInfo,
|
||||
valid_link, iter.cur);
|
||||
|
||||
if (hashvalue == 0 || riinfo->oidHashValue == hashvalue)
|
||||
/*
|
||||
* We must invalidate not only entries directly matching the given
|
||||
* hash value, but also child entries, in case the invalidation
|
||||
* affects a root constraint.
|
||||
*/
|
||||
if (hashvalue == 0 ||
|
||||
riinfo->oidHashValue == hashvalue ||
|
||||
riinfo->rootHashValue == hashvalue)
|
||||
{
|
||||
riinfo->valid = false;
|
||||
/* Remove invalidated entries from the list, too */
|
||||
|
Reference in New Issue
Block a user