1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-28 23:42:10 +03:00

Use in-place updates for pg_restore_relation_stats().

This matches the behavior of vac_update_relstats(), which is important
to avoid bloating pg_class.

Author: Corey Huinker
Discussion: https://postgr.es/m/CADkLM=fc3je+ufv3gsHqjjSSf+t8674RXpuXW62EL55MUEQd-g@mail.gmail.com
This commit is contained in:
Jeff Davis
2024-12-10 16:30:37 -08:00
parent 8ede501685
commit a43567483c
4 changed files with 244 additions and 80 deletions

View File

@ -30175,6 +30175,14 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
function is to maintain a consistent function signature to avoid function is to maintain a consistent function signature to avoid
errors when restoring statistics from previous versions. errors when restoring statistics from previous versions.
</para> </para>
<para>
To match the behavior of <xref linkend="sql-vacuum"/> and <xref
linkend="sql-analyze"/> when updating relation statistics,
<function>pg_restore_relation_stats()</function> does not follow MVCC
transactional semantics (see <xref linkend="mvcc"/>). New relation
statistics may be durable even if the transaction aborts, and the
changes are not isolated from other transactions.
</para>
<para> <para>
Arguments are passed as pairs of <replaceable>argname</replaceable> Arguments are passed as pairs of <replaceable>argname</replaceable>
and <replaceable>argvalue</replaceable>, where and <replaceable>argvalue</replaceable>, where

View File

@ -20,6 +20,7 @@
#include "access/heapam.h" #include "access/heapam.h"
#include "catalog/indexing.h" #include "catalog/indexing.h"
#include "statistics/stat_utils.h" #include "statistics/stat_utils.h"
#include "utils/fmgroids.h"
#include "utils/fmgrprotos.h" #include "utils/fmgrprotos.h"
#include "utils/syscache.h" #include "utils/syscache.h"
@ -50,25 +51,75 @@ static struct StatsArgInfo relarginfo[] =
[NUM_RELATION_STATS_ARGS] = {0} [NUM_RELATION_STATS_ARGS] = {0}
}; };
static bool relation_statistics_update(FunctionCallInfo fcinfo, int elevel); static bool relation_statistics_update(FunctionCallInfo fcinfo, int elevel,
bool inplace);
/* /*
* Internal function for modifying statistics for a relation. * Internal function for modifying statistics for a relation.
*/ */
static bool static bool
relation_statistics_update(FunctionCallInfo fcinfo, int elevel) relation_statistics_update(FunctionCallInfo fcinfo, int elevel, bool inplace)
{ {
Oid reloid; Oid reloid;
Relation crel; Relation crel;
HeapTuple ctup; int32 relpages = DEFAULT_RELPAGES;
Form_pg_class pgcform; bool update_relpages = false;
int replaces[3] = {0}; float reltuples = DEFAULT_RELTUPLES;
Datum values[3] = {0}; bool update_reltuples = false;
bool nulls[3] = {0}; int32 relallvisible = DEFAULT_RELALLVISIBLE;
int ncols = 0; bool update_relallvisible = false;
TupleDesc tupdesc;
bool result = true; bool result = true;
if (!PG_ARGISNULL(RELPAGES_ARG))
{
relpages = PG_GETARG_INT32(RELPAGES_ARG);
/*
* Partitioned tables may have relpages=-1. Note: for relations with
* no storage, relpages=-1 is not used consistently, but must be
* supported here.
*/
if (relpages < -1)
{
ereport(elevel,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("relpages cannot be < -1")));
result = false;
}
else
update_relpages = true;
}
if (!PG_ARGISNULL(RELTUPLES_ARG))
{
reltuples = PG_GETARG_FLOAT4(RELTUPLES_ARG);
if (reltuples < -1.0)
{
ereport(elevel,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("reltuples cannot be < -1.0")));
result = false;
}
else
update_reltuples = true;
}
if (!PG_ARGISNULL(RELALLVISIBLE_ARG))
{
relallvisible = PG_GETARG_INT32(RELALLVISIBLE_ARG);
if (relallvisible < 0)
{
ereport(elevel,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("relallvisible cannot be < 0")));
result = false;
}
else
update_relallvisible = true;
}
stats_check_required_arg(fcinfo, relarginfo, RELATION_ARG); stats_check_required_arg(fcinfo, relarginfo, RELATION_ARG);
reloid = PG_GETARG_OID(RELATION_ARG); reloid = PG_GETARG_OID(RELATION_ARG);
@ -86,92 +137,99 @@ relation_statistics_update(FunctionCallInfo fcinfo, int elevel)
*/ */
crel = table_open(RelationRelationId, RowExclusiveLock); crel = table_open(RelationRelationId, RowExclusiveLock);
tupdesc = RelationGetDescr(crel); if (inplace)
ctup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(reloid));
if (!HeapTupleIsValid(ctup))
{ {
ereport(elevel, HeapTuple ctup = NULL;
(errcode(ERRCODE_OBJECT_IN_USE), ScanKeyData key[1];
errmsg("pg_class entry for relid %u not found", reloid))); Form_pg_class pgcform;
table_close(crel, RowExclusiveLock); void *inplace_state = NULL;
return false; bool dirty = false;
ScanKeyInit(&key[0], Anum_pg_class_oid, BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(reloid));
systable_inplace_update_begin(crel, ClassOidIndexId, true, NULL, 1, key,
&ctup, &inplace_state);
if (!HeapTupleIsValid(ctup))
elog(ERROR, "pg_class entry for relid %u vanished while updating statistics",
reloid);
pgcform = (Form_pg_class) GETSTRUCT(ctup);
if (update_relpages && pgcform->relpages != relpages)
{
pgcform->relpages = relpages;
dirty = true;
}
if (update_reltuples && pgcform->reltuples != reltuples)
{
pgcform->reltuples = reltuples;
dirty = true;
}
if (update_relallvisible && pgcform->relallvisible != relallvisible)
{
pgcform->relallvisible = relallvisible;
dirty = true;
}
if (dirty)
systable_inplace_update_finish(inplace_state, ctup);
else
systable_inplace_update_cancel(inplace_state);
heap_freetuple(ctup);
} }
else
pgcform = (Form_pg_class) GETSTRUCT(ctup);
/* relpages */
if (!PG_ARGISNULL(RELPAGES_ARG))
{ {
int32 relpages = PG_GETARG_INT32(RELPAGES_ARG); TupleDesc tupdesc = RelationGetDescr(crel);
HeapTuple ctup;
Form_pg_class pgcform;
int replaces[3] = {0};
Datum values[3] = {0};
bool nulls[3] = {0};
int nreplaces = 0;
/* ctup = SearchSysCache1(RELOID, ObjectIdGetDatum(reloid));
* Partitioned tables may have relpages=-1. Note: for relations with if (!HeapTupleIsValid(ctup))
* no storage, relpages=-1 is not used consistently, but must be
* supported here.
*/
if (relpages < -1)
{ {
ereport(elevel, ereport(elevel,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE), (errcode(ERRCODE_OBJECT_IN_USE),
errmsg("relpages cannot be < -1"))); errmsg("pg_class entry for relid %u not found", reloid)));
result = false; table_close(crel, RowExclusiveLock);
return false;
} }
else if (relpages != pgcform->relpages) pgcform = (Form_pg_class) GETSTRUCT(ctup);
if (update_relpages && relpages != pgcform->relpages)
{ {
replaces[ncols] = Anum_pg_class_relpages; replaces[nreplaces] = Anum_pg_class_relpages;
values[ncols] = Int32GetDatum(relpages); values[nreplaces] = Int32GetDatum(relpages);
ncols++; nreplaces++;
} }
}
if (!PG_ARGISNULL(RELTUPLES_ARG)) if (update_reltuples && reltuples != pgcform->reltuples)
{
float reltuples = PG_GETARG_FLOAT4(RELTUPLES_ARG);
if (reltuples < -1.0)
{ {
ereport(elevel, replaces[nreplaces] = Anum_pg_class_reltuples;
(errcode(ERRCODE_INVALID_PARAMETER_VALUE), values[nreplaces] = Float4GetDatum(reltuples);
errmsg("reltuples cannot be < -1.0"))); nreplaces++;
result = false;
} }
else if (reltuples != pgcform->reltuples)
if (update_relallvisible && relallvisible != pgcform->relallvisible)
{ {
replaces[ncols] = Anum_pg_class_reltuples; replaces[nreplaces] = Anum_pg_class_relallvisible;
values[ncols] = Float4GetDatum(reltuples); values[nreplaces] = Int32GetDatum(relallvisible);
ncols++; nreplaces++;
} }
} if (nreplaces > 0)
if (!PG_ARGISNULL(RELALLVISIBLE_ARG))
{
int32 relallvisible = PG_GETARG_INT32(RELALLVISIBLE_ARG);
if (relallvisible < 0)
{ {
ereport(elevel, HeapTuple newtup;
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("relallvisible cannot be < 0")));
result = false;
}
else if (relallvisible != pgcform->relallvisible)
{
replaces[ncols] = Anum_pg_class_relallvisible;
values[ncols] = Int32GetDatum(relallvisible);
ncols++;
}
}
/* only update pg_class if there is a meaningful change */ newtup = heap_modify_tuple_by_cols(ctup, tupdesc, nreplaces,
if (ncols > 0) replaces, values, nulls);
{ CatalogTupleUpdate(crel, &newtup->t_self, newtup);
HeapTuple newtup; heap_freetuple(newtup);
}
newtup = heap_modify_tuple_by_cols(ctup, tupdesc, ncols, replaces, values, ReleaseSysCache(ctup);
nulls);
CatalogTupleUpdate(crel, &newtup->t_self, newtup);
heap_freetuple(newtup);
} }
/* release the lock, consistent with vac_update_relstats() */ /* release the lock, consistent with vac_update_relstats() */
@ -188,7 +246,7 @@ relation_statistics_update(FunctionCallInfo fcinfo, int elevel)
Datum Datum
pg_set_relation_stats(PG_FUNCTION_ARGS) pg_set_relation_stats(PG_FUNCTION_ARGS)
{ {
relation_statistics_update(fcinfo, ERROR); relation_statistics_update(fcinfo, ERROR, false);
PG_RETURN_VOID(); PG_RETURN_VOID();
} }
@ -212,7 +270,7 @@ pg_clear_relation_stats(PG_FUNCTION_ARGS)
newfcinfo->args[3].value = DEFAULT_RELALLVISIBLE; newfcinfo->args[3].value = DEFAULT_RELALLVISIBLE;
newfcinfo->args[3].isnull = false; newfcinfo->args[3].isnull = false;
relation_statistics_update(newfcinfo, ERROR); relation_statistics_update(newfcinfo, ERROR, false);
PG_RETURN_VOID(); PG_RETURN_VOID();
} }
@ -230,7 +288,7 @@ pg_restore_relation_stats(PG_FUNCTION_ARGS)
relarginfo, WARNING)) relarginfo, WARNING))
result = false; result = false;
if (!relation_statistics_update(positional_fcinfo, WARNING)) if (!relation_statistics_update(positional_fcinfo, WARNING, true))
result = false; result = false;
PG_RETURN_BOOL(result); PG_RETURN_BOOL(result);

View File

@ -105,6 +105,47 @@ WHERE oid = 'stats_import.test'::regclass;
18 | 401 | 5 18 | 401 | 5
(1 row) (1 row)
-- test MVCC behavior: changes do not persist after abort (in contrast
-- to pg_restore_relation_stats(), which uses in-place updates).
BEGIN;
SELECT
pg_catalog.pg_set_relation_stats(
relation => 'stats_import.test'::regclass,
relpages => NULL::integer,
reltuples => 4000.0::real,
relallvisible => 4::integer);
pg_set_relation_stats
-----------------------
(1 row)
ABORT;
SELECT relpages, reltuples, relallvisible
FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
relpages | reltuples | relallvisible
----------+-----------+---------------
18 | 401 | 5
(1 row)
BEGIN;
SELECT
pg_catalog.pg_clear_relation_stats(
'stats_import.test'::regclass);
pg_clear_relation_stats
-------------------------
(1 row)
ABORT;
SELECT relpages, reltuples, relallvisible
FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
relpages | reltuples | relallvisible
----------+-----------+---------------
18 | 401 | 5
(1 row)
-- clear -- clear
SELECT SELECT
pg_catalog.pg_clear_relation_stats( pg_catalog.pg_clear_relation_stats(
@ -705,6 +746,25 @@ WHERE oid = 'stats_import.test'::regclass;
(1 row) (1 row)
-- ok: just relpages -- ok: just relpages
SELECT pg_restore_relation_stats(
'relation', 'stats_import.test'::regclass,
'version', 150000::integer,
'relpages', '15'::integer);
pg_restore_relation_stats
---------------------------
t
(1 row)
SELECT relpages, reltuples, relallvisible
FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
relpages | reltuples | relallvisible
----------+-----------+---------------
15 | 400 | 4
(1 row)
-- test non-MVCC behavior: new value should persist after abort
BEGIN;
SELECT pg_restore_relation_stats( SELECT pg_restore_relation_stats(
'relation', 'stats_import.test'::regclass, 'relation', 'stats_import.test'::regclass,
'version', 150000::integer, 'version', 150000::integer,
@ -714,6 +774,7 @@ SELECT pg_restore_relation_stats(
t t
(1 row) (1 row)
ABORT;
SELECT relpages, reltuples, relallvisible SELECT relpages, reltuples, relallvisible
FROM pg_class FROM pg_class
WHERE oid = 'stats_import.test'::regclass; WHERE oid = 'stats_import.test'::regclass;

View File

@ -76,6 +76,31 @@ SELECT relpages, reltuples, relallvisible
FROM pg_class FROM pg_class
WHERE oid = 'stats_import.test'::regclass; WHERE oid = 'stats_import.test'::regclass;
-- test MVCC behavior: changes do not persist after abort (in contrast
-- to pg_restore_relation_stats(), which uses in-place updates).
BEGIN;
SELECT
pg_catalog.pg_set_relation_stats(
relation => 'stats_import.test'::regclass,
relpages => NULL::integer,
reltuples => 4000.0::real,
relallvisible => 4::integer);
ABORT;
SELECT relpages, reltuples, relallvisible
FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
BEGIN;
SELECT
pg_catalog.pg_clear_relation_stats(
'stats_import.test'::regclass);
ABORT;
SELECT relpages, reltuples, relallvisible
FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
-- clear -- clear
SELECT SELECT
pg_catalog.pg_clear_relation_stats( pg_catalog.pg_clear_relation_stats(
@ -565,10 +590,22 @@ FROM pg_class
WHERE oid = 'stats_import.test'::regclass; WHERE oid = 'stats_import.test'::regclass;
-- ok: just relpages -- ok: just relpages
SELECT pg_restore_relation_stats(
'relation', 'stats_import.test'::regclass,
'version', 150000::integer,
'relpages', '15'::integer);
SELECT relpages, reltuples, relallvisible
FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
-- test non-MVCC behavior: new value should persist after abort
BEGIN;
SELECT pg_restore_relation_stats( SELECT pg_restore_relation_stats(
'relation', 'stats_import.test'::regclass, 'relation', 'stats_import.test'::regclass,
'version', 150000::integer, 'version', 150000::integer,
'relpages', '16'::integer); 'relpages', '16'::integer);
ABORT;
SELECT relpages, reltuples, relallvisible SELECT relpages, reltuples, relallvisible
FROM pg_class FROM pg_class