summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeff Davis2024-12-11 00:30:37 +0000
committerJeff Davis2024-12-11 00:30:37 +0000
commita43567483c617fb046c805b61964d5168c9a0553 (patch)
treed24eb2f5904f7b7e21ef5fc23a70e2a28bb651ae
parent8ede5016859e151003fdb662e13d11e43f372174 (diff)
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
-rw-r--r--doc/src/sgml/func.sgml8
-rw-r--r--src/backend/statistics/relation_stats.c200
-rw-r--r--src/test/regress/expected/stats_import.out61
-rw-r--r--src/test/regress/sql/stats_import.sql37
4 files changed, 235 insertions, 71 deletions
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 8b81106fa23..2c35252dc06 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -30176,6 +30176,14 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
errors when restoring statistics from previous versions.
</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>
Arguments are passed as pairs of <replaceable>argname</replaceable>
and <replaceable>argvalue</replaceable>, where
<replaceable>argname</replaceable> corresponds to a named argument in
diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c
index e619d5cf5b1..264a224a4d8 100644
--- a/src/backend/statistics/relation_stats.c
+++ b/src/backend/statistics/relation_stats.c
@@ -20,6 +20,7 @@
#include "access/heapam.h"
#include "catalog/indexing.h"
#include "statistics/stat_utils.h"
+#include "utils/fmgroids.h"
#include "utils/fmgrprotos.h"
#include "utils/syscache.h"
@@ -50,59 +51,28 @@ static struct StatsArgInfo relarginfo[] =
[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.
*/
static bool
-relation_statistics_update(FunctionCallInfo fcinfo, int elevel)
+relation_statistics_update(FunctionCallInfo fcinfo, int elevel, bool inplace)
{
Oid reloid;
Relation crel;
- HeapTuple ctup;
- Form_pg_class pgcform;
- int replaces[3] = {0};
- Datum values[3] = {0};
- bool nulls[3] = {0};
- int ncols = 0;
- TupleDesc tupdesc;
+ int32 relpages = DEFAULT_RELPAGES;
+ bool update_relpages = false;
+ float reltuples = DEFAULT_RELTUPLES;
+ bool update_reltuples = false;
+ int32 relallvisible = DEFAULT_RELALLVISIBLE;
+ bool update_relallvisible = false;
bool result = true;
- stats_check_required_arg(fcinfo, relarginfo, RELATION_ARG);
- reloid = PG_GETARG_OID(RELATION_ARG);
-
- if (RecoveryInProgress())
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("recovery is in progress"),
- errhint("Statistics cannot be modified during recovery.")));
-
- stats_lock_check_privileges(reloid);
-
- /*
- * Take RowExclusiveLock on pg_class, consistent with
- * vac_update_relstats().
- */
- crel = table_open(RelationRelationId, RowExclusiveLock);
-
- tupdesc = RelationGetDescr(crel);
- ctup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(reloid));
- if (!HeapTupleIsValid(ctup))
- {
- ereport(elevel,
- (errcode(ERRCODE_OBJECT_IN_USE),
- errmsg("pg_class entry for relid %u not found", reloid)));
- table_close(crel, RowExclusiveLock);
- return false;
- }
-
- pgcform = (Form_pg_class) GETSTRUCT(ctup);
-
- /* relpages */
if (!PG_ARGISNULL(RELPAGES_ARG))
{
- int32 relpages = PG_GETARG_INT32(RELPAGES_ARG);
+ relpages = PG_GETARG_INT32(RELPAGES_ARG);
/*
* Partitioned tables may have relpages=-1. Note: for relations with
@@ -116,17 +86,13 @@ relation_statistics_update(FunctionCallInfo fcinfo, int elevel)
errmsg("relpages cannot be < -1")));
result = false;
}
- else if (relpages != pgcform->relpages)
- {
- replaces[ncols] = Anum_pg_class_relpages;
- values[ncols] = Int32GetDatum(relpages);
- ncols++;
- }
+ else
+ update_relpages = true;
}
if (!PG_ARGISNULL(RELTUPLES_ARG))
{
- float reltuples = PG_GETARG_FLOAT4(RELTUPLES_ARG);
+ reltuples = PG_GETARG_FLOAT4(RELTUPLES_ARG);
if (reltuples < -1.0)
{
@@ -135,18 +101,13 @@ relation_statistics_update(FunctionCallInfo fcinfo, int elevel)
errmsg("reltuples cannot be < -1.0")));
result = false;
}
- else if (reltuples != pgcform->reltuples)
- {
- replaces[ncols] = Anum_pg_class_reltuples;
- values[ncols] = Float4GetDatum(reltuples);
- ncols++;
- }
-
+ else
+ update_reltuples = true;
}
if (!PG_ARGISNULL(RELALLVISIBLE_ARG))
{
- int32 relallvisible = PG_GETARG_INT32(RELALLVISIBLE_ARG);
+ relallvisible = PG_GETARG_INT32(RELALLVISIBLE_ARG);
if (relallvisible < 0)
{
@@ -155,23 +116,120 @@ relation_statistics_update(FunctionCallInfo fcinfo, int elevel)
errmsg("relallvisible cannot be < 0")));
result = false;
}
- else if (relallvisible != pgcform->relallvisible)
+ else
+ update_relallvisible = true;
+ }
+
+ stats_check_required_arg(fcinfo, relarginfo, RELATION_ARG);
+ reloid = PG_GETARG_OID(RELATION_ARG);
+
+ if (RecoveryInProgress())
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("recovery is in progress"),
+ errhint("Statistics cannot be modified during recovery.")));
+
+ stats_lock_check_privileges(reloid);
+
+ /*
+ * Take RowExclusiveLock on pg_class, consistent with
+ * vac_update_relstats().
+ */
+ crel = table_open(RelationRelationId, RowExclusiveLock);
+
+ if (inplace)
+ {
+ HeapTuple ctup = NULL;
+ ScanKeyData key[1];
+ Form_pg_class pgcform;
+ void *inplace_state = NULL;
+ 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)
{
- replaces[ncols] = Anum_pg_class_relallvisible;
- values[ncols] = Int32GetDatum(relallvisible);
- ncols++;
+ 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);
- /* only update pg_class if there is a meaningful change */
- if (ncols > 0)
+ heap_freetuple(ctup);
+ }
+ else
{
- HeapTuple newtup;
+ 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));
+ if (!HeapTupleIsValid(ctup))
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_OBJECT_IN_USE),
+ errmsg("pg_class entry for relid %u not found", reloid)));
+ table_close(crel, RowExclusiveLock);
+ return false;
+ }
+ pgcform = (Form_pg_class) GETSTRUCT(ctup);
+
+ if (update_relpages && relpages != pgcform->relpages)
+ {
+ replaces[nreplaces] = Anum_pg_class_relpages;
+ values[nreplaces] = Int32GetDatum(relpages);
+ nreplaces++;
+ }
+
+ if (update_reltuples && reltuples != pgcform->reltuples)
+ {
+ replaces[nreplaces] = Anum_pg_class_reltuples;
+ values[nreplaces] = Float4GetDatum(reltuples);
+ nreplaces++;
+ }
+
+ if (update_relallvisible && relallvisible != pgcform->relallvisible)
+ {
+ replaces[nreplaces] = Anum_pg_class_relallvisible;
+ values[nreplaces] = Int32GetDatum(relallvisible);
+ nreplaces++;
+ }
+
+ if (nreplaces > 0)
+ {
+ HeapTuple newtup;
+
+ newtup = heap_modify_tuple_by_cols(ctup, tupdesc, nreplaces,
+ replaces, values, nulls);
+ CatalogTupleUpdate(crel, &newtup->t_self, newtup);
+ heap_freetuple(newtup);
+ }
- newtup = heap_modify_tuple_by_cols(ctup, tupdesc, ncols, replaces, values,
- nulls);
- CatalogTupleUpdate(crel, &newtup->t_self, newtup);
- heap_freetuple(newtup);
+ ReleaseSysCache(ctup);
}
/* release the lock, consistent with vac_update_relstats() */
@@ -188,7 +246,7 @@ relation_statistics_update(FunctionCallInfo fcinfo, int elevel)
Datum
pg_set_relation_stats(PG_FUNCTION_ARGS)
{
- relation_statistics_update(fcinfo, ERROR);
+ relation_statistics_update(fcinfo, ERROR, false);
PG_RETURN_VOID();
}
@@ -212,7 +270,7 @@ pg_clear_relation_stats(PG_FUNCTION_ARGS)
newfcinfo->args[3].value = DEFAULT_RELALLVISIBLE;
newfcinfo->args[3].isnull = false;
- relation_statistics_update(newfcinfo, ERROR);
+ relation_statistics_update(newfcinfo, ERROR, false);
PG_RETURN_VOID();
}
@@ -230,7 +288,7 @@ pg_restore_relation_stats(PG_FUNCTION_ARGS)
relarginfo, WARNING))
result = false;
- if (!relation_statistics_update(positional_fcinfo, WARNING))
+ if (!relation_statistics_update(positional_fcinfo, WARNING, true))
result = false;
PG_RETURN_BOOL(result);
diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out
index aab862c97c7..fb50da1cd83 100644
--- a/src/test/regress/expected/stats_import.out
+++ b/src/test/regress/expected/stats_import.out
@@ -105,6 +105,47 @@ WHERE oid = 'stats_import.test'::regclass;
18 | 401 | 5
(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
SELECT
pg_catalog.pg_clear_relation_stats(
@@ -708,12 +749,32 @@ WHERE oid = 'stats_import.test'::regclass;
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(
+ 'relation', 'stats_import.test'::regclass,
+ 'version', 150000::integer,
'relpages', '16'::integer);
pg_restore_relation_stats
---------------------------
t
(1 row)
+ABORT;
SELECT relpages, reltuples, relallvisible
FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql
index 31455b58c1d..d3058bf8f6b 100644
--- a/src/test/regress/sql/stats_import.sql
+++ b/src/test/regress/sql/stats_import.sql
@@ -76,6 +76,31 @@ SELECT relpages, reltuples, relallvisible
FROM pg_class
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
SELECT
pg_catalog.pg_clear_relation_stats(
@@ -568,7 +593,19 @@ WHERE oid = 'stats_import.test'::regclass;
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(
+ 'relation', 'stats_import.test'::regclass,
+ 'version', 150000::integer,
'relpages', '16'::integer);
+ABORT;
SELECT relpages, reltuples, relallvisible
FROM pg_class