vitessio/vitess · error

VT12002

VT12002

Error message

unsupported: cross-shard foreign keys between table '%s' and '%s'

What it means

Vitess cannot plan an UPDATE to a parent table that participates in a cross-shard ON UPDATE RESTRICT foreign key unless vtgate is configured to verify all foreign keys at the VTGate level (ctx.VerifyAllFKs). When only some FKs are verified locally, a cross-shard RESTRICT check cannot be done safely, so planning panics with VT12002 naming the parent and child tables.

Source

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

	if len(vTbl.UniqueKeys) > 0 {
		return sqlparser.ForUpdateLockNoWait
	}
	return sqlparser.ForUpdateLock
}

// Each child foreign key constraint is verified by a join query of the form:
// select 1 from child_tbl join parent_tbl on <columns in fk> where <clause same as original update> [AND ({<bind variables in the SET clause of the original update> IS NULL OR}... <child_columns_in_fk> NOT IN (<bind variables in the SET clause of the original update>))] limit 1
// E.g:
// Child (c1, c2) references Parent (p1, p2)
// update Parent set p1 = col + 1 where id = 1
// verify query:
// select 1 from Child join Parent on Parent.p1 = Child.c1 and Parent.p2 = Child.c2
// where Parent.id = 1 and ((Parent.col + 1) IS NULL OR (child.c1) NOT IN ((Parent.col + 1))) limit 1
func createFkVerifyOpForChildFKForUpdate(ctx *plancontext.PlanningContext, updatedTable *vindexes.BaseTable, updStmt *sqlparser.Update, cFk vindexes.ChildFKInfo) Operator {
	// ON UPDATE RESTRICT foreign keys that require validation, should only be allowed in the case where we
	// are verifying all the FKs on vtgate level.
	if !ctx.VerifyAllFKs {
		panic(vterrors.VT12002(updatedTable.String(), cFk.Table.String()))
	}

	parentTblExpr := sqlparser.NewAliasedTableExpr(updatedTable.GetTableName(), "parent")
	parentTbl, err := parentTblExpr.TableName()
	if err != nil {
		panic(err)
	}

	// Alias the foreign key's child table name
	childTblExpr := sqlparser.NewAliasedTableExpr(cFk.Table.GetTableName(), "child")
	childTbl, err := childTblExpr.TableName()
	if err != nil {
		panic(err)
	}

	var joinCond sqlparser.Expr
	for idx := range cFk.ParentColumns {
		joinExpr := &sqlparser.ComparisonExpr{

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Enable VTGate-level FK verification by starting vtgate with --verify_all_fks=true
  2. Set --foreign_key_mode to managed (or check the FK is not handled only at MySQL level) so cross-shard RESTRICT updates are supported
  3. Move the parent and child tables into the same shard/keyspace, or change the FK to not require ON UPDATE RESTRICT validation
  4. Rewrite the application to avoid updating the parent's referenced columns (update children first, or use surrogate keys)

Example fix

// before: vtgate started without FK verification
vtgate --foreign_key_mode=managed
// after
vtgate --foreign_key_mode=managed --verify_all_fks=true
Defensive patterns

Strategy: validation

Validate before calling

// Before issuing: parent-table UPDATE touching FK parent columns on a sharded keyspace
// ensure vtgate flags: --foreign_key_mode=managed --verify_all_fks=true
// App-side guard:
if updatingParentFKColumns(update) && clusterIsSharded {
    requireVTGateFlag(t, "verify_all_fks", true)
}

Try / catch

// Vitess returns VT12002 as a gRPC error
if verr, ok := err.(vtgo.Err); ok && verr.Code == 12002 {
    // fall back to explicit select-then-update flow
}

Prevention

When it happens

Trigger: Running an UPDATE that modifies columns of a parent table referenced by a RESTRICT foreign key whose parent and child live on different shards, while --verify_all_fks (VerifyAllFKs) is disabled or FK verification mode is not set to VTGate-level verification.

Common situations: Clusters with foreign keys enabled (foreign_key_mode=unmanaged is off) but verify_all_fks=false; sharded keyspaces where a RESTRICT FK spans shards; operators upgrading schemas to add FKs without changing vtgate FK settings.

Related errors


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