Skip to content

Commit 3c429b3

Browse files
jeff-davisreshke
authored andcommitted
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
1 parent c735594 commit 3c429b3

4 files changed

Lines changed: 229 additions & 65 deletions

File tree

doc/src/sgml/func.sgml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30039,6 +30039,14 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
3003930039
function is to maintain a consistent function signature to avoid
3004030040
errors when restoring statistics from previous versions.
3004130041
</para>
30042+
<para>
30043+
To match the behavior of <xref linkend="sql-vacuum"/> and <xref
30044+
linkend="sql-analyze"/> when updating relation statistics,
30045+
<function>pg_restore_relation_stats()</function> does not follow MVCC
30046+
transactional semantics (see <xref linkend="mvcc"/>). New relation
30047+
statistics may be durable even if the transaction aborts, and the
30048+
changes are not isolated from other transactions.
30049+
</para>
3004230050
<para>
3004330051
Arguments are passed as pairs of <replaceable>argname</replaceable>
3004430052
and <replaceable>argvalue</replaceable>, where

src/backend/statistics/relation_stats.c

Lines changed: 123 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "access/heapam.h"
2121
#include "catalog/indexing.h"
2222
#include "statistics/stat_utils.h"
23+
#include "utils/fmgroids.h"
2324
#include "utils/fmgrprotos.h"
2425
#include "utils/syscache.h"
2526

@@ -50,53 +51,28 @@ static struct StatsArgInfo relarginfo[] =
5051
[NUM_RELATION_STATS_ARGS] = {0}
5152
};
5253

53-
static bool relation_statistics_update(FunctionCallInfo fcinfo, int elevel);
54+
static bool relation_statistics_update(FunctionCallInfo fcinfo, int elevel,
55+
bool inplace);
5456

5557
/*
5658
* Internal function for modifying statistics for a relation.
5759
*/
5860
static bool
59-
relation_statistics_update(FunctionCallInfo fcinfo, int elevel)
61+
relation_statistics_update(FunctionCallInfo fcinfo, int elevel, bool inplace)
6062
{
6163
Oid reloid;
6264
Relation crel;
63-
HeapTuple ctup;
64-
Form_pg_class pgcform;
65-
int replaces[3] = {0};
66-
Datum values[3] = {0};
67-
bool nulls[3] = {0};
68-
int ncols = 0;
69-
TupleDesc tupdesc;
65+
int32 relpages = DEFAULT_RELPAGES;
66+
bool update_relpages = false;
67+
float reltuples = DEFAULT_RELTUPLES;
68+
bool update_reltuples = false;
69+
int32 relallvisible = DEFAULT_RELALLVISIBLE;
70+
bool update_relallvisible = false;
7071
bool result = true;
7172

72-
stats_check_required_arg(fcinfo, relarginfo, RELATION_ARG);
73-
reloid = PG_GETARG_OID(RELATION_ARG);
74-
75-
stats_lock_check_privileges(reloid);
76-
77-
/*
78-
* Take RowExclusiveLock on pg_class, consistent with
79-
* vac_update_relstats().
80-
*/
81-
crel = table_open(RelationRelationId, RowExclusiveLock);
82-
83-
tupdesc = RelationGetDescr(crel);
84-
ctup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(reloid));
85-
if (!HeapTupleIsValid(ctup))
86-
{
87-
ereport(elevel,
88-
(errcode(ERRCODE_OBJECT_IN_USE),
89-
errmsg("pg_class entry for relid %u not found", reloid)));
90-
table_close(crel, RowExclusiveLock);
91-
return false;
92-
}
93-
94-
pgcform = (Form_pg_class) GETSTRUCT(ctup);
95-
96-
/* relpages */
9773
if (!PG_ARGISNULL(RELPAGES_ARG))
9874
{
99-
int32 relpages = PG_GETARG_INT32(RELPAGES_ARG);
75+
relpages = PG_GETARG_INT32(RELPAGES_ARG);
10076

10177
/*
10278
* Partitioned tables may have relpages=-1. Note: for relations with
@@ -110,17 +86,13 @@ relation_statistics_update(FunctionCallInfo fcinfo, int elevel)
11086
errmsg("relpages cannot be < -1")));
11187
result = false;
11288
}
113-
else if (relpages != pgcform->relpages)
114-
{
115-
replaces[ncols] = Anum_pg_class_relpages;
116-
values[ncols] = Int32GetDatum(relpages);
117-
ncols++;
118-
}
89+
else
90+
update_relpages = true;
11991
}
12092

12193
if (!PG_ARGISNULL(RELTUPLES_ARG))
12294
{
123-
float reltuples = PG_GETARG_FLOAT4(RELTUPLES_ARG);
95+
reltuples = PG_GETARG_FLOAT4(RELTUPLES_ARG);
12496

12597
if (reltuples < -1.0)
12698
{
@@ -129,18 +101,13 @@ relation_statistics_update(FunctionCallInfo fcinfo, int elevel)
129101
errmsg("reltuples cannot be < -1.0")));
130102
result = false;
131103
}
132-
else if (reltuples != pgcform->reltuples)
133-
{
134-
replaces[ncols] = Anum_pg_class_reltuples;
135-
values[ncols] = Float4GetDatum(reltuples);
136-
ncols++;
137-
}
138-
104+
else
105+
update_reltuples = true;
139106
}
140107

141108
if (!PG_ARGISNULL(RELALLVISIBLE_ARG))
142109
{
143-
int32 relallvisible = PG_GETARG_INT32(RELALLVISIBLE_ARG);
110+
relallvisible = PG_GETARG_INT32(RELALLVISIBLE_ARG);
144111

145112
if (relallvisible < 0)
146113
{
@@ -149,23 +116,114 @@ relation_statistics_update(FunctionCallInfo fcinfo, int elevel)
149116
errmsg("relallvisible cannot be < 0")));
150117
result = false;
151118
}
152-
else if (relallvisible != pgcform->relallvisible)
119+
else
120+
update_relallvisible = true;
121+
}
122+
123+
stats_check_required_arg(fcinfo, relarginfo, RELATION_ARG);
124+
reloid = PG_GETARG_OID(RELATION_ARG);
125+
126+
stats_lock_check_privileges(reloid);
127+
128+
/*
129+
* Take RowExclusiveLock on pg_class, consistent with
130+
* vac_update_relstats().
131+
*/
132+
crel = table_open(RelationRelationId, RowExclusiveLock);
133+
134+
if (inplace)
135+
{
136+
HeapTuple ctup = NULL;
137+
ScanKeyData key[1];
138+
Form_pg_class pgcform;
139+
void *inplace_state = NULL;
140+
bool dirty = false;
141+
142+
ScanKeyInit(&key[0], Anum_pg_class_oid, BTEqualStrategyNumber, F_OIDEQ,
143+
ObjectIdGetDatum(reloid));
144+
systable_inplace_update_begin(crel, ClassOidIndexId, true, NULL, 1, key,
145+
&ctup, &inplace_state);
146+
if (!HeapTupleIsValid(ctup))
147+
elog(ERROR, "pg_class entry for relid %u vanished while updating statistics",
148+
reloid);
149+
pgcform = (Form_pg_class) GETSTRUCT(ctup);
150+
151+
if (update_relpages && pgcform->relpages != relpages)
153152
{
154-
replaces[ncols] = Anum_pg_class_relallvisible;
155-
values[ncols] = Int32GetDatum(relallvisible);
156-
ncols++;
153+
pgcform->relpages = relpages;
154+
dirty = true;
157155
}
158-
}
156+
if (update_reltuples && pgcform->reltuples != reltuples)
157+
{
158+
pgcform->reltuples = reltuples;
159+
dirty = true;
160+
}
161+
if (update_relallvisible && pgcform->relallvisible != relallvisible)
162+
{
163+
pgcform->relallvisible = relallvisible;
164+
dirty = true;
165+
}
166+
167+
if (dirty)
168+
systable_inplace_update_finish(inplace_state, ctup);
169+
else
170+
systable_inplace_update_cancel(inplace_state);
159171

160-
/* only update pg_class if there is a meaningful change */
161-
if (ncols > 0)
172+
heap_freetuple(ctup);
173+
}
174+
else
162175
{
163-
HeapTuple newtup;
176+
TupleDesc tupdesc = RelationGetDescr(crel);
177+
HeapTuple ctup;
178+
Form_pg_class pgcform;
179+
int replaces[3] = {0};
180+
Datum values[3] = {0};
181+
bool nulls[3] = {0};
182+
int nreplaces = 0;
183+
184+
ctup = SearchSysCache1(RELOID, ObjectIdGetDatum(reloid));
185+
if (!HeapTupleIsValid(ctup))
186+
{
187+
ereport(elevel,
188+
(errcode(ERRCODE_OBJECT_IN_USE),
189+
errmsg("pg_class entry for relid %u not found", reloid)));
190+
table_close(crel, RowExclusiveLock);
191+
return false;
192+
}
193+
pgcform = (Form_pg_class) GETSTRUCT(ctup);
194+
195+
if (update_relpages && relpages != pgcform->relpages)
196+
{
197+
replaces[nreplaces] = Anum_pg_class_relpages;
198+
values[nreplaces] = Int32GetDatum(relpages);
199+
nreplaces++;
200+
}
201+
202+
if (update_reltuples && reltuples != pgcform->reltuples)
203+
{
204+
replaces[nreplaces] = Anum_pg_class_reltuples;
205+
values[nreplaces] = Float4GetDatum(reltuples);
206+
nreplaces++;
207+
}
208+
209+
if (update_relallvisible && relallvisible != pgcform->relallvisible)
210+
{
211+
replaces[nreplaces] = Anum_pg_class_relallvisible;
212+
values[nreplaces] = Int32GetDatum(relallvisible);
213+
nreplaces++;
214+
}
215+
216+
if (nreplaces > 0)
217+
{
218+
HeapTuple newtup;
219+
220+
newtup = heap_modify_tuple_by_cols(ctup, tupdesc, nreplaces,
221+
replaces, values, nulls);
222+
CatalogTupleUpdate(crel, &newtup->t_self, newtup);
223+
heap_freetuple(newtup);
224+
}
164225

165-
newtup = heap_modify_tuple_by_cols(ctup, tupdesc, ncols, replaces, values,
166-
nulls);
167-
CatalogTupleUpdate(crel, &newtup->t_self, newtup);
168-
heap_freetuple(newtup);
226+
ReleaseSysCache(ctup);
169227
}
170228

171229
/* release the lock, consistent with vac_update_relstats() */
@@ -180,7 +238,7 @@ relation_statistics_update(FunctionCallInfo fcinfo, int elevel)
180238
Datum
181239
pg_set_relation_stats(PG_FUNCTION_ARGS)
182240
{
183-
relation_statistics_update(fcinfo, ERROR);
241+
relation_statistics_update(fcinfo, ERROR, false);
184242
PG_RETURN_VOID();
185243
}
186244

@@ -204,7 +262,7 @@ pg_clear_relation_stats(PG_FUNCTION_ARGS)
204262
newfcinfo->args[3].value = DEFAULT_RELALLVISIBLE;
205263
newfcinfo->args[3].isnull = false;
206264

207-
relation_statistics_update(newfcinfo, ERROR);
265+
relation_statistics_update(newfcinfo, ERROR, false);
208266
PG_RETURN_VOID();
209267
}
210268

@@ -222,7 +280,7 @@ pg_restore_relation_stats(PG_FUNCTION_ARGS)
222280
relarginfo, WARNING))
223281
result = false;
224282

225-
if (!relation_statistics_update(positional_fcinfo, WARNING))
283+
if (!relation_statistics_update(positional_fcinfo, WARNING, true))
226284
result = false;
227285

228286
PG_RETURN_BOOL(result);

src/test/regress/expected/stats_import.out

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,47 @@ WHERE oid = 'stats_import.test'::regclass;
105105
18 | 401 | 5
106106
(1 row)
107107

108+
-- test MVCC behavior: changes do not persist after abort (in contrast
109+
-- to pg_restore_relation_stats(), which uses in-place updates).
110+
BEGIN;
111+
SELECT
112+
pg_catalog.pg_set_relation_stats(
113+
relation => 'stats_import.test'::regclass,
114+
relpages => NULL::integer,
115+
reltuples => 4000.0::real,
116+
relallvisible => 4::integer);
117+
pg_set_relation_stats
118+
-----------------------
119+
120+
(1 row)
121+
122+
ABORT;
123+
SELECT relpages, reltuples, relallvisible
124+
FROM pg_class
125+
WHERE oid = 'stats_import.test'::regclass;
126+
relpages | reltuples | relallvisible
127+
----------+-----------+---------------
128+
18 | 401 | 5
129+
(1 row)
130+
131+
BEGIN;
132+
SELECT
133+
pg_catalog.pg_clear_relation_stats(
134+
'stats_import.test'::regclass);
135+
pg_clear_relation_stats
136+
-------------------------
137+
138+
(1 row)
139+
140+
ABORT;
141+
SELECT relpages, reltuples, relallvisible
142+
FROM pg_class
143+
WHERE oid = 'stats_import.test'::regclass;
144+
relpages | reltuples | relallvisible
145+
----------+-----------+---------------
146+
18 | 401 | 5
147+
(1 row)
148+
108149
-- clear
109150
SELECT
110151
pg_catalog.pg_clear_relation_stats(
@@ -705,6 +746,25 @@ WHERE oid = 'stats_import.test'::regclass;
705746
(1 row)
706747

707748
-- ok: just relpages
749+
SELECT pg_restore_relation_stats(
750+
'relation', 'stats_import.test'::regclass,
751+
'version', 150000::integer,
752+
'relpages', '15'::integer);
753+
pg_restore_relation_stats
754+
---------------------------
755+
t
756+
(1 row)
757+
758+
SELECT relpages, reltuples, relallvisible
759+
FROM pg_class
760+
WHERE oid = 'stats_import.test'::regclass;
761+
relpages | reltuples | relallvisible
762+
----------+-----------+---------------
763+
15 | 400 | 4
764+
(1 row)
765+
766+
-- test non-MVCC behavior: new value should persist after abort
767+
BEGIN;
708768
SELECT pg_restore_relation_stats(
709769
'relation', 'stats_import.test'::regclass,
710770
'version', 150000::integer,
@@ -714,6 +774,7 @@ SELECT pg_restore_relation_stats(
714774
t
715775
(1 row)
716776

777+
ABORT;
717778
SELECT relpages, reltuples, relallvisible
718779
FROM pg_class
719780
WHERE oid = 'stats_import.test'::regclass;

0 commit comments

Comments
 (0)