vitessio/vitess · error

VT09016

VT09016

Error message

Cannot delete or update a parent row: a foreign key constraint fails

What it means

VT09016: the planner cannot build an UPDATE cascade operation for a foreign key declared ON UPDATE SET DEFAULT. Vitess does not implement propagating SET DEFAULT updates from parent to child rows, so it fails the statement with MySQL's parent-row FK message semantics.

Source

Thrown at go/vt/vtgate/planbuilder/operators/update.go:650

	var childWhereExpr sqlparser.Expr = compExpr

	// In the case of non-literal updates, we need to assign bindvariables for storing the updated value of the columns
	// coming from the SELECT query.
	if len(nonLiteralUpdateInfo) > 0 {
		for idx, info := range nonLiteralUpdateInfo {
			info.UpdateExprBvName = ctx.ReservedVars.ReserveVariable(foreignKeyUpdateExpr)
			nonLiteralUpdateInfo[idx] = info
		}
	}

	var childOp Operator
	switch fk.OnUpdate {
	case sqlparser.Cascade:
		childOp = buildChildUpdOpForCascade(ctx, fk, childWhereExpr, nonLiteralUpdateInfo, updatedTable)
	case sqlparser.SetNull:
		childOp = buildChildUpdOpForSetNull(ctx, fk, childWhereExpr, nonLiteralUpdateInfo, updatedTable)
	case sqlparser.SetDefault:
		panic(vterrors.VT09016())
	}

	return &FkChild{
		BVName:         bvName,
		Cols:           selectOffsets,
		Op:             childOp,
		NonLiteralInfo: nonLiteralUpdateInfo,
	}
}

// buildChildUpdOpForCascade builds the child update statement operator for the CASCADE type foreign key constraint.
// The query looks like this -
//
//	`UPDATE <child_table> SET <child_column_updated_using_update_exprs_from_parent_update_query> WHERE <child_columns_in_fk> IN (<bind variable for the output from SELECT>)`
func buildChildUpdOpForCascade(ctx *plancontext.PlanningContext, fk vindexes.ChildFKInfo, childWhereExpr sqlparser.Expr, nonLiteralUpdateInfo []engine.NonLiteralUpdateInfo, updatedTable *vindexes.BaseTable) Operator {
	// The update expressions are the same as the update expressions in the parent update query
	// with the column names replaced with the child column names.
	var childUpdateExprs sqlparser.UpdateExprs

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Change the child FK to ON UPDATE CASCADE, SET NULL, or NO ACTION.
  2. If the default behavior is required, handle the update in application logic across parent and child tables.
  3. Set foreign_key_mode to unmanaged so MySQL (not Vitess) enforces FKs, if compatible.
  4. Rework the schema to avoid SET DEFAULT on update.

Example fix

-- before
FOREIGN KEY (pid) REFERENCES parent(id) ON UPDATE SET DEFAULT;
-- after
FOREIGN KEY (pid) REFERENCES parent(id) ON UPDATE CASCADE;
Defensive patterns

Strategy: validation

Validate before calling

// Reject schemas using ON UPDATE SET DEFAULT under managed FKs
for _, fk := range allForeignKeys(schema) {
  if fk.OnUpdate == "SET DEFAULT" {
    return fmt.Errorf("FK %s uses unsupported ON UPDATE SET DEFAULT", fk.Name)
  }
}

Try / catch

res, err := vtgate.Execute(ctx, session, query, vars)
if err != nil && strings.Contains(err.Error(), "VT09016") {
  return applySchemaFix() // migrate FK to CASCADE/SET NULL and retry
}

Prevention

When it happens

Trigger: Updating a parent table's referenced column where the child FK is defined with ON UPDATE SET DEFAULT, reached via createFkChildForUpdate's switch on fk.OnUpdate.

Common situations: Schemas written for MySQL using ON UPDATE SET DEFAULT that are then run behind Vitess with managed foreign keys; legacy schemas where SET DEFAULT was common.

Related errors


AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01). Data as JSON: /api/errors/df91f705bdcc098e. Report an issue: GitHub.