Skip to content

Commit a435674

Browse files
committed
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 8ede501 commit a435674

File tree

4 files changed

+235
-71
lines changed

4 files changed

+235
-71
lines changed

doc/src/sgml/func.sgml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30175,6 +30175,14 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
3017530175
function is to maintain a consistent function signature to avoid
3017630176
errors when restoring statistics from previous versions.
3017730177
</para>
30178+
<para>
30179+
To match the behavior of <xref linkend="sql-vacuum"/> and <xref
30180+
linkend="sql-analyze"/> when updating relation statistics,
30181+
<function>pg_restore_relation_stats()</function> does not follow MVCC
30182+
transactional semantics (see <xref linkend="mvcc"/>). New relation
30183+
statistics may be durable even if the transaction aborts, and the
30184+
changes are not isolated from other transactions.
30185+
</para>
3017830186
<para>
3017930187
Arguments are passed as pairs of <replaceable>argname</replaceable>
3018030188
and <replaceable>argvalue</replaceable>, where

src/backend/statistics/relation_stats.c

Lines changed: 129 additions & 71 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,59 +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-
if (RecoveryInProgress())
76-
ereport(ERROR,
77-
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
78-
errmsg("recovery is in progress"),
79-
errhint("Statistics cannot be modified during recovery.")));
80-
81-
stats_lock_check_privileges(reloid);
82-
83-
/*
84-
* Take RowExclusiveLock on pg_class, consistent with
85-
* vac_update_relstats().
86-
*/
87-
crel = table_open(RelationRelationId, RowExclusiveLock);
88-
89-
tupdesc = RelationGetDescr(crel);
90-
ctup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(reloid));
91-
if (!HeapTupleIsValid(ctup))
92-
{
93-
ereport(elevel,
94-
(errcode(ERRCODE_OBJECT_IN_USE),
95-
errmsg("pg_class entry for relid %u not found", reloid)));
96-
table_close(crel, RowExclusiveLock);
97-
return false;
98-
}
99-
100-
pgcform = (Form_pg_class) GETSTRUCT(ctup);
101-
102-
/* relpages */
10373
if (!PG_ARGISNULL(RELPAGES_ARG))
10474
{
105-
int32 relpages = PG_GETARG_INT32(RELPAGES_ARG);
75+
relpages = PG_GETARG_INT32(RELPAGES_ARG);
10676

10777
/*
10878
* Partitioned tables may have relpages=-1. Note: for relations with
@@ -116,17 +86,13 @@ relation_statistics_update(FunctionCallInfo fcinfo, int elevel)
11686
errmsg("relpages cannot be < -1")));
11787
result = false;
11888
}
119-
else if (relpages != pgcform->relpages)
120-
{
121-
replaces[ncols] = Anum_pg_class_relpages;
122-
values[ncols] = Int32GetDatum(relpages);
123-
ncols++;
124-
}
89+
else
90+
update_relpages = true;
12591
}
12692

12793
if (!PG_ARGISNULL(RELTUPLES_ARG))
12894
{
129-
float reltuples = PG_GETARG_FLOAT4(RELTUPLES_ARG);
95+
reltuples = PG_GETARG_FLOAT4(RELTUPLES_ARG);
13096

13197
if (reltuples < -1.0)
13298
{
@@ -135,18 +101,13 @@ relation_statistics_update(FunctionCallInfo fcinfo, int elevel)
135101
errmsg("reltuples cannot be < -1.0")));
136102
result = false;
137103
}
138-
else if (reltuples != pgcform->reltuples)
139-
{
140-
replaces[ncols] = Anum_pg_class_reltuples;
141-
values[ncols] = Float4GetDatum(reltuples);
142-
ncols++;
143-
}
144-
104+
else
105+
update_reltuples = true;
145106
}
146107

147108
if (!PG_ARGISNULL(RELALLVISIBLE_ARG))
148109
{
149-
int32 relallvisible = PG_GETARG_INT32(RELALLVISIBLE_ARG);
110+
relallvisible = PG_GETARG_INT32(RELALLVISIBLE_ARG);
150111

151112
if (relallvisible < 0)
152113
{
@@ -155,23 +116,120 @@ relation_statistics_update(FunctionCallInfo fcinfo, int elevel)
155116
errmsg("relallvisible cannot be < 0")));
156117
result = false;
157118
}
158-
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+
if (RecoveryInProgress())
127+
ereport(ERROR,
128+
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
129+
errmsg("recovery is in progress"),
130+
errhint("Statistics cannot be modified during recovery.")));
131+
132+
stats_lock_check_privileges(reloid);
133+
134+
/*
135+
* Take RowExclusiveLock on pg_class, consistent with
136+
* vac_update_relstats().
137+
*/
138+
crel = table_open(RelationRelationId, RowExclusiveLock);
139+
140+
if (inplace)
141+
{
142+
HeapTuple ctup = NULL;
143+
ScanKeyData key[1];
144+
Form_pg_class pgcform;
145+
void *inplace_state = NULL;
146+
bool dirty = false;
147+
148+
ScanKeyInit(&key[0], Anum_pg_class_oid, BTEqualStrategyNumber, F_OIDEQ,
149+
ObjectIdGetDatum(reloid));
150+
systable_inplace_update_begin(crel, ClassOidIndexId, true, NULL, 1, key,
151+
&ctup, &inplace_state);
152+
if (!HeapTupleIsValid(ctup))
153+
elog(ERROR, "pg_class entry for relid %u vanished while updating statistics",
154+
reloid);
155+
pgcform = (Form_pg_class) GETSTRUCT(ctup);
156+
157+
if (update_relpages && pgcform->relpages != relpages)
159158
{
160-
replaces[ncols] = Anum_pg_class_relallvisible;
161-
values[ncols] = Int32GetDatum(relallvisible);
162-
ncols++;
159+
pgcform->relpages = relpages;
160+
dirty = true;
163161
}
164-
}
162+
if (update_reltuples && pgcform->reltuples != reltuples)
163+
{
164+
pgcform->reltuples = reltuples;
165+
dirty = true;
166+
}
167+
if (update_relallvisible && pgcform->relallvisible != relallvisible)
168+
{
169+
pgcform->relallvisible = relallvisible;
170+
dirty = true;
171+
}
172+
173+
if (dirty)
174+
systable_inplace_update_finish(inplace_state, ctup);
175+
else
176+
systable_inplace_update_cancel(inplace_state);
165177

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

171-
newtup = heap_modify_tuple_by_cols(ctup, tupdesc, ncols, replaces, values,
172-
nulls);
173-
CatalogTupleUpdate(crel, &newtup->t_self, newtup);
174-
heap_freetuple(newtup);
232+
ReleaseSysCache(ctup);
175233
}
176234

177235
/* release the lock, consistent with vac_update_relstats() */
@@ -188,7 +246,7 @@ relation_statistics_update(FunctionCallInfo fcinfo, int elevel)
188246
Datum
189247
pg_set_relation_stats(PG_FUNCTION_ARGS)
190248
{
191-
relation_statistics_update(fcinfo, ERROR);
249+
relation_statistics_update(fcinfo, ERROR, false);
192250
PG_RETURN_VOID();
193251
}
194252

@@ -212,7 +270,7 @@ pg_clear_relation_stats(PG_FUNCTION_ARGS)
212270
newfcinfo->args[3].value = DEFAULT_RELALLVISIBLE;
213271
newfcinfo->args[3].isnull = false;
214272

215-
relation_statistics_update(newfcinfo, ERROR);
273+
relation_statistics_update(newfcinfo, ERROR, false);
216274
PG_RETURN_VOID();
217275
}
218276

@@ -230,7 +288,7 @@ pg_restore_relation_stats(PG_FUNCTION_ARGS)
230288
relarginfo, WARNING))
231289
result = false;
232290

233-
if (!relation_statistics_update(positional_fcinfo, WARNING))
291+
if (!relation_statistics_update(positional_fcinfo, WARNING, true))
234292
result = false;
235293

236294
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy