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:
@ -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
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
|
Reference in New Issue
Block a user