Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit f61d923

Browse files
jianhe-funCommitfest Bot
authored and
Commitfest Bot
committed
using indexscan to speedup add not null constraints
This patch tries to use index_beginscan() / index_getnext() / index_endscan() mentioned in [1] to speedup adding not-null constraints to the existing table. The main logic happens in phase3 ATRewriteTable 1. collect all not-null constraints. 2. For each not-null constraint, check whether there is a corresponding index available for validation. If not, then can not use indexscan to verify not-null constraints. 3. If any of the following conditions are true * table scan or rewrite is required, * table wasn't locked with `AccessExclusiveLock` * the NOT NULL constraint applies to a virtual generated column then index scan cannot be used for fast validation. 4. If all conditions are satisfied, attempt to use indexscan to verify whether the column contains any NULL values. concurrency concern: ALTER TABLE SET NOT NULL will take an ACCESS EXCLUSIVE lock, so there is less variant of racing issue can occur? to prove accurate, I wrote some isolation tests. see[2] performance: demo: case when: %20 percent values are NULL and have been deleted from heap but they still on the index. drop table if exists t; create unlogged table t(a int, b int) with (autovacuum_enabled = off, vacuum_index_cleanup=off); insert into t select case when g % 5 = 0 then null else g end, g+1 from generate_series(1,1_000_000) g; create index t_idx_a on t(a); delete from t where a is null; alter table t alter column a drop not null; alter table t add constraint t1 not null a; the above two statement running several times: patch Time:: 1.084 ms master Time: 12.045 ms references: [1] https://postgr.es/m/CA%2BTgmoa5NKz8iGW_9v7wz%3D-%2BzQFu%3DE4SZoaTaU1znLaEXRYp-Q%40mail.gmail.com [2] https://postgr.es/m/900056D1-32DF-4927-8251-3E0C0DC407FD%40anarazel.de discussion: https://postgr.es/m/CACJufxFiW=4k1is=F1J=r-Cx1RuByXQPUrWB331U47rSnGz+hw@mail.gmail.com commitfest entry: https://commitfest.postgresql.org/patch/5444
1 parent 44ce4e1 commit f61d923

File tree

8 files changed

+630
-5
lines changed

8 files changed

+630
-5
lines changed

src/backend/commands/tablecmds.c

Lines changed: 120 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -209,16 +209,19 @@ typedef struct AlteredTableInfo
209209
List *changedStatisticsDefs; /* string definitions of same */
210210
} AlteredTableInfo;
211211

212-
/* Struct describing one new constraint to check in Phase 3 scan */
213-
/* Note: new not-null constraints are handled elsewhere */
212+
/*
213+
* Struct describing one new constraint to check in Phase 3 scan. Note: new
214+
* not-null constraints are handled here too.
215+
*/
214216
typedef struct NewConstraint
215217
{
216218
char *name; /* Constraint name, or NULL if none */
217-
ConstrType contype; /* CHECK or FOREIGN */
219+
ConstrType contype; /* CHECK or FOREIGN or NOT NULL */
218220
Oid refrelid; /* PK rel, if FOREIGN */
219221
Oid refindid; /* OID of PK's index, if FOREIGN */
220222
bool conwithperiod; /* Whether the new FOREIGN KEY uses PERIOD */
221223
Oid conid; /* OID of pg_constraint entry, if FOREIGN */
224+
int attnum; /* NOT NULL constraint attribute number */
222225
Node *qual; /* Check expr or CONSTR_FOREIGN Constraint */
223226
ExprState *qualstate; /* Execution state for CHECK expr */
224227
} NewConstraint;
@@ -6182,6 +6185,9 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
61826185
needscan = true;
61836186
con->qualstate = ExecPrepareExpr((Expr *) expand_generated_columns_in_expr(con->qual, oldrel, 1), estate);
61846187
break;
6188+
case CONSTR_NOTNULL:
6189+
/* Nothing to do here */
6190+
break;
61856191
case CONSTR_FOREIGN:
61866192
/* Nothing to do here */
61876193
break;
@@ -6235,9 +6241,81 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
62356241
wholeatt->attnum);
62366242
}
62376243
}
6238-
if (notnull_attrs || notnull_virtual_attrs)
6244+
}
6245+
6246+
/*
6247+
* The conditions for using indexscan mechanism fast vertifing not-null
6248+
* constraints are quite strict. All of the following conditions must be
6249+
* met.
6250+
* 1. We must have the need to validate not-null constraints
6251+
* That means AlteredTableInfo->verify_new_notnull must be true.
6252+
* 2. If table scan or table rewrite is expected later, using indexscan is
6253+
* just wastes cycles.
6254+
* 3. Indexes cannot be created on virtual generated columns, so fast
6255+
* checking not-null constraints is not applicable to them.
6256+
* 4. Only apply to regular relations.
6257+
* 5. To avoid concurrency issue, we only do it when table was locked with
6258+
* an AccessExclusiveLock, which is the lock acquired during ALTER TABLE
6259+
* SET NOT NULL.
6260+
*/
6261+
if (!needscan &&
6262+
newrel == NULL &&
6263+
!tab->rewrite &&
6264+
tab->verify_new_notnull &&
6265+
notnull_virtual_attrs == NIL &&
6266+
oldrel->rd_rel->relkind == RELKIND_RELATION &&
6267+
CheckRelationLockedByMe(oldrel, AccessExclusiveLock, false))
6268+
{
6269+
List *notnull_attnums = NIL;
6270+
6271+
foreach(l, tab->constraints)
6272+
{
6273+
NewConstraint *con = lfirst(l);
6274+
6275+
Form_pg_attribute attr = TupleDescAttr(newTupDesc, con->attnum - 1);
6276+
6277+
if (con->contype != CONSTR_NOTNULL)
6278+
continue;
6279+
6280+
Assert(attr->attnotnull);
6281+
Assert(con->attnum > 0);
6282+
Assert(attr->attnum == con->attnum);
6283+
6284+
if (attr->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
6285+
{
6286+
needscan = true;
6287+
break;
6288+
}
6289+
6290+
notnull_attnums = lappend_int(notnull_attnums, attr->attnum);
6291+
}
6292+
6293+
/*
6294+
* notnull_attnums contains all attributes with NOT NULL constraints
6295+
* that need validation. If needscan is true, later table scan
6296+
* will do the validation, indedxscan would just waste cycle.
6297+
*/
6298+
if (notnull_attnums != NIL && !needscan)
6299+
{
6300+
if (!index_check_notnull(oldrel, notnull_attnums))
6301+
needscan = true;
6302+
else
6303+
ereport(DEBUG1,
6304+
errmsg_internal("all new not-null constraints on relation \"%s\" have been validated by using index scan",
6305+
RelationGetRelationName(oldrel)));
6306+
}
6307+
else
6308+
{
6309+
/*
6310+
* corner case: in Phase2, somehow we didn't add the NOT NULL
6311+
* constraint to AlteredTableInfo->constraints, but already set
6312+
* verify_new_notnull to true then table scan is needed.
6313+
*/
62396314
needscan = true;
6315+
}
62406316
}
6317+
else if (notnull_attrs || notnull_virtual_attrs)
6318+
needscan = true;
62416319

62426320
if (newrel || needscan)
62436321
{
@@ -7910,6 +7988,7 @@ ATExecSetNotNull(List **wqueue, Relation rel, char *conName, char *colName,
79107988
CookedConstraint *ccon;
79117989
List *cooked;
79127990
bool is_no_inherit = false;
7991+
AlteredTableInfo *tab = NULL;
79137992

79147993
/* Guard against stack overflow due to overly deep inheritance tree. */
79157994
check_stack_depth();
@@ -8034,10 +8113,31 @@ ATExecSetNotNull(List **wqueue, Relation rel, char *conName, char *colName,
80348113
constraint->is_no_inherit = is_no_inherit;
80358114
constraint->conname = conName;
80368115

8116+
tab = ATGetQueueEntry(wqueue, rel);
8117+
80378118
/* and do it */
80388119
cooked = AddRelationNewConstraints(rel, NIL, list_make1(constraint),
80398120
false, !recursing, false, NULL);
80408121
ccon = linitial(cooked);
8122+
8123+
Assert(ccon->contype == CONSTR_NOTNULL);
8124+
8125+
/*
8126+
* we may use indexscan to validate not-null constraint in Phase3. Add the
8127+
* to-be-validated not-null constraint to Phase 3's queue.
8128+
*/
8129+
if (!ccon->skip_validation)
8130+
{
8131+
NewConstraint *newcon;
8132+
newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
8133+
8134+
newcon->name = ccon->name;
8135+
newcon->contype = ccon->contype;
8136+
newcon->attnum = ccon->attnum;
8137+
8138+
tab->constraints = lappend(tab->constraints, newcon);
8139+
}
8140+
80418141
ObjectAddressSet(address, ConstraintRelationId, ccon->conoid);
80428142

80438143
InvokeObjectPostAlterHook(RelationRelationId,
@@ -9918,13 +10018,15 @@ ATAddCheckNNConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
991810018
{
991910019
CookedConstraint *ccon = (CookedConstraint *) lfirst(lcon);
992010020

9921-
if (!ccon->skip_validation && ccon->contype != CONSTR_NOTNULL)
10021+
if (!ccon->skip_validation)
992210022
{
992310023
NewConstraint *newcon;
992410024

992510025
newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
992610026
newcon->name = ccon->name;
992710027
newcon->contype = ccon->contype;
10028+
if (ccon->contype == CONSTR_NOTNULL)
10029+
newcon->attnum = ccon->attnum;
992810030
newcon->qual = ccon->expr;
992910031

993010032
tab->constraints = lappend(tab->constraints, newcon);
@@ -13166,6 +13268,7 @@ QueueNNConstraintValidation(List **wqueue, Relation conrel, Relation rel,
1316613268
List *children = NIL;
1316713269
AttrNumber attnum;
1316813270
char *colname;
13271+
NewConstraint *newcon;
1316913272

1317013273
con = (Form_pg_constraint) GETSTRUCT(contuple);
1317113274
Assert(con->contype == CONSTRAINT_NOTNULL);
@@ -13229,7 +13332,19 @@ QueueNNConstraintValidation(List **wqueue, Relation conrel, Relation rel,
1322913332
/* Set attnotnull appropriately without queueing another validation */
1323013333
set_attnotnull(NULL, rel, attnum, true, false);
1323113334

13335+
/*
13336+
* Queue validation for phase 3. ALTER TABLE SET NOT NULL will add NOT NULL
13337+
* constraint to AlteredTableInfo->constraints, for consistency, do the same
13338+
* here.
13339+
*/
13340+
newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
13341+
newcon->name = colname;
13342+
newcon->contype = CONSTR_NOTNULL;
13343+
newcon->attnum = attnum;
13344+
13345+
/* Find or create work queue entry for this table */
1323213346
tab = ATGetQueueEntry(wqueue, rel);
13347+
tab->constraints = lappend(tab->constraints, newcon);
1323313348
tab->verify_new_notnull = true;
1323413349

1323513350
/*

0 commit comments

Comments
 (0)