Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
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)