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.UpdateExprsView on GitHub (pinned to 01a25a7d17)
Solutions
- Change the child FK to ON UPDATE CASCADE, SET NULL, or NO ACTION.
- If the default behavior is required, handle the update in application logic across parent and child tables.
- Set foreign_key_mode to unmanaged so MySQL (not Vitess) enforces FKs, if compatible.
- 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
- Audit DDL for ON UPDATE SET DEFAULT before enabling managed FK mode
- Use CASCADE or SET NULL instead of SET DEFAULT
- Keep FK behavior within Vitess's supported set (CASCADE, SET NULL, NO ACTION)
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
- VT12002
- VT09016
- Foreign key found
- Foreign key found
- both atomic copy and partial mode cannot be specified for th
AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01).
Data as JSON: /api/errors/df91f705bdcc098e.
Report an issue: GitHub.